mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-21 10:49:18 +00:00
Compare commits
34 Commits
v3.0.0-bet
...
v3.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2a591356e | ||
| 8d24ec6373 | |||
| 58430d947b | |||
| 1533125f9d | |||
| 5654060dc8 | |||
| 84079c3934 | |||
|
|
48804dc2e3 | ||
|
|
8cfa8b7dab | ||
|
|
93a4d7e55d | ||
|
|
7c424a7966 | ||
| 678210d58a | |||
| 4302821615 | |||
| c4b4244b59 | |||
| 52c138bf06 | |||
| 813c8aa539 | |||
| 0f0aaab795 | |||
| b21892be31 | |||
| 247d6da94b | |||
| bd95faa9c3 | |||
| 2c57e4960f | |||
| cff4be2979 | |||
|
|
e1da90943c | ||
|
|
b5713db333 | ||
|
|
4c3f5e9836 | ||
|
|
e9ce02feb0 | ||
|
|
9a378d85ed | ||
|
|
c8ff45f2c8 | ||
|
|
34e4513c69 | ||
| a015971c20 | |||
| e67d5998b3 | |||
| 91bc23edfc | |||
| c9c3b32376 | |||
| 0dc123b064 | |||
| 1e62d4664c |
24
.github/workflows/build.yaml
vendored
24
.github/workflows/build.yaml
vendored
@@ -36,9 +36,9 @@ jobs:
|
|||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-targets --all-features
|
run: cargo check --all-targets --all-features --workspace
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
run: cargo clippy --all-targets --all-features
|
run: cargo clippy --all-targets --all-features --workspace
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -92,7 +92,7 @@ jobs:
|
|||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --features all
|
run: cargo test --release --all-features --workspace
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -146,13 +146,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
. .venv/bin/activate
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -213,13 +214,14 @@ jobs:
|
|||||||
sudo apt-get -y install ${{ matrix.packages }}
|
sudo apt-get -y install ${{ matrix.packages }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
. .venv/bin/activate
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ repos:
|
|||||||
description: Run cargo clippy on all project files.
|
description: Run cargo clippy on all project files.
|
||||||
language: system
|
language: system
|
||||||
entry: cargo
|
entry: cargo
|
||||||
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
|
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
- id: cargo-deny
|
- id: cargo-deny
|
||||||
name: cargo deny
|
name: cargo deny
|
||||||
|
|||||||
425
Cargo.lock
generated
425
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@@ -5,18 +5,28 @@ members = [
|
|||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
"objdiff-wasm",
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
|
default-members = [
|
||||||
|
"objdiff-cli",
|
||||||
|
"objdiff-core",
|
||||||
|
"objdiff-gui",
|
||||||
|
# Exclude objdiff-wasm by default
|
||||||
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "3.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]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[profile.release-min]
|
||||||
version = "3.0.0-beta.12"
|
inherits = "release-lto"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
opt-level = "z"
|
||||||
edition = "2024"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
repository = "https://github.com/encounter/objdiff"
|
|
||||||
rust-version = "1.88"
|
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -90,22 +90,31 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
"build_base": true,
|
"build_base": true,
|
||||||
"watch_patterns": [
|
"watch_patterns": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
],
|
],
|
||||||
|
"ignore_patterns": [
|
||||||
|
"build/**/*"
|
||||||
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "main/MetroTRK/mslsupp",
|
"name": "main/MetroTRK/mslsupp",
|
||||||
@@ -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 any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
||||||
If not specified, objdiff will use the default patterns listed above.
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
|
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
|
||||||
|
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
|
|
||||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
|
|||||||
@@ -57,23 +57,39 @@
|
|||||||
},
|
},
|
||||||
"default": [
|
"default": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignore_patterns": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"build/**/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Use units instead.",
|
"description": "Use units instead.",
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ ignore = [
|
|||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
# If this is false, then it uses a built-in git library.
|
# If this is false, then it uses a built-in git library.
|
||||||
@@ -241,8 +242,8 @@ allow-git = []
|
|||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# github.com organizations to allow git sources for
|
# github.com organizations to allow git sources for
|
||||||
github = [
|
github = [
|
||||||
"enarx", # flagset
|
|
||||||
"encounter",
|
"encounter",
|
||||||
|
"gimli-rs", # gimli
|
||||||
]
|
]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use crossterm::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::diff::DiffResult,
|
|
||||||
build::{
|
build::{
|
||||||
BuildConfig, BuildStatus,
|
BuildConfig, BuildStatus,
|
||||||
watcher::{Watcher, create_watcher},
|
watcher::{Watcher, create_watcher},
|
||||||
@@ -28,7 +27,7 @@ use objdiff_core::{
|
|||||||
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
||||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
},
|
},
|
||||||
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
|
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
|
||||||
jobs::{
|
jobs::{
|
||||||
Job, JobQueue, JobResult,
|
Job, JobQueue, JobResult,
|
||||||
objdiff::{ObjDiffConfig, start_build},
|
objdiff::{ObjDiffConfig, start_build},
|
||||||
@@ -40,10 +39,7 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd::apply_config_args,
|
cmd::apply_config_args,
|
||||||
util::{
|
util::term::crossterm_panic_handler,
|
||||||
output::{OutputFormat, write_output},
|
|
||||||
term::crossterm_panic_handler,
|
|
||||||
},
|
|
||||||
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
|
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,12 +59,6 @@ pub struct Args {
|
|||||||
#[argp(option, short = 'u')]
|
#[argp(option, short = 'u')]
|
||||||
/// Unit name within project
|
/// Unit name within project
|
||||||
unit: Option<String>,
|
unit: Option<String>,
|
||||||
#[argp(option, short = 'o', 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)]
|
#[argp(positional)]
|
||||||
/// Function symbol to diff
|
/// Function symbol to diff
|
||||||
symbol: Option<String>,
|
symbol: Option<String>,
|
||||||
@@ -171,11 +161,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
_ => bail!("Either target and base or project and unit must be specified"),
|
_ => bail!("Either target and base or project and unit must be specified"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(output) = &args.output {
|
run_interactive(args, target_path, base_path, project_config)
|
||||||
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
|
|
||||||
} else {
|
|
||||||
run_interactive(args, target_path, base_path, project_config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
||||||
@@ -194,28 +180,6 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
|
|||||||
Ok((diff_config, mapping_config))
|
Ok((diff_config, mapping_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_oneshot(
|
|
||||||
args: &Args,
|
|
||||||
output: &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 struct AppState {
|
||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub waker: Arc<TermWaker>,
|
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) {
|
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
|
||||||
let watch_patterns = project_config.build_watch_patterns()?;
|
let watch_patterns = project_config.build_watch_patterns()?;
|
||||||
|
let ignore_patterns = project_config.build_ignore_patterns()?;
|
||||||
state.watcher = Some(create_watcher(
|
state.watcher = Some(create_watcher(
|
||||||
state.modified.clone(),
|
state.modified.clone(),
|
||||||
project_dir.as_ref(),
|
project_dir.as_ref(),
|
||||||
build_globset(&watch_patterns)?,
|
build_globset(&watch_patterns)?,
|
||||||
|
build_globset(&ignore_patterns)?,
|
||||||
Waker::from(state.waker.clone()),
|
Waker::from(state.waker.clone()),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,13 +248,12 @@ fn report_object(
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(existing_functions) = &mut existing_functions {
|
if let Some(existing_functions) = &mut existing_functions
|
||||||
if (symbol.flags.contains(SymbolFlag::Global)
|
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||||
&& !existing_functions.insert(symbol.name.clone())
|
&& !existing_functions.insert(symbol.name.clone())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
|
|||||||
@@ -170,32 +170,31 @@ impl UiView for FunctionDiffUi {
|
|||||||
|
|
||||||
let mut prev_text = None;
|
let mut prev_text = None;
|
||||||
let mut prev_margin_text = None;
|
let mut prev_margin_text = None;
|
||||||
if self.three_way {
|
if self.three_way
|
||||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
&& let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||||
{
|
{
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||||
self.print_sym(
|
self.print_sym(
|
||||||
&mut text,
|
&mut text,
|
||||||
obj,
|
obj,
|
||||||
symbol_idx,
|
symbol_idx,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
&state.diff_obj_config,
|
&state.diff_obj_config,
|
||||||
rect,
|
rect,
|
||||||
&self.right_highlight,
|
&self.right_highlight,
|
||||||
result,
|
result,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
max_width = max_width.max(text.width());
|
max_width = max_width.max(text.width());
|
||||||
prev_text = Some(text);
|
prev_text = Some(text);
|
||||||
|
|
||||||
// Render margin
|
// Render margin
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||||
self.print_margin(&mut text, symbol_diff, rect);
|
self.print_margin(&mut text, symbol_diff, rect);
|
||||||
prev_margin_text = Some(text);
|
prev_margin_text = Some(text);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_scroll_x =
|
let max_scroll_x =
|
||||||
@@ -561,10 +560,12 @@ impl FunctionDiffUi {
|
|||||||
let len = label_text.len();
|
let len = label_text.len();
|
||||||
let highlighted =
|
let highlighted =
|
||||||
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||||
if let Some((cx, cy)) = result.click_xy {
|
if let Some((cx, cy)) = result.click_xy
|
||||||
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
&& cx >= sx
|
||||||
new_highlight = Some(highlight_kind);
|
&& cx < sx + len as u16
|
||||||
}
|
&& cy == sy
|
||||||
|
{
|
||||||
|
new_highlight = Some(highlight_kind);
|
||||||
}
|
}
|
||||||
let mut style = Style::new().fg(match segment.color {
|
let mut style = Style::new().fg(match segment.color {
|
||||||
DiffTextColor::Normal => Color::Gray,
|
DiffTextColor::Normal => Color::Gray,
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ config = [
|
|||||||
"dep:semver",
|
"dep:semver",
|
||||||
"dep:typed-path",
|
"dep:typed-path",
|
||||||
]
|
]
|
||||||
dwarf = ["dep:gimli"]
|
dwarf = [
|
||||||
|
"dep:gimli",
|
||||||
|
"dep:typed-arena",
|
||||||
|
]
|
||||||
serde = [
|
serde = [
|
||||||
"dep:pbjson",
|
"dep:pbjson",
|
||||||
"dep:pbjson-build",
|
"dep:pbjson-build",
|
||||||
@@ -78,6 +81,7 @@ std = [
|
|||||||
"prost?/std",
|
"prost?/std",
|
||||||
"serde?/std",
|
"serde?/std",
|
||||||
"similar?/std",
|
"similar?/std",
|
||||||
|
"typed-arena?/std",
|
||||||
"typed-path?/std",
|
"typed-path?/std",
|
||||||
"dep:filetime",
|
"dep:filetime",
|
||||||
"dep:memmap2",
|
"dep:memmap2",
|
||||||
@@ -128,12 +132,12 @@ itertools = { version = "0.14", default-features = false, features = ["use_alloc
|
|||||||
log = { version = "0.4", default-features = false, optional = true }
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
memmap2 = { version = "0.9", optional = true }
|
memmap2 = { version = "0.9", optional = true }
|
||||||
num-traits = { version = "0.2", default-features = false, optional = true }
|
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||||
object = { git = "https://github.com/gimli-rs/object", rev = "16ff70aa6fbd97d6bb7b92375929f4d72414c32b", default-features = false, features = ["read_core", "elf", "coff"] }
|
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||||
pbjson = { version = "0.8", default-features = false, optional = true }
|
pbjson = { version = "0.8", default-features = false, optional = true }
|
||||||
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
|
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
|
||||||
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
similar = { 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 }
|
typed-path = { version = "0.11", default-features = false, optional = true }
|
||||||
|
|
||||||
# config
|
# config
|
||||||
@@ -142,7 +146,8 @@ semver = { version = "1.0", default-features = false, optional = true }
|
|||||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||||
|
|
||||||
# dwarf
|
# dwarf
|
||||||
gimli = { version = "0.32", 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
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -464,6 +464,22 @@ impl Arch for ArchArm {
|
|||||||
}
|
}
|
||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_function_size(
|
||||||
|
&self,
|
||||||
|
symbol: &Symbol,
|
||||||
|
section: &Section,
|
||||||
|
mut next_address: u64,
|
||||||
|
) -> Result<u64> {
|
||||||
|
// Trim any trailing 4-byte zeroes from the end (padding)
|
||||||
|
while next_address >= symbol.address + 4
|
||||||
|
&& let Some(data) = section.data_range(next_address - 4, 4)
|
||||||
|
&& data == [0u8; 4]
|
||||||
|
{
|
||||||
|
next_address -= 4;
|
||||||
|
}
|
||||||
|
Ok(next_address.saturating_sub(symbol.address))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
|||||||
@@ -509,25 +509,25 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
return "ubfx";
|
return "ubfx";
|
||||||
}
|
}
|
||||||
Opcode::SBFM => {
|
Opcode::SBFM => {
|
||||||
if let Operand::Immediate(63) = ins.operands[3] {
|
if let Operand::Immediate(63) = ins.operands[3]
|
||||||
if let Operand::Register(SizeCode::X, _) = ins.operands[0] {
|
&& let Operand::Register(SizeCode::X, _) = ins.operands[0]
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[1], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[1], ctx);
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_separator(args);
|
||||||
return "asr";
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
}
|
return "asr";
|
||||||
}
|
}
|
||||||
if let Operand::Immediate(31) = ins.operands[3] {
|
if let Operand::Immediate(31) = ins.operands[3]
|
||||||
if let Operand::Register(SizeCode::W, _) = ins.operands[0] {
|
&& let Operand::Register(SizeCode::W, _) = ins.operands[0]
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[1], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[1], ctx);
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_separator(args);
|
||||||
return "asr";
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
}
|
return "asr";
|
||||||
}
|
}
|
||||||
if let Operand::Immediate(0) = ins.operands[2] {
|
if let Operand::Immediate(0) = ins.operands[2] {
|
||||||
let newsrc = if let Operand::Register(_size, srcnum) = ins.operands[1] {
|
let newsrc = if let Operand::Register(_size, srcnum) = ins.operands[1] {
|
||||||
@@ -554,22 +554,21 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
if let (Operand::Immediate(imms), Operand::Immediate(immr)) =
|
if let (Operand::Immediate(imms), Operand::Immediate(immr)) =
|
||||||
(ins.operands[2], ins.operands[3])
|
(ins.operands[2], ins.operands[3])
|
||||||
|
&& immr < imms
|
||||||
{
|
{
|
||||||
if immr < imms {
|
let size = if let Operand::Register(size, _) = ins.operands[0] {
|
||||||
let size = if let Operand::Register(size, _) = ins.operands[0] {
|
if size == SizeCode::W { 32 } else { 64 }
|
||||||
if size == SizeCode::W { 32 } else { 64 }
|
} else {
|
||||||
} else {
|
unreachable!("operand 0 is always a register");
|
||||||
unreachable!("operand 0 is always a register");
|
};
|
||||||
};
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[1], ctx);
|
||||||
push_operand(args, &ins.operands[1], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_unsigned(args, (size - imms) as u64);
|
||||||
push_unsigned(args, (size - imms) as u64);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_unsigned(args, (immr + 1) as u64);
|
||||||
push_unsigned(args, (immr + 1) as u64);
|
return "sbfiz";
|
||||||
return "sbfiz";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// `sbfm` is never actually displayed: in the remaining case, it is always aliased to `sbfx`
|
// `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)) =
|
let width = if let (Operand::Immediate(lsb), Operand::Immediate(width)) =
|
||||||
@@ -593,15 +592,14 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
Opcode::EXTR => {
|
Opcode::EXTR => {
|
||||||
if let (Operand::Register(_, rn), Operand::Register(_, rm)) =
|
if let (Operand::Register(_, rn), Operand::Register(_, rm)) =
|
||||||
(ins.operands[1], ins.operands[2])
|
(ins.operands[1], ins.operands[2])
|
||||||
|
&& rn == rm
|
||||||
{
|
{
|
||||||
if rn == rm {
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[3], ctx);
|
||||||
push_operand(args, &ins.operands[3], ctx);
|
return "ror";
|
||||||
return "ror";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"extr"
|
"extr"
|
||||||
}
|
}
|
||||||
@@ -804,27 +802,24 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
"csneg"
|
"csneg"
|
||||||
}
|
}
|
||||||
Opcode::CSINC => {
|
Opcode::CSINC => {
|
||||||
if let (
|
if let (Operand::Register(_, n), Operand::Register(_, m), Operand::ConditionCode(cond)) =
|
||||||
Operand::Register(_, n),
|
(ins.operands[1], ins.operands[2], ins.operands[3])
|
||||||
Operand::Register(_, m),
|
&& n == m
|
||||||
Operand::ConditionCode(cond),
|
&& cond < 0b1110
|
||||||
) = (ins.operands[1], ins.operands[2], ins.operands[3])
|
|
||||||
{
|
{
|
||||||
if n == m && cond < 0b1110 {
|
return if n == 31 {
|
||||||
return if n == 31 {
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_condition_code(args, cond ^ 0x01);
|
||||||
push_condition_code(args, cond ^ 0x01);
|
"cset"
|
||||||
"cset"
|
} else {
|
||||||
} else {
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_operand(args, &ins.operands[1], ctx);
|
||||||
push_operand(args, &ins.operands[1], ctx);
|
push_separator(args);
|
||||||
push_separator(args);
|
push_condition_code(args, cond ^ 0x01);
|
||||||
push_condition_code(args, cond ^ 0x01);
|
"cinc"
|
||||||
"cinc"
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"csinc"
|
"csinc"
|
||||||
}
|
}
|
||||||
@@ -1200,15 +1195,13 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
Operand::Register(reg_sz, _),
|
Operand::Register(reg_sz, _),
|
||||||
Operand::SIMDRegisterElementsLane(_, _, elem_sz, _),
|
Operand::SIMDRegisterElementsLane(_, _, elem_sz, _),
|
||||||
) = (ins.operands[0], ins.operands[1])
|
) = (ins.operands[0], ins.operands[1])
|
||||||
|
&& ((reg_sz == SizeCode::W && elem_sz == SIMDSizeCode::S)
|
||||||
|
|| (reg_sz == SizeCode::X && elem_sz == SIMDSizeCode::D))
|
||||||
{
|
{
|
||||||
if (reg_sz == SizeCode::W && elem_sz == SIMDSizeCode::S)
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
|| (reg_sz == SizeCode::X && elem_sz == SIMDSizeCode::D)
|
push_separator(args);
|
||||||
{
|
push_operand(args, &ins.operands[1], ctx);
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
return "mov";
|
||||||
push_separator(args);
|
|
||||||
push_operand(args, &ins.operands[1], ctx);
|
|
||||||
return "mov";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"umov"
|
"umov"
|
||||||
}
|
}
|
||||||
@@ -1308,14 +1301,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDADDB(ar) => {
|
Opcode::LDADDB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "staddb" } else { "staddlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "staddb" } else { "staddlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldaddb"
|
"ldaddb"
|
||||||
@@ -1328,14 +1322,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDCLRB(ar) => {
|
Opcode::LDCLRB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stclrb" } else { "stclrlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stclrb" } else { "stclrlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldclrb"
|
"ldclrb"
|
||||||
@@ -1348,14 +1343,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDEORB(ar) => {
|
Opcode::LDEORB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "steorb" } else { "steorlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "steorb" } else { "steorlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldeorb"
|
"ldeorb"
|
||||||
@@ -1368,14 +1364,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSETB(ar) => {
|
Opcode::LDSETB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsetb" } else { "stsetlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsetb" } else { "stsetlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsetb"
|
"ldsetb"
|
||||||
@@ -1388,14 +1385,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMAXB(ar) => {
|
Opcode::LDSMAXB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsmaxb" } else { "stsmaxlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsmaxb" } else { "stsmaxlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsmaxb"
|
"ldsmaxb"
|
||||||
@@ -1408,14 +1406,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMINB(ar) => {
|
Opcode::LDSMINB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsminb" } else { "stsminlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsminb" } else { "stsminlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsminb"
|
"ldsminb"
|
||||||
@@ -1428,14 +1427,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMAXB(ar) => {
|
Opcode::LDUMAXB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stumaxb" } else { "stumaxlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stumaxb" } else { "stumaxlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldumaxb"
|
"ldumaxb"
|
||||||
@@ -1448,14 +1448,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMINB(ar) => {
|
Opcode::LDUMINB(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stuminb" } else { "stuminlb" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stuminb" } else { "stuminlb" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
// write!(fmt, "{}", self.opcode)?;
|
// write!(fmt, "{}", self.opcode)?;
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
@@ -1469,14 +1470,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDADDH(ar) => {
|
Opcode::LDADDH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "staddh" } else { "staddlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "staddh" } else { "staddlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldaddh"
|
"ldaddh"
|
||||||
@@ -1489,14 +1491,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDCLRH(ar) => {
|
Opcode::LDCLRH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stclrh" } else { "stclrlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stclrh" } else { "stclrlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldclrh"
|
"ldclrh"
|
||||||
@@ -1509,14 +1512,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDEORH(ar) => {
|
Opcode::LDEORH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "steorh" } else { "steorlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "steorh" } else { "steorlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldeorh"
|
"ldeorh"
|
||||||
@@ -1529,14 +1533,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSETH(ar) => {
|
Opcode::LDSETH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stseth" } else { "stsetlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stseth" } else { "stsetlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldseth"
|
"ldseth"
|
||||||
@@ -1549,14 +1554,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMAXH(ar) => {
|
Opcode::LDSMAXH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsmaxh" } else { "stsmaxlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsmaxh" } else { "stsmaxlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsmaxh"
|
"ldsmaxh"
|
||||||
@@ -1569,14 +1575,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMINH(ar) => {
|
Opcode::LDSMINH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsminh" } else { "stsminlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsminh" } else { "stsminlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsminh"
|
"ldsminh"
|
||||||
@@ -1589,14 +1596,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMAXH(ar) => {
|
Opcode::LDUMAXH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stumaxh" } else { "stumaxlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stumaxh" } else { "stumaxlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldumaxh"
|
"ldumaxh"
|
||||||
@@ -1609,14 +1617,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMINH(ar) => {
|
Opcode::LDUMINH(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stuminh" } else { "stuminlh" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stuminh" } else { "stuminlh" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"lduminh"
|
"lduminh"
|
||||||
@@ -1629,14 +1638,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDADD(ar) => {
|
Opcode::LDADD(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stadd" } else { "staddl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stadd" } else { "staddl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldadd"
|
"ldadd"
|
||||||
@@ -1649,14 +1659,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDCLR(ar) => {
|
Opcode::LDCLR(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stclr" } else { "stclrl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stclr" } else { "stclrl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldclr"
|
"ldclr"
|
||||||
@@ -1669,14 +1680,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDEOR(ar) => {
|
Opcode::LDEOR(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "steor" } else { "steorl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "steor" } else { "steorl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldeor"
|
"ldeor"
|
||||||
@@ -1689,14 +1701,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSET(ar) => {
|
Opcode::LDSET(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stset" } else { "stsetl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stset" } else { "stsetl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldset"
|
"ldset"
|
||||||
@@ -1709,14 +1722,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMAX(ar) => {
|
Opcode::LDSMAX(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsmax" } else { "stsmaxl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsmax" } else { "stsmaxl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsmax"
|
"ldsmax"
|
||||||
@@ -1729,14 +1743,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDSMIN(ar) => {
|
Opcode::LDSMIN(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stsmin" } else { "stsminl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stsmin" } else { "stsminl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldsmin"
|
"ldsmin"
|
||||||
@@ -1749,14 +1764,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMAX(ar) => {
|
Opcode::LDUMAX(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stumax" } else { "stumaxl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stumax" } else { "stumaxl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldumax"
|
"ldumax"
|
||||||
@@ -1769,14 +1785,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opcode::LDUMIN(ar) => {
|
Opcode::LDUMIN(ar) => {
|
||||||
if let Operand::Register(_, rt) = ins.operands[1] {
|
if let Operand::Register(_, rt) = ins.operands[1]
|
||||||
if rt == 31 && ar & 0b10 == 0b00 {
|
&& rt == 31
|
||||||
let inst = if ar & 0b01 == 0b00 { "stumin" } else { "stuminl" };
|
&& ar & 0b10 == 0b00
|
||||||
push_operand(args, &ins.operands[0], ctx);
|
{
|
||||||
push_separator(args);
|
let inst = if ar & 0b01 == 0b00 { "stumin" } else { "stuminl" };
|
||||||
push_operand(args, &ins.operands[2], ctx);
|
push_operand(args, &ins.operands[0], ctx);
|
||||||
return inst;
|
push_separator(args);
|
||||||
}
|
push_operand(args, &ins.operands[2], ctx);
|
||||||
|
return inst;
|
||||||
}
|
}
|
||||||
if ar == 0 {
|
if ar == 0 {
|
||||||
"ldumin"
|
"ldumin"
|
||||||
@@ -2067,16 +2084,15 @@ where Cb: FnMut(InstructionPart<'static>) {
|
|||||||
|
|
||||||
/// Relocations that appear in Operand::PCOffset.
|
/// Relocations that appear in Operand::PCOffset.
|
||||||
fn is_pc_offset_reloc(reloc: Option<ResolvedRelocation>) -> Option<ResolvedRelocation> {
|
fn is_pc_offset_reloc(reloc: Option<ResolvedRelocation>) -> Option<ResolvedRelocation> {
|
||||||
if let Some(resolved) = reloc {
|
if let Some(resolved) = reloc
|
||||||
if let RelocationFlags::Elf(
|
&& let RelocationFlags::Elf(
|
||||||
elf::R_AARCH64_ADR_PREL_PG_HI21
|
elf::R_AARCH64_ADR_PREL_PG_HI21
|
||||||
| elf::R_AARCH64_JUMP26
|
| elf::R_AARCH64_JUMP26
|
||||||
| elf::R_AARCH64_CALL26
|
| elf::R_AARCH64_CALL26
|
||||||
| elf::R_AARCH64_ADR_GOT_PAGE,
|
| elf::R_AARCH64_ADR_GOT_PAGE,
|
||||||
) = resolved.relocation.flags
|
) = resolved.relocation.flags
|
||||||
{
|
{
|
||||||
return Some(resolved);
|
return Some(resolved);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::{
|
|||||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||||
obj::{
|
obj::{
|
||||||
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
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 {
|
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
|
||||||
let isa_extension = match diff_config.mips_instr_category {
|
let isa_extension = match diff_config.mips_instr_category {
|
||||||
MipsInstrCategory::Auto => self.isa_extension,
|
MipsInstrCategory::Auto => self.isa_extension,
|
||||||
@@ -151,7 +159,7 @@ impl ArchMips {
|
|||||||
};
|
};
|
||||||
match isa_extension {
|
match isa_extension {
|
||||||
Some(extension) => rabbitizer::InstructionFlags::new_extension(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 {
|
.with_abi(match diff_config.mips_abi {
|
||||||
MipsAbi::Auto => self.abi,
|
MipsAbi::Auto => self.abi,
|
||||||
@@ -234,17 +242,16 @@ impl Arch for ArchMips {
|
|||||||
object::RelocationFlags::Elf { r_type } => {
|
object::RelocationFlags::Elf { r_type } => {
|
||||||
if relocation.has_implicit_addend() {
|
if relocation.has_implicit_addend() {
|
||||||
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
|
// 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 elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
|
||||||
if let Some(addend) = self
|
&& let Some(addend) = self
|
||||||
.paired_relocations
|
.paired_relocations
|
||||||
.get(section.index().0)
|
.get(section.index().0)
|
||||||
.and_then(|m| m.get(&address).copied())
|
.and_then(|m| m.get(&address).copied())
|
||||||
{
|
{
|
||||||
return Ok(Some(RelocationOverride {
|
return Ok(Some(RelocationOverride {
|
||||||
target: RelocationOverrideTarget::Keep,
|
target: RelocationOverrideTarget::Keep,
|
||||||
addend,
|
addend,
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = section.data()?;
|
let data = section.data()?;
|
||||||
@@ -331,6 +338,36 @@ impl Arch for ArchMips {
|
|||||||
}
|
}
|
||||||
flags
|
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(
|
fn push_args(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use alloc::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use core::{
|
use core::{
|
||||||
|
any::Any,
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
fmt::{self, Debug},
|
fmt::{self, Debug},
|
||||||
};
|
};
|
||||||
@@ -215,10 +216,10 @@ impl dyn Arch {
|
|||||||
|
|
||||||
// Remove any branch destinations that are outside the function range
|
// Remove any branch destinations that are outside the function range
|
||||||
for ins in result.iter_mut() {
|
for ins in result.iter_mut() {
|
||||||
if let Some(branch_dest) = ins.branch_dest {
|
if let Some(branch_dest) = ins.branch_dest
|
||||||
if branch_dest < function_start || branch_dest >= function_end {
|
&& (branch_dest < function_start || branch_dest >= function_end)
|
||||||
ins.branch_dest = None;
|
{
|
||||||
}
|
ins.branch_dest = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.
|
/// Finishes arch-specific initialization that must be done after sections have been combined.
|
||||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
||||||
|
|
||||||
@@ -406,6 +407,15 @@ pub trait Arch: Send + Sync + Debug {
|
|||||||
) -> Vec<ContextItem> {
|
) -> Vec<ContextItem> {
|
||||||
Vec::new()
|
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>> {
|
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
|
||||||
|
|||||||
@@ -514,14 +514,14 @@ pub fn ppc_data_flow_analysis(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
|
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
|
||||||
if let Some(sym) = obj.symbols.get(symbol_index) {
|
if let Some(sym) = obj.symbols.get(symbol_index)
|
||||||
if sym.name.starts_with("@stringBase") && offset.0 != 0 {
|
&& sym.name.starts_with("@stringBase")
|
||||||
if let Some(data) = obj.symbol_data(symbol_index) {
|
&& offset.0 != 0
|
||||||
let bytes = &data[offset.0 as usize..];
|
&& let Some(data) = obj.symbol_data(symbol_index)
|
||||||
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
|
{
|
||||||
return Some(str);
|
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
|
None
|
||||||
@@ -577,19 +577,17 @@ fn generate_flow_analysis_result(
|
|||||||
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
|
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)) =
|
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
|
||||||
(ins.op, args[1], args[2])
|
(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] {
|
// Show the string constant in the analysis result
|
||||||
if let Some(str) = get_string_data(obj, sym_index, offset) {
|
let formatted = format!("\"{str}\"");
|
||||||
// Show the string constant in the analysis result
|
analysis_result.set_argument_value_at_address(
|
||||||
let formatted = format!("\"{str}\"");
|
ins_address,
|
||||||
analysis_result.set_argument_value_at_address(
|
2,
|
||||||
ins_address,
|
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
|
||||||
2,
|
);
|
||||||
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
|
// Don't continue, we want to show the stringbase value as well
|
||||||
);
|
|
||||||
// Don't continue, we want to show the stringbase value as well
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_store = is_store_instruction(ins.op);
|
let is_store = is_store_instruction(ins.op);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
obj::{
|
obj::{
|
||||||
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
|
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() {
|
if file.is_64() {
|
||||||
powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
|
powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
|
||||||
} else {
|
} 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -455,6 +457,22 @@ impl Arch for ArchPpc {
|
|||||||
}
|
}
|
||||||
out
|
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 {
|
impl ArchPpc {
|
||||||
@@ -657,7 +675,7 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PoolReference {
|
struct PoolReference {
|
||||||
addr_src_gpr: powerpc::GPR,
|
addr_src_gpr: powerpc::GPR,
|
||||||
addr_offset: i16,
|
addr_offset: i64,
|
||||||
addr_dst_gpr: Option<powerpc::GPR>,
|
addr_dst_gpr: Option<powerpc::GPR>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,16 +683,20 @@ struct PoolReference {
|
|||||||
// If so, return information pertaining to where the instruction is getting that address from and
|
// 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).
|
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
|
||||||
fn get_pool_reference_for_inst(
|
fn get_pool_reference_for_inst(
|
||||||
opcode: powerpc::Opcode,
|
ins: powerpc::Ins,
|
||||||
simplified: &powerpc::ParsedIns,
|
simplified: &powerpc::ParsedIns,
|
||||||
) -> Option<PoolReference> {
|
) -> Option<PoolReference> {
|
||||||
use powerpc::{Argument, Opcode};
|
use powerpc::{Argument, Opcode};
|
||||||
let args = &simplified.args;
|
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]) {
|
match (args[1], args[2]) {
|
||||||
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
|
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
|
||||||
// e.g. lwz. Immediate offset.
|
// 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)) => {
|
(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.
|
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
|
||||||
@@ -694,17 +716,51 @@ fn get_pool_reference_for_inst(
|
|||||||
// If either of these match, we also want to return the destination register that the
|
// 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
|
// address is being copied into so that we can detect any future references to that new
|
||||||
// register as well.
|
// 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,
|
Opcode::Addi,
|
||||||
Argument::GPR(addr_dst_gpr),
|
Argument::GPR(addr_dst_gpr),
|
||||||
Argument::GPR(addr_src_gpr),
|
Argument::GPR(addr_src_gpr),
|
||||||
Argument::Simm(simm),
|
Argument::Simm(simm),
|
||||||
) => Some(PoolReference {
|
) => {
|
||||||
addr_src_gpr,
|
let offset = if simplified.mnemonic == "addi" { simm.0 } else { -simm.0 };
|
||||||
addr_offset: simm.0,
|
Some(PoolReference {
|
||||||
addr_dst_gpr: Some(addr_dst_gpr),
|
addr_src_gpr,
|
||||||
}),
|
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.`
|
// `mr` or `mr.`
|
||||||
Opcode::Or,
|
Opcode::Or,
|
||||||
@@ -759,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
|
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
|
||||||
// addend to indicate that.
|
// addend to indicate that.
|
||||||
fn make_fake_pool_reloc(
|
fn make_fake_pool_reloc(
|
||||||
offset: i16,
|
offset: i64,
|
||||||
cur_addr: u32,
|
cur_addr: u32,
|
||||||
pool_reloc: &Relocation,
|
pool_reloc: &Relocation,
|
||||||
symbols: &[Symbol],
|
symbols: &[Symbol],
|
||||||
) -> Option<Relocation> {
|
) -> Option<Relocation> {
|
||||||
let pool_reloc = resolve_relocation(symbols, pool_reloc);
|
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_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
|
||||||
let target_symbol;
|
let target_symbol;
|
||||||
let addend;
|
let addend;
|
||||||
@@ -850,44 +906,43 @@ fn generate_fake_pool_relocations_for_function(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(branch_dest) = branch_dest {
|
if let Some(branch_dest) = branch_dest
|
||||||
if branch_dest >= func_address as u32
|
&& branch_dest >= func_address as u32
|
||||||
&& (branch_dest - func_address as u32) < code.len() as u32
|
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||||
{
|
{
|
||||||
let dest_offset_into_func = branch_dest - func_address as u32;
|
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||||
match ins.op {
|
match ins.op {
|
||||||
Opcode::Bc => {
|
Opcode::Bc => {
|
||||||
// Conditional branch.
|
// Conditional branch.
|
||||||
// Add the branch destination to the queue to do later.
|
// Add the branch destination to the queue to do later.
|
||||||
|
ins_iters_with_gpr_state.push((
|
||||||
|
InsIter::new(dest_code_slice, branch_dest, extensions),
|
||||||
|
gpr_pool_relocs.clone(),
|
||||||
|
));
|
||||||
|
// Then continue on with the current iterator.
|
||||||
|
}
|
||||||
|
Opcode::B => {
|
||||||
|
if simplified.mnemonic != "bl" {
|
||||||
|
// Unconditional branch.
|
||||||
|
// Add the branch destination to the queue.
|
||||||
ins_iters_with_gpr_state.push((
|
ins_iters_with_gpr_state.push((
|
||||||
InsIter::new(dest_code_slice, branch_dest, extensions),
|
InsIter::new(dest_code_slice, branch_dest, extensions),
|
||||||
gpr_pool_relocs.clone(),
|
gpr_pool_relocs.clone(),
|
||||||
));
|
));
|
||||||
// Then continue on with the current iterator.
|
// Break out of the current iterator so we can do the newly added one.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Opcode::B => {
|
|
||||||
if simplified.mnemonic != "bl" {
|
|
||||||
// Unconditional branch.
|
|
||||||
// Add the branch destination to the queue.
|
|
||||||
ins_iters_with_gpr_state.push((
|
|
||||||
InsIter::new(dest_code_slice, branch_dest, extensions),
|
|
||||||
gpr_pool_relocs.clone(),
|
|
||||||
));
|
|
||||||
// Break out of the current iterator so we can do the newly added one.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Opcode::Bcctr = ins.op {
|
if let Opcode::Bcctr = ins.op
|
||||||
if simplified.mnemonic == "bctr" {
|
&& simplified.mnemonic == "bctr"
|
||||||
// Unconditional branch to count register.
|
{
|
||||||
// Likely a jump table.
|
// Unconditional branch to count register.
|
||||||
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
// 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.
|
// Then handle keeping track of which GPR contains which pool relocation.
|
||||||
@@ -929,7 +984,7 @@ fn generate_fake_pool_relocations_for_function(
|
|||||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
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
|
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||||
// the already-loaded pools.
|
// the already-loaded pools.
|
||||||
if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
|
if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
|
||||||
@@ -948,7 +1003,7 @@ fn generate_fake_pool_relocations_for_function(
|
|||||||
// with the offset within the .data section of an array variable into r21.
|
// 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.
|
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||||
let mut new_reloc = pool_reloc.clone();
|
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);
|
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||||
} else {
|
} else {
|
||||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
|
|||||||
use crate::{
|
use crate::{
|
||||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||||
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
||||||
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
|
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -303,6 +303,52 @@ impl Arch for ArchX86 {
|
|||||||
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
self.reloc_size(flags).unwrap_or(1)
|
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> {
|
struct InstructionFormatterOutput<'a> {
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,3 +1 @@
|
|||||||
#[cfg(feature = "any-arch")]
|
|
||||||
pub mod diff;
|
|
||||||
pub mod report;
|
pub mod report;
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
|
|||||||
};
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let mut command = {
|
let mut command = {
|
||||||
|
use alloc::borrow::Cow;
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
let mut command = if config.selected_wsl_distro.is_some() {
|
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}
|
// Strip distro root prefix \\wsl.localhost\{distro}
|
||||||
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
||||||
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
||||||
Ok(new_cwd) => Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()),
|
// Convert to absolute Unix path
|
||||||
Err(_) => cwd.with_unix_encoding(),
|
Ok(new_cwd) => Cow::Owned(
|
||||||
|
Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()).into_string(),
|
||||||
|
),
|
||||||
|
// Otherwise, use the Windows path as is
|
||||||
|
Err(_) => Cow::Borrowed(cwd.as_str()),
|
||||||
};
|
};
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("--cd")
|
.arg("--cd")
|
||||||
.arg(cwd.as_str())
|
.arg::<&str>(cwd.as_ref())
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg(distro)
|
.arg(distro)
|
||||||
.arg("--")
|
.arg("--")
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub fn create_watcher(
|
|||||||
modified: Arc<AtomicBool>,
|
modified: Arc<AtomicBool>,
|
||||||
project_dir: &Path,
|
project_dir: &Path,
|
||||||
patterns: GlobSet,
|
patterns: GlobSet,
|
||||||
|
ignore_patterns: GlobSet,
|
||||||
waker: Waker,
|
waker: Waker,
|
||||||
) -> notify::Result<Watcher> {
|
) -> notify::Result<Watcher> {
|
||||||
let base_dir = fs::canonicalize(project_dir)?;
|
let base_dir = fs::canonicalize(project_dir)?;
|
||||||
@@ -54,8 +55,8 @@ pub fn create_watcher(
|
|||||||
let Ok(path) = path.strip_prefix(&base_dir_clone) else {
|
let Ok(path) = path.strip_prefix(&base_dir_clone) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if patterns.is_match(path) {
|
if patterns.is_match(path) && !ignore_patterns.is_match(path) {
|
||||||
// log::info!("File modified: {}", path.display());
|
log::debug!("File modified: {}", path.display());
|
||||||
any_match = true;
|
any_match = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ pub struct ProjectConfig {
|
|||||||
pub build_target: Option<bool>,
|
pub build_target: Option<bool>,
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub watch_patterns: Option<Vec<String>>,
|
pub watch_patterns: Option<Vec<String>>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub ignore_patterns: Option<Vec<String>>,
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde",
|
feature = "serde",
|
||||||
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
||||||
@@ -66,7 +68,18 @@ impl ProjectConfig {
|
|||||||
.map(|s| Glob::new(s))
|
.map(|s| Glob::new(s))
|
||||||
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
||||||
} else {
|
} else {
|
||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
default_watch_patterns()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_ignore_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
|
||||||
|
Ok(if let Some(ignore_patterns) = &self.ignore_patterns {
|
||||||
|
ignore_patterns
|
||||||
|
.iter()
|
||||||
|
.map(|s| Glob::new(s))
|
||||||
|
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
||||||
|
} else {
|
||||||
|
default_ignore_patterns()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,14 +204,21 @@ pub struct ScratchConfig {
|
|||||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
||||||
|
|
||||||
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
||||||
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
|
"*.c", "*.cc", "*.cp", "*.cpp", "*.cxx", "*.c++", "*.h", "*.hh", "*.hp", "*.hpp", "*.hxx",
|
||||||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
"*.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> {
|
pub fn default_watch_patterns() -> Vec<Glob> {
|
||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_ignore_patterns() -> Vec<Glob> {
|
||||||
|
DEFAULT_IGNORE_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ProjectConfigInfo {
|
pub struct ProjectConfigInfo {
|
||||||
|
|||||||
@@ -496,6 +496,14 @@ fn diff_instruction(
|
|||||||
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
|
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);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
fn reloc_eq(
|
||||||
left_obj: &Object,
|
left_obj: &Object,
|
||||||
right_obj: &Object,
|
right_obj: &Object,
|
||||||
@@ -45,8 +59,8 @@ fn reloc_eq(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol_name_addend_matches =
|
let symbol_name_addend_matches = symbol_name_matches(&left.symbol.name, &right.symbol.name)
|
||||||
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
|
&& left.relocation.addend == right.relocation.addend;
|
||||||
match (left.symbol.section, right.symbol.section) {
|
match (left.symbol.section, right.symbol.section) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
// Match if section and name+addend or address match
|
// Match if section and name+addend or address match
|
||||||
@@ -127,6 +141,28 @@ fn diff_data_relocs_for_range<'left, 'right>(
|
|||||||
diffs
|
diffs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
|
||||||
|
let section = &obj.sections[section_idx];
|
||||||
|
let len = section.data.len();
|
||||||
|
let data = §ion.data[0..len];
|
||||||
|
|
||||||
|
let data_diff =
|
||||||
|
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
|
||||||
|
|
||||||
|
let mut reloc_diffs = Vec::new();
|
||||||
|
for reloc in section.relocations.iter() {
|
||||||
|
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
|
||||||
|
let range = reloc.address as usize..reloc.address as usize + reloc_len;
|
||||||
|
reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: reloc.clone(),
|
||||||
|
kind: DataDiffKind::None,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SectionDiff { match_percent: Some(0.0), data_diff, reloc_diff: reloc_diffs })
|
||||||
|
}
|
||||||
|
|
||||||
/// Compare the data sections of two object files.
|
/// Compare the data sections of two object files.
|
||||||
pub fn diff_data_section(
|
pub fn diff_data_section(
|
||||||
left_obj: &Object,
|
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.
|
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
||||||
pub fn diff_bss_section(
|
pub fn diff_bss_section(
|
||||||
left_obj: &Object,
|
left_obj: &Object,
|
||||||
|
|||||||
@@ -385,13 +385,13 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
|||||||
if let Some(name) = &symbol.demangled_name {
|
if let Some(name) = &symbol.demangled_name {
|
||||||
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
||||||
}
|
}
|
||||||
if symbol.section.is_some() {
|
if symbol.section.is_some()
|
||||||
if let Some(address) = symbol.virtual_address {
|
&& let Some(address) = symbol.virtual_address
|
||||||
out.push(ContextItem::Copy {
|
{
|
||||||
value: format!("{address:x}"),
|
out.push(ContextItem::Copy {
|
||||||
label: Some("virtual address".to_string()),
|
value: format!("{address:x}"),
|
||||||
});
|
label: Some("virtual address".to_string()),
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||||
out
|
out
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
code::{diff_code, no_diff_code},
|
code::{diff_code, no_diff_code},
|
||||||
data::{
|
data::{
|
||||||
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
|
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},
|
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
|
||||||
@@ -288,52 +288,84 @@ pub fn diff_objs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for section_match in section_matches {
|
for section_match in section_matches {
|
||||||
if let SectionMatch {
|
match section_match {
|
||||||
left: Some(left_section_idx),
|
SectionMatch {
|
||||||
right: Some(right_section_idx),
|
left: Some(left_section_idx),
|
||||||
section_kind,
|
right: Some(right_section_idx),
|
||||||
} = section_match
|
section_kind,
|
||||||
{
|
} => {
|
||||||
let (left_obj, left_out) = left.as_mut().unwrap();
|
let (left_obj, left_out) = left.as_mut().unwrap();
|
||||||
let (right_obj, right_out) = right.as_mut().unwrap();
|
let (right_obj, right_out) = right.as_mut().unwrap();
|
||||||
match section_kind {
|
match section_kind {
|
||||||
SectionKind::Code => {
|
SectionKind::Code => {
|
||||||
let (left_diff, right_diff) = diff_generic_section(
|
let (left_diff, right_diff) = diff_generic_section(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
left_out,
|
left_out,
|
||||||
right_out,
|
right_out,
|
||||||
left_section_idx,
|
left_section_idx,
|
||||||
right_section_idx,
|
right_section_idx,
|
||||||
)?;
|
)?;
|
||||||
left_out.sections[left_section_idx] = left_diff;
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
right_out.sections[right_section_idx] = right_diff;
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
|
}
|
||||||
|
SectionKind::Data => {
|
||||||
|
let (left_diff, right_diff) = diff_data_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_out,
|
||||||
|
right_out,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
|
}
|
||||||
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
|
let (left_diff, right_diff) = diff_bss_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_out,
|
||||||
|
right_out,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
SectionKind::Data => {
|
}
|
||||||
let (left_diff, right_diff) = diff_data_section(
|
SectionMatch { left: Some(left_section_idx), right: None, section_kind } => {
|
||||||
left_obj,
|
let (left_obj, left_out) = left.as_mut().unwrap();
|
||||||
right_obj,
|
match section_kind {
|
||||||
left_out,
|
SectionKind::Code => {}
|
||||||
right_out,
|
SectionKind::Data => {
|
||||||
left_section_idx,
|
left_out.sections[left_section_idx] =
|
||||||
right_section_idx,
|
no_diff_data_section(left_obj, left_section_idx)?;
|
||||||
)?;
|
}
|
||||||
left_out.sections[left_section_idx] = left_diff;
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
right_out.sections[right_section_idx] = right_diff;
|
left_out.sections[left_section_idx] = no_diff_bss_section()?;
|
||||||
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
SectionKind::Bss | SectionKind::Common => {
|
}
|
||||||
let (left_diff, right_diff) = diff_bss_section(
|
SectionMatch { left: None, right: Some(right_section_idx), section_kind } => {
|
||||||
left_obj,
|
let (right_obj, right_out) = right.as_mut().unwrap();
|
||||||
right_obj,
|
match section_kind {
|
||||||
left_out,
|
SectionKind::Code => {}
|
||||||
right_out,
|
SectionKind::Data => {
|
||||||
left_section_idx,
|
right_out.sections[right_section_idx] =
|
||||||
right_section_idx,
|
no_diff_data_section(right_obj, right_section_idx)?;
|
||||||
)?;
|
}
|
||||||
left_out.sections[left_section_idx] = left_diff;
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
right_out.sections[right_section_idx] = right_diff;
|
right_out.sections[right_section_idx] = no_diff_bss_section()?;
|
||||||
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
SectionKind::Unknown => unreachable!(),
|
}
|
||||||
|
SectionMatch { left: None, right: None, .. } => {
|
||||||
|
// Should not happen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,15 +499,15 @@ fn apply_symbol_mappings(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// If we're selecting a symbol to use as a comparison, mark it as used
|
// 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
|
// 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_name) = &mapping_config.selecting_left
|
||||||
if let Some(left_symbol) = left.symbol_by_name(left_name) {
|
&& let Some(left_symbol) = left.symbol_by_name(left_name)
|
||||||
left_used.insert(left_symbol);
|
{
|
||||||
}
|
left_used.insert(left_symbol);
|
||||||
}
|
}
|
||||||
if let Some(right_name) = &mapping_config.selecting_right {
|
if let Some(right_name) = &mapping_config.selecting_right
|
||||||
if let Some(right_symbol) = right.symbol_by_name(right_name) {
|
&& let Some(right_symbol) = right.symbol_by_name(right_name)
|
||||||
right_used.insert(right_symbol);
|
{
|
||||||
}
|
right_used.insert(right_symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply manual symbol mappings
|
// Apply manual symbol mappings
|
||||||
@@ -543,47 +575,60 @@ fn matching_symbols(
|
|||||||
&mut matches,
|
&mut matches,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
|
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
|
||||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
// those are correct first, while the second pass catches near matches.
|
||||||
continue;
|
for fuzzy_literals in [false, true] {
|
||||||
}
|
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
|
||||||
let section_kind = symbol_section_kind(left, symbol);
|
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||||
if section_kind == SectionKind::Unknown {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
let section_kind = symbol_section_kind(left, symbol);
|
||||||
if left_used.contains(&symbol_idx) {
|
if section_kind == SectionKind::Unknown {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let symbol_match = SymbolMatch {
|
if left_used.contains(&symbol_idx) {
|
||||||
left: Some(symbol_idx),
|
continue;
|
||||||
right: find_symbol(right, left, symbol, Some(&right_used)),
|
}
|
||||||
prev: find_symbol(prev, left, symbol, None),
|
let symbol_match = SymbolMatch {
|
||||||
section_kind,
|
left: Some(symbol_idx),
|
||||||
};
|
right: find_symbol(right, left, symbol_idx, Some(&right_used), fuzzy_literals),
|
||||||
matches.push(symbol_match);
|
prev: find_symbol(prev, left, symbol_idx, None, fuzzy_literals),
|
||||||
if let Some(right) = symbol_match.right {
|
section_kind,
|
||||||
right_used.insert(right);
|
};
|
||||||
|
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 {
|
if let Some(right) = right {
|
||||||
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
|
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
|
||||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
// those are correct first, while the second pass catches near matches.
|
||||||
continue;
|
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;
|
||||||
|
}
|
||||||
|
let section_kind = symbol_section_kind(right, symbol);
|
||||||
|
if section_kind == SectionKind::Unknown {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if right_used.contains(&symbol_idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let symbol_match = SymbolMatch {
|
||||||
|
left: None,
|
||||||
|
right: Some(symbol_idx),
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let section_kind = symbol_section_kind(right, symbol);
|
|
||||||
if section_kind == SectionKind::Unknown {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if right_used.contains(&symbol_idx) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
matches.push(SymbolMatch {
|
|
||||||
left: None,
|
|
||||||
right: Some(symbol_idx),
|
|
||||||
prev: find_symbol(prev, right, symbol, None),
|
|
||||||
section_kind,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(matches)
|
Ok(matches)
|
||||||
@@ -605,7 +650,10 @@ where
|
|||||||
|
|
||||||
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
|
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)) {
|
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) {
|
} else if symbol.flags.contains(SymbolFlag::Common) {
|
||||||
Some((".comm", SectionKind::Common))
|
Some((".comm", SectionKind::Common))
|
||||||
} else {
|
} 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(
|
fn find_symbol(
|
||||||
obj: Option<&Object>,
|
obj: Option<&Object>,
|
||||||
in_obj: &Object,
|
in_obj: &Object,
|
||||||
in_symbol: &Symbol,
|
in_symbol_idx: usize,
|
||||||
used: Option<&BTreeSet<usize>>,
|
used: Option<&BTreeSet<usize>>,
|
||||||
|
fuzzy_literals: bool,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
|
let in_symbol = &in_obj.symbols[in_symbol_idx];
|
||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
|
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
|
// Try to find an exact name match
|
||||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||||
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
|
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
|
||||||
}) {
|
}) {
|
||||||
return Some(symbol_idx);
|
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 let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||||
if in_symbol.name.starts_with('@')
|
symbol_name_matches(&in_symbol.name, &symbol.name)
|
||||||
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
|
&& symbol_section_kind(obj, symbol) == section_kind
|
||||||
{
|
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
}) {
|
||||||
let Some(section_index) = symbol.section else {
|
return Some(symbol_idx);
|
||||||
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_section_kind(obj, symbol) == section_kind
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
return Some(symbol_idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,13 +170,6 @@ pub enum JobResult {
|
|||||||
CreateScratch(Option<Box<CreateScratchResult>>),
|
CreateScratch(Option<Box<CreateScratchResult>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_cancel(rx: &Receiver<()>) -> bool {
|
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_job(
|
fn start_job(
|
||||||
waker: Waker,
|
waker: Waker,
|
||||||
title: &str,
|
title: &str,
|
||||||
@@ -203,7 +196,6 @@ fn start_job(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
// log::info!("Started job {}", id); TODO
|
|
||||||
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,3 +220,10 @@ fn update_status(
|
|||||||
context.waker.wake_by_ref();
|
context.waker.wake_by_ref();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
109
objdiff-core/src/obj/dwarf2.rs
Normal file
109
objdiff-core/src/obj/dwarf2.rs
Normal 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:?}")
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(feature = "dwarf")]
|
||||||
|
mod dwarf2;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod split_meta;
|
pub mod split_meta;
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ fn map_symbols(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Infer symbol sizes for 0-size 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))
|
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
|
/// 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.
|
/// usually emitted as branch targets and do not represent the start of a function or object.
|
||||||
fn is_local_label(symbol: &Symbol) -> bool {
|
fn is_local_label(symbol: &Symbol) -> bool {
|
||||||
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
|
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_", "switchD_"];
|
||||||
symbol.size == 0
|
symbol.size == 0
|
||||||
&& symbol.flags.contains(SymbolFlag::Local)
|
&& symbol.flags.contains(SymbolFlag::Local)
|
||||||
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
|
&& 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
|
// Create a sorted list of symbol indices by section
|
||||||
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
|
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
|
||||||
for (i, symbol) in symbols.iter().enumerate() {
|
for (i, symbol) in symbols.iter().enumerate() {
|
||||||
@@ -206,18 +206,13 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
|||||||
iter_idx += 1;
|
iter_idx += 1;
|
||||||
};
|
};
|
||||||
let section = §ions[section_idx];
|
let section = §ions[section_idx];
|
||||||
let mut next_address =
|
let next_address =
|
||||||
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
|
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
|
||||||
if section.kind == SectionKind::Code {
|
let new_size = if section.kind == SectionKind::Code {
|
||||||
// For functions, trim any trailing 4-byte zeroes from the end (padding, nops)
|
arch.infer_function_size(symbol, section, next_address)?
|
||||||
while next_address > symbol.address + 4
|
} else {
|
||||||
&& let Some(data) = section.data_range(next_address - 4, 4)
|
next_address.saturating_sub(symbol.address)
|
||||||
&& data == [0u8; 4]
|
};
|
||||||
{
|
|
||||||
next_address -= 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let new_size = next_address.saturating_sub(symbol.address);
|
|
||||||
if new_size > 0 {
|
if new_size > 0 {
|
||||||
let symbol = &mut symbols[symbol_idx];
|
let symbol = &mut symbols[symbol_idx];
|
||||||
symbol.size = new_size;
|
symbol.size = new_size;
|
||||||
@@ -234,6 +229,7 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_sections(
|
fn map_sections(
|
||||||
@@ -554,12 +550,11 @@ fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optional full data flow analysis
|
// Optional full data flow analysis
|
||||||
if config.analyze_data_flow {
|
if config.analyze_data_flow
|
||||||
if let Some(flow_result) =
|
&& let Some(flow_result) =
|
||||||
obj.arch.data_flow_analysis(obj, symbol, code, §ion.relocations)
|
obj.arch.data_flow_analysis(obj, symbol, code, §ion.relocations)
|
||||||
{
|
{
|
||||||
generated_flow_results.push((symbol.clone(), flow_result));
|
generated_flow_results.push((symbol.clone(), flow_result));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,6 +577,28 @@ fn parse_line_info(
|
|||||||
obj_data: &[u8],
|
obj_data: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// DWARF 1.1
|
// 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") {
|
if let Some(section) = obj_file.section_by_name(".line") {
|
||||||
let data = section.uncompressed_data()?;
|
let data = section.uncompressed_data()?;
|
||||||
let mut reader: &[u8] = data.as_ref();
|
let mut reader: &[u8] = data.as_ref();
|
||||||
@@ -609,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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub fn align_data_to_4<W: std::io::Write + ?Sized>(
|
|||||||
len: usize,
|
len: usize,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||||
if len % 4 != 0 {
|
if !len.is_multiple_of(4) {
|
||||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -507,116 +507,4 @@ expression: diff.instruction_rows
|
|||||||
),
|
),
|
||||||
arg_diff: [],
|
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: [],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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(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(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(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)]
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Object {
|
|||||||
"int __cdecl test(int)",
|
"int __cdecl test(int)",
|
||||||
),
|
),
|
||||||
address: 0,
|
address: 0,
|
||||||
size: 96,
|
size: 88,
|
||||||
kind: Function,
|
kind: Function,
|
||||||
section: Some(
|
section: Some(
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ wsl = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
argp = "0.4"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ use globset::Glob;
|
|||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
build::watcher::{Watcher, create_watcher},
|
build::watcher::{Watcher, create_watcher},
|
||||||
config::{
|
config::{
|
||||||
DEFAULT_WATCH_PATTERNS, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
|
ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig, build_globset,
|
||||||
build_globset, default_watch_patterns, path::platform_path_serde_option,
|
default_ignore_patterns, default_watch_patterns, path::platform_path_serde_option,
|
||||||
save_project_config,
|
save_project_config,
|
||||||
},
|
},
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
@@ -219,6 +219,8 @@ pub struct AppConfig {
|
|||||||
#[serde(default = "default_watch_patterns")]
|
#[serde(default = "default_watch_patterns")]
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub ignore_patterns: Vec<Glob>,
|
||||||
|
#[serde(default)]
|
||||||
pub recent_projects: Vec<String>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
@@ -239,7 +241,8 @@ impl Default for AppConfig {
|
|||||||
build_target: false,
|
build_target: false,
|
||||||
rebuild_on_changes: true,
|
rebuild_on_changes: true,
|
||||||
auto_update_check: 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![],
|
recent_projects: vec![],
|
||||||
diff_obj_config: Default::default(),
|
diff_obj_config: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -431,6 +434,7 @@ impl App {
|
|||||||
app_path: Option<PathBuf>,
|
app_path: Option<PathBuf>,
|
||||||
graphics_config: GraphicsConfig,
|
graphics_config: GraphicsConfig,
|
||||||
graphics_config_path: Option<PathBuf>,
|
graphics_config_path: Option<PathBuf>,
|
||||||
|
project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Load previous app state (if any).
|
// Load previous app state (if any).
|
||||||
// Note that you must enable the `persistence` feature for this to work.
|
// Note that you must enable the `persistence` feature for this to work.
|
||||||
@@ -440,18 +444,26 @@ impl App {
|
|||||||
app.appearance = appearance;
|
app.appearance = appearance;
|
||||||
}
|
}
|
||||||
if let Some(config) = deserialize_config(storage) {
|
if let Some(config) = deserialize_config(storage) {
|
||||||
let mut state = AppState { config, ..Default::default() };
|
let state = AppState { config, ..Default::default() };
|
||||||
if state.config.project_dir.is_some() {
|
|
||||||
state.config_change = true;
|
|
||||||
state.watcher_change = true;
|
|
||||||
}
|
|
||||||
if state.config.selected_obj.is_some() {
|
|
||||||
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.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;
|
||||||
|
}
|
||||||
|
if state.config.selected_obj.is_some() {
|
||||||
|
state.queue_build = true;
|
||||||
|
}
|
||||||
|
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
|
||||||
|
}
|
||||||
app.appearance.init_fonts(&cc.egui_ctx);
|
app.appearance.init_fonts(&cc.egui_ctx);
|
||||||
app.appearance.utc_offset = utc_offset;
|
app.appearance.utc_offset = utc_offset;
|
||||||
app.app_path = app_path;
|
app.app_path = app_path;
|
||||||
@@ -526,14 +538,12 @@ impl App {
|
|||||||
mod_check = true;
|
mod_check = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if mod_check {
|
if mod_check
|
||||||
if let Some(info) = &state.project_config_info {
|
&& let Some(info) = &state.project_config_info
|
||||||
if let Some(last_ts) = info.timestamp {
|
&& let Some(last_ts) = info.timestamp
|
||||||
if file_modified(&info.path, last_ts) {
|
&& file_modified(&info.path, last_ts)
|
||||||
state.config_change = true;
|
{
|
||||||
}
|
state.config_change = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.config_change {
|
if state.config_change {
|
||||||
@@ -553,11 +563,17 @@ impl App {
|
|||||||
if let Some(project_dir) = &state.config.project_dir {
|
if let Some(project_dir) = &state.config.project_dir {
|
||||||
match build_globset(&state.config.watch_patterns)
|
match build_globset(&state.config.watch_patterns)
|
||||||
.map_err(anyhow::Error::new)
|
.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(
|
create_watcher(
|
||||||
self.modified.clone(),
|
self.modified.clone(),
|
||||||
project_dir.as_ref(),
|
project_dir.as_ref(),
|
||||||
globset,
|
patterns,
|
||||||
|
ignore_patterns,
|
||||||
egui_waker(ctx),
|
egui_waker(ctx),
|
||||||
)
|
)
|
||||||
.map_err(anyhow::Error::new)
|
.map_err(anyhow::Error::new)
|
||||||
@@ -581,22 +597,20 @@ impl App {
|
|||||||
state.queue_build = true;
|
state.queue_build = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = &diff_state.build {
|
if let Some(result) = &diff_state.build
|
||||||
if mod_check {
|
&& mod_check
|
||||||
if let Some((obj, _)) = &result.first_obj {
|
{
|
||||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
if let Some((obj, _)) = &result.first_obj
|
||||||
if file_modified(path, timestamp) {
|
&& let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp)
|
||||||
state.queue_reload = true;
|
&& file_modified(path, timestamp)
|
||||||
}
|
{
|
||||||
}
|
state.queue_reload = true;
|
||||||
}
|
}
|
||||||
if let Some((obj, _)) = &result.second_obj {
|
if let Some((obj, _)) = &result.second_obj
|
||||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
&& let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp)
|
||||||
if file_modified(path, timestamp) {
|
&& file_modified(path, timestamp)
|
||||||
state.queue_reload = true;
|
{
|
||||||
}
|
state.queue_reload = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,13 +632,12 @@ impl App {
|
|||||||
state.queue_reload = false;
|
state.queue_reload = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if graphics_state.should_relaunch {
|
if graphics_state.should_relaunch
|
||||||
if let Some(app_path) = &self.app_path {
|
&& let Some(app_path) = &self.app_path
|
||||||
if let Ok(mut guard) = self.relaunch_path.lock() {
|
&& let Ok(mut guard) = self.relaunch_path.lock()
|
||||||
*guard = Some(app_path.clone());
|
{
|
||||||
self.should_relaunch = true;
|
*guard = Some(app_path.clone());
|
||||||
}
|
self.should_relaunch = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,7 +678,10 @@ impl eframe::App for App {
|
|||||||
let side_panel_available = diff_state.current_view == View::SymbolDiff;
|
let side_panel_available = diff_state.current_view == View::SymbolDiff;
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
egui::MenuBar::new().ui(ui, |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
|
if ui
|
||||||
.add_enabled(
|
.add_enabled(
|
||||||
side_panel_available,
|
side_panel_available,
|
||||||
@@ -677,7 +693,8 @@ impl eframe::App for App {
|
|||||||
*show_side_panel = !*show_side_panel;
|
*show_side_panel = !*show_side_panel;
|
||||||
}
|
}
|
||||||
ui.separator();
|
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)]
|
#[cfg(debug_assertions)]
|
||||||
if ui.button("Debug…").clicked() {
|
if ui.button("Debug…").clicked() {
|
||||||
*show_debug = !*show_debug;
|
*show_debug = !*show_debug;
|
||||||
@@ -694,22 +711,29 @@ impl eframe::App for App {
|
|||||||
};
|
};
|
||||||
if recent_projects.is_empty() {
|
if recent_projects.is_empty() {
|
||||||
ui.add_enabled(false, egui::Button::new("Recent projects…"));
|
ui.add_enabled(false, egui::Button::new("Recent projects…"));
|
||||||
} else {
|
} else if let Some(menu_root) = bar_state.as_ref() {
|
||||||
ui.menu_button("Recent Projects…", |ui| {
|
egui::menu::submenu_button(
|
||||||
if ui.button("Clear").clicked() {
|
ui,
|
||||||
state.write().unwrap().config.recent_projects.clear();
|
menu_root.menu_state.clone(),
|
||||||
};
|
"Recent Projects…",
|
||||||
ui.separator();
|
|ui| {
|
||||||
for path in recent_projects {
|
if ui.button("Clear").clicked() {
|
||||||
if ui.button(&path).clicked() {
|
state.write().unwrap().config.recent_projects.clear();
|
||||||
state
|
};
|
||||||
.write()
|
ui.separator();
|
||||||
.unwrap()
|
for path in recent_projects {
|
||||||
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
if ui.button(&path).clicked() {
|
||||||
ui.close();
|
state
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
||||||
|
ui.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
} else {
|
||||||
|
ui.add_enabled(false, egui::Button::new("Recent projects…"));
|
||||||
}
|
}
|
||||||
if ui.button("Appearance…").clicked() {
|
if ui.button("Appearance…").clicked() {
|
||||||
*show_appearance_config = !*show_appearance_config;
|
*show_appearance_config = !*show_appearance_config;
|
||||||
@@ -723,7 +747,7 @@ impl eframe::App for App {
|
|||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Tools", |ui| {
|
egui::menu::menu_button(ui, "Tools", |ui| {
|
||||||
if ui.button("Demangle…").clicked() {
|
if ui.button("Demangle…").clicked() {
|
||||||
*show_demangle = !*show_demangle;
|
*show_demangle = !*show_demangle;
|
||||||
ui.close();
|
ui.close();
|
||||||
@@ -733,7 +757,7 @@ impl eframe::App for App {
|
|||||||
ui.close();
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Diff Options", |ui| {
|
egui::menu::menu_button(ui, "Diff Options", |ui| {
|
||||||
if ui.button("Arch Settings…").clicked() {
|
if ui.button("Arch Settings…").clicked() {
|
||||||
*show_arch_config = !*show_arch_config;
|
*show_arch_config = !*show_arch_config;
|
||||||
ui.close();
|
ui.close();
|
||||||
|
|||||||
63
objdiff-gui/src/argp_version.rs
Normal file
63
objdiff-gui/src/argp_version.rs
Normal 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 process’s `env::args`.
|
||||||
|
///
|
||||||
|
/// This function will exit early from the current process if argument parsing was unsuccessful or if information like `--help` was requested.
|
||||||
|
/// Error messages will be printed to stderr, and `--help` output to stdout.
|
||||||
|
pub fn from_env<T>() -> T
|
||||||
|
where T: TopLevelCommand {
|
||||||
|
argp::parse_args_or_exit::<ArgsOrVersion<T>>(argp::DEFAULT).0
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use globset::Glob;
|
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 typed_path::{Utf8UnixComponent, Utf8UnixPath};
|
||||||
|
|
||||||
use crate::app::{AppState, ObjectConfig};
|
use crate::app::{AppState, ObjectConfig};
|
||||||
@@ -96,8 +96,15 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
.map(|s| Glob::new(s))
|
.map(|s| Glob::new(s))
|
||||||
.collect::<Result<Vec<Glob>, globset::Error>>()?;
|
.collect::<Result<Vec<Glob>, globset::Error>>()?;
|
||||||
} else {
|
} else {
|
||||||
state.config.watch_patterns =
|
state.config.watch_patterns = default_watch_patterns();
|
||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
}
|
||||||
|
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.watcher_change = true;
|
||||||
state.objects = project_config
|
state.objects = project_config
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod app_config;
|
mod app_config;
|
||||||
|
mod argp_version;
|
||||||
mod config;
|
mod config;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod hotkeys;
|
mod hotkeys;
|
||||||
@@ -11,19 +12,83 @@ mod update;
|
|||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
fmt::Display,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::ExitCode,
|
process::ExitCode,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
|
str::FromStr,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Result, ensure};
|
use anyhow::{Result, ensure};
|
||||||
|
use argp::{FromArgValue, FromArgs};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
|
use objdiff_core::config::path::check_path_buf;
|
||||||
use time::UtcOffset;
|
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};
|
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> {
|
fn load_icon() -> Result<egui::IconData> {
|
||||||
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
|
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
|
||||||
let mut reader = decoder.read_info()?;
|
let mut reader = decoder.read_info()?;
|
||||||
@@ -38,23 +103,63 @@ fn load_icon() -> Result<egui::IconData> {
|
|||||||
const APP_NAME: &str = "objdiff";
|
const APP_NAME: &str = "objdiff";
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
let args: TopLevel = argp_version::from_env();
|
||||||
tracing_subscriber::fmt()
|
let builder = tracing_subscriber::fmt();
|
||||||
.with_env_filter(
|
if let Some(level) = args.log_level {
|
||||||
EnvFilter::builder()
|
builder
|
||||||
// Default to info level
|
.with_max_level(match level {
|
||||||
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
|
LogLevel::Error => LevelFilter::ERROR,
|
||||||
.from_env_lossy()
|
LogLevel::Warn => LevelFilter::WARN,
|
||||||
// This module is noisy at info level
|
LogLevel::Info => LevelFilter::INFO,
|
||||||
.add_directive("wgpu_core::device::resource=warn".parse().unwrap()),
|
LogLevel::Debug => LevelFilter::DEBUG,
|
||||||
)
|
LogLevel::Trace => LevelFilter::TRACE,
|
||||||
.init();
|
})
|
||||||
|
.init();
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
.with_env_filter(
|
||||||
|
EnvFilter::builder()
|
||||||
|
// Default to info level
|
||||||
|
.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,
|
// Because localtime_r is unsound in multithreaded apps,
|
||||||
// we must call this before initializing eframe.
|
// we must call this before initializing eframe.
|
||||||
// https://github.com/time-rs/time/issues/293
|
// https://github.com/time-rs/time/issues/293
|
||||||
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
|
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 app_path = std::env::current_exe().ok();
|
||||||
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||||
let mut native_options = eframe::NativeOptions {
|
let mut native_options = eframe::NativeOptions {
|
||||||
@@ -113,6 +218,7 @@ fn main() -> ExitCode {
|
|||||||
app_path.clone(),
|
app_path.clone(),
|
||||||
graphics_config.clone(),
|
graphics_config.clone(),
|
||||||
graphics_config_path.clone(),
|
graphics_config_path.clone(),
|
||||||
|
project_dir.clone(),
|
||||||
) {
|
) {
|
||||||
eframe_error = Some(e);
|
eframe_error = Some(e);
|
||||||
}
|
}
|
||||||
@@ -139,6 +245,7 @@ fn main() -> ExitCode {
|
|||||||
app_path.clone(),
|
app_path.clone(),
|
||||||
graphics_config.clone(),
|
graphics_config.clone(),
|
||||||
graphics_config_path.clone(),
|
graphics_config_path.clone(),
|
||||||
|
project_dir.clone(),
|
||||||
) {
|
) {
|
||||||
eframe_error = Some(e);
|
eframe_error = Some(e);
|
||||||
} else {
|
} else {
|
||||||
@@ -161,6 +268,7 @@ fn main() -> ExitCode {
|
|||||||
app_path,
|
app_path,
|
||||||
graphics_config,
|
graphics_config,
|
||||||
graphics_config_path,
|
graphics_config_path,
|
||||||
|
project_dir,
|
||||||
) {
|
) {
|
||||||
eframe_error = Some(e);
|
eframe_error = Some(e);
|
||||||
} else {
|
} else {
|
||||||
@@ -173,23 +281,23 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to relaunch application from the updated path
|
// Attempt to relaunch application from the updated path
|
||||||
if let Ok(mut guard) = exec_path.lock() {
|
if let Ok(mut guard) = exec_path.lock()
|
||||||
if let Some(path) = guard.take() {
|
&& let Some(path) = guard.take()
|
||||||
cfg_if! {
|
{
|
||||||
if #[cfg(unix)] {
|
cfg_if! {
|
||||||
let e = exec::Command::new(path)
|
if #[cfg(unix)] {
|
||||||
.args(&std::env::args().collect::<Vec<String>>())
|
let e = exec::Command::new(path)
|
||||||
.exec();
|
.args(&std::env::args().collect::<Vec<String>>())
|
||||||
|
.exec();
|
||||||
|
log::error!("Failed to relaunch: {e:?}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
} else {
|
||||||
|
let result = std::process::Command::new(path)
|
||||||
|
.args(std::env::args())
|
||||||
|
.spawn();
|
||||||
|
if let Err(e) = result {
|
||||||
log::error!("Failed to relaunch: {e:?}");
|
log::error!("Failed to relaunch: {e:?}");
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
} else {
|
|
||||||
let result = std::process::Command::new(path)
|
|
||||||
.args(std::env::args())
|
|
||||||
.spawn();
|
|
||||||
if let Err(e) = result {
|
|
||||||
log::error!("Failed to relaunch: {e:?}");
|
|
||||||
return ExitCode::FAILURE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +312,7 @@ fn run_eframe(
|
|||||||
app_path: Option<PathBuf>,
|
app_path: Option<PathBuf>,
|
||||||
graphics_config: GraphicsConfig,
|
graphics_config: GraphicsConfig,
|
||||||
graphics_config_path: Option<PathBuf>,
|
graphics_config_path: Option<PathBuf>,
|
||||||
|
project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
) -> Result<(), eframe::Error> {
|
) -> Result<(), eframe::Error> {
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
@@ -216,6 +325,7 @@ fn run_eframe(
|
|||||||
app_path,
|
app_path,
|
||||||
graphics_config,
|
graphics_config,
|
||||||
graphics_config_path,
|
graphics_config_path,
|
||||||
|
project_dir,
|
||||||
)))
|
)))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub struct Appearance {
|
|||||||
pub ui_font: FontId,
|
pub ui_font: FontId,
|
||||||
pub code_font: FontId,
|
pub code_font: FontId,
|
||||||
pub diff_colors: Vec<Color32>,
|
pub diff_colors: Vec<Color32>,
|
||||||
|
pub diff_bg_color: Option<Color32>,
|
||||||
pub theme: egui::Theme,
|
pub theme: egui::Theme,
|
||||||
|
|
||||||
// Applied by theme
|
// Applied by theme
|
||||||
@@ -67,6 +68,7 @@ impl Default for Appearance {
|
|||||||
replace_color: Color32::LIGHT_BLUE,
|
replace_color: Color32::LIGHT_BLUE,
|
||||||
insert_color: Color32::GREEN,
|
insert_color: Color32::GREEN,
|
||||||
delete_color: Color32::from_rgb(200, 40, 41),
|
delete_color: Color32::from_rgb(200, 40, 41),
|
||||||
|
diff_bg_color: None,
|
||||||
utc_offset: UtcOffset::UTC,
|
utc_offset: UtcOffset::UTC,
|
||||||
fonts: FontState::default(),
|
fonts: FontState::default(),
|
||||||
next_ui_font: None,
|
next_ui_font: None,
|
||||||
@@ -103,6 +105,9 @@ impl Appearance {
|
|||||||
match self.theme {
|
match self.theme {
|
||||||
egui::Theme::Dark => {
|
egui::Theme::Dark => {
|
||||||
style.visuals = egui::Visuals::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.text_color = Color32::GRAY;
|
||||||
self.emphasized_text_color = Color32::LIGHT_GRAY;
|
self.emphasized_text_color = Color32::LIGHT_GRAY;
|
||||||
self.deemphasized_text_color = Color32::DARK_GRAY;
|
self.deemphasized_text_color = Color32::DARK_GRAY;
|
||||||
@@ -114,6 +119,9 @@ impl Appearance {
|
|||||||
}
|
}
|
||||||
egui::Theme::Light => {
|
egui::Theme::Light => {
|
||||||
style.visuals = egui::Visuals::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.text_color = Color32::GRAY;
|
||||||
self.emphasized_text_color = Color32::DARK_GRAY;
|
self.emphasized_text_color = Color32::DARK_GRAY;
|
||||||
self.deemphasized_text_color = Color32::LIGHT_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,
|
appearance,
|
||||||
);
|
);
|
||||||
ui.separator();
|
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:");
|
ui.label("Diff colors:");
|
||||||
if ui.button("Reset").clicked() {
|
if ui.button("Reset").clicked() {
|
||||||
appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use egui::{
|
|||||||
};
|
};
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{DEFAULT_WATCH_PATTERNS, path::check_path_buf},
|
config::{default_ignore_patterns, default_watch_patterns, path::check_path_buf},
|
||||||
diff::{
|
diff::{
|
||||||
CONFIG_GROUPS, ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
CONFIG_GROUPS, ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||||
ConfigPropertyValue,
|
ConfigPropertyValue,
|
||||||
@@ -41,6 +41,7 @@ pub struct ConfigViewState {
|
|||||||
pub build_running: bool,
|
pub build_running: bool,
|
||||||
pub queue_build: bool,
|
pub queue_build: bool,
|
||||||
pub watch_pattern_text: String,
|
pub watch_pattern_text: String,
|
||||||
|
pub ignore_pattern_text: String,
|
||||||
pub object_search: String,
|
pub object_search: String,
|
||||||
pub filter_diffable: bool,
|
pub filter_diffable: bool,
|
||||||
pub filter_incomplete: bool,
|
pub filter_incomplete: bool,
|
||||||
@@ -185,16 +186,15 @@ pub fn config_ui(
|
|||||||
if result.update_available {
|
if result.update_available {
|
||||||
ui.colored_label(appearance.insert_color, "Update available");
|
ui.colored_label(appearance.insert_color, "Update available");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if let Some(bin_name) = &result.found_binary {
|
if let Some(bin_name) = &result.found_binary
|
||||||
if ui
|
&& ui
|
||||||
.add_enabled(!config_state.update_running, egui::Button::new("Automatic"))
|
.add_enabled(!config_state.update_running, egui::Button::new("Automatic"))
|
||||||
.on_hover_text_at_pointer(
|
.on_hover_text_at_pointer(
|
||||||
"Automatically download and replace the current build",
|
"Automatically download and replace the current build",
|
||||||
)
|
)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
config_state.queue_update = Some(bin_name.clone());
|
config_state.queue_update = Some(bin_name.clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.button("Manual")
|
.button("Manual")
|
||||||
@@ -329,12 +329,12 @@ pub fn config_ui(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if new_selected_index != selected_index {
|
if new_selected_index != selected_index
|
||||||
if let Some(idx) = new_selected_index {
|
&& let Some(idx) = new_selected_index
|
||||||
// Will set obj_changed, which will trigger a rebuild
|
{
|
||||||
let config = objects[idx].clone();
|
// Will set obj_changed, which will trigger a rebuild
|
||||||
state_guard.set_selected_obj(config);
|
let config = objects[idx].clone();
|
||||||
}
|
state_guard.set_selected_obj(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,18 +374,17 @@ fn display_unit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
||||||
if let Some(source_path) = &object.source_path {
|
if let Some(source_path) = &object.source_path
|
||||||
if ui
|
&& ui
|
||||||
.button("Open source file")
|
.button("Open source file")
|
||||||
.on_hover_text("Open the source file in the default editor")
|
.on_hover_text("Open the source file in the default editor")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
log::info!("Opening file {source_path}");
|
log::info!("Opening file {source_path}");
|
||||||
if let Err(e) = open::that_detached(source_path.as_str()) {
|
if let Err(e) = open::that_detached(source_path.as_str()) {
|
||||||
log::error!("Failed to open source file: {e}");
|
log::error!("Failed to open source file: {e}");
|
||||||
}
|
|
||||||
ui.close();
|
|
||||||
}
|
}
|
||||||
|
ui.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,20 +791,49 @@ fn split_obj_config_ui(
|
|||||||
state.watcher_change = true;
|
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.horizontal(|ui| {
|
||||||
ui.label(RichText::new("File patterns").color(appearance.text_color));
|
ui.label(RichText::new(text).color(appearance.text_color));
|
||||||
if ui
|
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)
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
state.config.watch_patterns =
|
*patterns = on_reset();
|
||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
change = true;
|
||||||
state.watcher_change = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut remove_at: Option<usize> = None;
|
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.horizontal(|ui| {
|
||||||
ui.label(
|
ui.label(
|
||||||
RichText::new(glob.to_string())
|
RichText::new(glob.to_string())
|
||||||
@@ -813,7 +841,7 @@ fn split_obj_config_ui(
|
|||||||
.family(FontFamily::Monospace),
|
.family(FontFamily::Monospace),
|
||||||
);
|
);
|
||||||
if ui
|
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)
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
@@ -822,27 +850,27 @@ fn split_obj_config_ui(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Some(idx) = remove_at {
|
if let Some(idx) = remove_at {
|
||||||
state.config.watch_patterns.remove(idx);
|
patterns.remove(idx);
|
||||||
state.watcher_change = true;
|
change = true;
|
||||||
}
|
}
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_enabled(
|
ui.add_enabled(
|
||||||
state.project_config_info.is_none(),
|
!has_project_config,
|
||||||
egui::TextEdit::singleline(&mut config_state.watch_pattern_text).desired_width(100.0),
|
egui::TextEdit::singleline(pattern_text).desired_width(100.0),
|
||||||
)
|
)
|
||||||
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
|
||||||
if ui
|
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)
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
|
||||||
.clicked()
|
.clicked()
|
||||||
|
&& let Ok(glob) = Glob::new(pattern_text)
|
||||||
{
|
{
|
||||||
if let Ok(glob) = Glob::new(&config_state.watch_pattern_text) {
|
patterns.push(glob);
|
||||||
state.config.watch_patterns.push(glob);
|
change = true;
|
||||||
state.watcher_change = true;
|
pattern_text.clear();
|
||||||
config_state.watch_pattern_text.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
change
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arch_config_window(
|
pub fn arch_config_window(
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ pub(crate) fn data_row_ui(
|
|||||||
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
|
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
|
||||||
cur_addr += 1;
|
cur_addr += 1;
|
||||||
cur_addr_actual += 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());
|
write_text(" ", base_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ pub fn diff_view_ui(
|
|||||||
let mut navigation = current_navigation.clone();
|
let mut navigation = current_navigation.clone();
|
||||||
if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol {
|
if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol {
|
||||||
// If a matching symbol appears, select it
|
// If a matching symbol appears, select it
|
||||||
if !right_ctx.has_symbol() {
|
if !right_ctx.has_symbol()
|
||||||
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
&& let Some(target_symbol_ref) = symbol_diff.target_symbol
|
||||||
navigation.right_symbol = Some(target_symbol_ref);
|
{
|
||||||
}
|
navigation.right_symbol = Some(target_symbol_ref);
|
||||||
}
|
}
|
||||||
} else if navigation.left_symbol.is_some()
|
} else if navigation.left_symbol.is_some()
|
||||||
&& left_ctx.obj.is_some()
|
&& left_ctx.obj.is_some()
|
||||||
@@ -142,10 +142,10 @@ pub fn diff_view_ui(
|
|||||||
}
|
}
|
||||||
if let Some((_symbol, symbol_diff, _symbol_idx)) = right_ctx.symbol {
|
if let Some((_symbol, symbol_diff, _symbol_idx)) = right_ctx.symbol {
|
||||||
// If a matching symbol appears, select it
|
// If a matching symbol appears, select it
|
||||||
if !left_ctx.has_symbol() {
|
if !left_ctx.has_symbol()
|
||||||
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
&& let Some(target_symbol_ref) = symbol_diff.target_symbol
|
||||||
navigation.left_symbol = Some(target_symbol_ref);
|
{
|
||||||
}
|
navigation.left_symbol = Some(target_symbol_ref);
|
||||||
}
|
}
|
||||||
} else if navigation.right_symbol.is_some()
|
} else if navigation.right_symbol.is_some()
|
||||||
&& right_ctx.obj.is_some()
|
&& right_ctx.obj.is_some()
|
||||||
@@ -247,16 +247,15 @@ pub fn diff_view_ui(
|
|||||||
|
|
||||||
// Third row
|
// Third row
|
||||||
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
||||||
if state.current_view == View::FunctionDiff
|
if (state.current_view == View::FunctionDiff
|
||||||
&& ui
|
&& ui
|
||||||
.button("Change target")
|
.button("Change target")
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
.clicked()
|
.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()));
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -409,17 +408,16 @@ pub fn diff_view_ui(
|
|||||||
if needs_separator {
|
if needs_separator {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
if ui
|
if (ui
|
||||||
.button("Change base")
|
.button("Change base")
|
||||||
.on_hover_text_at_pointer(
|
.on_hover_text_at_pointer(
|
||||||
"Choose a different symbol to use as the base",
|
"Choose a different symbol to use as the base",
|
||||||
)
|
)
|
||||||
.clicked()
|
.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()));
|
||||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if right_ctx.status.success && !right_ctx.has_symbol() {
|
} else if right_ctx.status.success && !right_ctx.has_symbol() {
|
||||||
@@ -583,8 +581,8 @@ pub fn diff_view_ui(
|
|||||||
) {
|
) {
|
||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
} else if column == 1 {
|
} else if column == 1
|
||||||
if let Some(action) = diff_col_ui(
|
&& let Some(action) = diff_col_ui(
|
||||||
ui,
|
ui,
|
||||||
state,
|
state,
|
||||||
appearance,
|
appearance,
|
||||||
@@ -594,9 +592,9 @@ pub fn diff_view_ui(
|
|||||||
available_width,
|
available_width,
|
||||||
open_sections.1,
|
open_sections.1,
|
||||||
diff_config,
|
diff_config,
|
||||||
) {
|
)
|
||||||
ret = Some(action);
|
{
|
||||||
}
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -621,9 +619,7 @@ fn symbol_label_ui(
|
|||||||
.font(appearance.code_font.clone())
|
.font(appearance.code_font.clone())
|
||||||
.color(appearance.highlight_color),
|
.color(appearance.highlight_color),
|
||||||
)
|
)
|
||||||
.selectable(false)
|
.show_tooltip_when_elided(false)
|
||||||
// TODO .show_tooltip_when_elided(false)
|
|
||||||
// https://github.com/emilk/egui/commit/071e090e2b2601e5ed4726a63a753188503dfaf2
|
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance))
|
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance))
|
||||||
.context_menu(|ui| {
|
.context_menu(|ui| {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
use core::any::Any;
|
||||||
|
|
||||||
use egui::ScrollArea;
|
use egui::ScrollArea;
|
||||||
use objdiff_core::{
|
use objdiff_core::{arch::ppc::ExceptionInfo, obj::Object};
|
||||||
arch::ppc::ExceptionInfo,
|
|
||||||
obj::{Object, Symbol},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
|
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
|
||||||
|
|
||||||
@@ -26,19 +25,19 @@ fn decode_extab(extab: &ExceptionInfo) -> String {
|
|||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_extab_entry<'a>(_obj: &'a Object, _symbol: &Symbol) -> Option<&'a ExceptionInfo> {
|
fn find_extab_entry(obj: &Object, symbol_index: usize) -> Option<&ExceptionInfo> {
|
||||||
// TODO
|
(obj.arch.as_ref() as &dyn Any)
|
||||||
// obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol))
|
.downcast_ref::<objdiff_core::arch::ppc::ArchPpc>()
|
||||||
None
|
.and_then(|ppc| ppc.extab_for_symbol(symbol_index))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extab_text_ui(
|
fn extab_text_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
symbol: &Symbol,
|
symbol_index: usize,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Option<()> {
|
) -> 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);
|
let text = decode_extab(extab_entry);
|
||||||
ui.colored_label(appearance.replace_color, &text);
|
ui.colored_label(appearance.replace_color, &text);
|
||||||
return Some(());
|
return Some(());
|
||||||
@@ -58,10 +57,8 @@ pub(crate) fn extab_ui(
|
|||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
if let Some(symbol) =
|
if let Some(symbol_index) = ctx.symbol_ref {
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.obj.symbols.get(symbol_ref))
|
extab_text_ui(ui, ctx, symbol_index, appearance);
|
||||||
{
|
|
||||||
extab_text_ui(ui, ctx, symbol, appearance);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ impl DiffViewState {
|
|||||||
JobResult::ObjDiff(result) => {
|
JobResult::ObjDiff(result) => {
|
||||||
self.build = take(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?
|
// TODO: where should this go?
|
||||||
if let Some(result) = self.post_build_nav.take() {
|
if let Some(result) = self.post_build_nav.take() {
|
||||||
self.current_view = result.view;
|
self.current_view = result.view;
|
||||||
@@ -211,19 +216,19 @@ impl DiffViewState {
|
|||||||
|
|
||||||
let mut resolved_left = self.resolve_symbol(nav.left_symbol, 0);
|
let mut resolved_left = self.resolve_symbol(nav.left_symbol, 0);
|
||||||
let mut resolved_right = self.resolve_symbol(nav.right_symbol, 1);
|
let mut resolved_right = self.resolve_symbol(nav.right_symbol, 1);
|
||||||
if let Some(resolved_right) = &resolved_right {
|
if let Some(resolved_right) = &resolved_right
|
||||||
if resolved_left.is_none() {
|
&& resolved_left.is_none()
|
||||||
resolved_left = resolved_right
|
{
|
||||||
.target_symbol
|
resolved_left = resolved_right
|
||||||
.and_then(|idx| self.resolve_symbol(Some(idx), 0));
|
.target_symbol
|
||||||
}
|
.and_then(|idx| self.resolve_symbol(Some(idx), 0));
|
||||||
}
|
}
|
||||||
if let Some(resolved_left) = &resolved_left {
|
if let Some(resolved_left) = &resolved_left
|
||||||
if resolved_right.is_none() {
|
&& resolved_right.is_none()
|
||||||
resolved_right = resolved_left
|
{
|
||||||
.target_symbol
|
resolved_right = resolved_left
|
||||||
.and_then(|idx| self.resolve_symbol(Some(idx), 1));
|
.target_symbol
|
||||||
}
|
.and_then(|idx| self.resolve_symbol(Some(idx), 1));
|
||||||
}
|
}
|
||||||
let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right);
|
let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right);
|
||||||
if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some())
|
if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some())
|
||||||
@@ -500,16 +505,16 @@ pub fn symbol_context_menu_ui(
|
|||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(section) = section {
|
if let Some(section) = section
|
||||||
if ui.button("Map symbol").clicked() {
|
&& ui.button("Map symbol").clicked()
|
||||||
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
|
{
|
||||||
if column == 0 {
|
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
|
||||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref));
|
if column == 0 {
|
||||||
} else {
|
ret = Some(DiffViewAction::SelectingRight(symbol_ref));
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref));
|
} else {
|
||||||
}
|
ret = Some(DiffViewAction::SelectingLeft(symbol_ref));
|
||||||
ui.close();
|
|
||||||
}
|
}
|
||||||
|
ui.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ret
|
ret
|
||||||
@@ -664,10 +669,10 @@ pub fn symbol_list_ui(
|
|||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
let mut show_mapped_symbols = state.show_mapped_symbols;
|
let mut show_mapped_symbols = state.show_mapped_symbols;
|
||||||
if let SymbolFilter::Mapping(_, _) = filter {
|
if let SymbolFilter::Mapping(_, _) = filter
|
||||||
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
|
&& ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed()
|
||||||
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
|
{
|
||||||
}
|
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
|
||||||
}
|
}
|
||||||
let section_display = display_sections(
|
let section_display = display_sections(
|
||||||
ctx.obj,
|
ctx.obj,
|
||||||
|
|||||||
6
objdiff-wasm/.cargo/config.toml
Normal file
6
objdiff-wasm/.cargo/config.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-wasip2"
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["panic_abort", "core", "alloc"]
|
||||||
|
build-std-features = ["compiler-builtins-mem"]
|
||||||
@@ -17,12 +17,13 @@ build = "build.rs"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = []
|
||||||
std = ["objdiff-core/std"]
|
std = ["objdiff-core/std"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
regex = { version = "1.11", default-features = false, features = ["unicode-case"] }
|
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"] }
|
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
|
||||||
|
|
||||||
[dependencies.objdiff-core]
|
[dependencies.objdiff-core]
|
||||||
@@ -33,8 +34,5 @@ features = ["arm", "arm64", "mips", "ppc", "superh", "x86", "dwarf"]
|
|||||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||||
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
|
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "wasi")'.dependencies]
|
|
||||||
wit-bindgen = { version = "0.43", default-features = false, features = ["macros"] }
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
wit-deps = "0.5"
|
wit-deps = "0.5"
|
||||||
|
|||||||
4
objdiff-wasm/package-lock.json
generated
4
objdiff-wasm/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.0.0-beta.12",
|
"version": "3.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.0.0-beta.12",
|
"version": "3.0.1",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.3",
|
"@biomejs/biome": "^1.9.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "3.0.0-beta.12",
|
"version": "3.0.1",
|
||||||
"description": "A local diffing tool for decompilation projects.",
|
"description": "A local diffing tool for decompilation projects.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Luke Street",
|
"name": "Luke Street",
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
"types": "dist/objdiff.d.ts",
|
"types": "dist/objdiff.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
|
"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:wasm": "cargo build --profile release-min --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: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"
|
"build:lib": "rslib build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
4
objdiff-wasm/rust-toolchain.toml
Normal file
4
objdiff-wasm/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
components = ["rust-src"]
|
||||||
|
targets = ["wasm32-wasip2"]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(clippy::derivable_impls)]
|
||||||
use alloc::{
|
use alloc::{
|
||||||
format,
|
format,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
@@ -223,7 +224,7 @@ impl GuestDisplay for Component {
|
|||||||
let symbol_display = from_symbol_ref(symbol_ref);
|
let symbol_display = from_symbol_ref(symbol_ref);
|
||||||
diff::display::symbol_context(obj, symbol_display.symbol as usize)
|
diff::display::symbol_context(obj, symbol_display.symbol as usize)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| ContextItem::from(item))
|
.map(ContextItem::from)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +236,7 @@ impl GuestDisplay for Component {
|
|||||||
let symbol_display = from_symbol_ref(symbol_ref);
|
let symbol_display = from_symbol_ref(symbol_ref);
|
||||||
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
|
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| HoverItem::from(item))
|
.map(HoverItem::from)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +283,7 @@ impl GuestDisplay for Component {
|
|||||||
};
|
};
|
||||||
diff::display::instruction_context(obj, resolved, &ins)
|
diff::display::instruction_context(obj, resolved, &ins)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| ContextItem::from(item))
|
.map(ContextItem::from)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +332,7 @@ impl GuestDisplay for Component {
|
|||||||
};
|
};
|
||||||
diff::display::instruction_hover(obj, resolved, &ins)
|
diff::display::instruction_hover(obj, resolved, &ins)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| HoverItem::from(item))
|
.map(HoverItem::from)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,9 +528,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
|
|||||||
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
|
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
|
||||||
let obj = self.0.as_ref();
|
let obj = self.0.as_ref();
|
||||||
let symbol_display = from_symbol_ref(symbol_ref);
|
let symbol_display = from_symbol_ref(symbol_ref);
|
||||||
let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
|
let symbol = obj.symbols.get(symbol_display.symbol)?;
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(SymbolInfo {
|
Some(SymbolInfo {
|
||||||
id: to_symbol_ref(symbol_display),
|
id: to_symbol_ref(symbol_display),
|
||||||
name: symbol.name.clone(),
|
name: symbol.name.clone(),
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(target_os = "wasi")]
|
|
||||||
mod api;
|
mod api;
|
||||||
#[cfg(target_os = "wasi")]
|
|
||||||
mod logging;
|
mod logging;
|
||||||
|
|
||||||
#[cfg(all(target_os = "wasi", not(feature = "std")))]
|
#[cfg(all(target_os = "wasi", not(feature = "std")))]
|
||||||
|
|||||||
Reference in New Issue
Block a user