mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-20 02:15:24 +00:00
Compare commits
37 Commits
v2.6.0
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
| fa4a6cadbb | |||
| 799971d54e | |||
| 8eef37e8df | |||
| 5f36916087 | |||
| ee667a2dde | |||
|
|
cf5fc54cfa | ||
| 1cdfa1e857 | |||
| 3f157f33a5 | |||
|
|
a1ea2919f8 | ||
| 9c31c82a37 | |||
| 4f34dfa194 | |||
| ae6d37a10b | |||
| 06e54f3456 | |||
| 6ed07bfaf1 | |||
| 80e939653a | |||
| 48305e0380 | |||
| 2eafbb218b | |||
| a1f2a535e5 | |||
| 8461b35cd7 | |||
| 95868f1d19 | |||
| 506c251d68 | |||
| f3c157ff06 | |||
|
|
6d3c63ccd8 | ||
| bbd8d9714f | |||
| 3c66ac3d54 | |||
| 561a9107e2 | |||
| e8de35b78e | |||
| d938988d43 | |||
|
|
3e6efb7736 | ||
|
|
674c942d7d | ||
|
|
6b7dcabbed | ||
| e202c3ef95 | |||
|
|
b7730b3d00 | ||
|
|
a4fdb61f04 | ||
|
|
2876be37a3 | ||
| 11171763eb | |||
| 6037a79ba2 |
55
.github/workflows/build.yaml
vendored
55
.github/workflows/build.yaml
vendored
@@ -10,7 +10,6 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
BUILD_PROFILE: release-lto
|
BUILD_PROFILE: release-lto
|
||||||
CARGO_TARGET_DIR: target
|
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -33,9 +32,9 @@ jobs:
|
|||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-features --all-targets
|
run: cargo check --features all
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
run: cargo clippy --all-features --all-targets
|
run: cargo clippy --features all
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -65,13 +64,12 @@ jobs:
|
|||||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
with:
|
with:
|
||||||
command: check ${{ matrix.checks }}
|
command: check ${{ matrix.checks }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
if: 'false' # No tests yet
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
@@ -90,7 +88,7 @@ jobs:
|
|||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --all-features
|
run: cargo test --release --features all
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -150,7 +148,7 @@ jobs:
|
|||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -168,8 +166,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
build-gui:
|
build-gui:
|
||||||
@@ -180,21 +178,26 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- platform: ubuntu-latest
|
- platform: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu.2.31
|
||||||
|
target_base: x86_64-unknown-linux-gnu
|
||||||
name: linux-x86_64
|
name: linux-x86_64
|
||||||
packages: libgtk-3-dev
|
packages: libgtk-3-dev
|
||||||
|
build: zigbuild
|
||||||
features: default
|
features: default
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
name: windows-x86_64
|
name: windows-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
name: macos-x86_64
|
name: macos-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
name: macos-arm64
|
name: macos-arm64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
@@ -206,27 +209,51 @@ jobs:
|
|||||||
sudo apt-get -y install ${{ matrix.packages }}
|
sudo apt-get -y install ${{ matrix.packages }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: Install cargo-zigbuild
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
run: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target_base || matrix.target }}
|
||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.target }}
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build-wasm:
|
||||||
|
name: Build objdiff-wasm
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rust-src
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm -C objdiff-wasm install
|
||||||
|
- name: Build
|
||||||
|
run: npm -C objdiff-wasm run build
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
|||||||
2050
Cargo.lock
generated
2050
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,9 @@ members = [
|
|||||||
"objdiff-cli",
|
"objdiff-cli",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "3"
|
||||||
|
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
@@ -13,9 +14,9 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.6.0"
|
version = "3.0.0-beta.1"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
rust-version = "1.74"
|
rust-version = "1.85"
|
||||||
|
|||||||
11
deny.toml
11
deny.toml
@@ -73,7 +73,6 @@ ignore = [
|
|||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained 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.
|
||||||
@@ -102,8 +101,7 @@ allow = [
|
|||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
"LicenseRef-UFL-1.0",
|
"Ubuntu-font-1.0",
|
||||||
"OpenSSL",
|
|
||||||
]
|
]
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
@@ -240,7 +238,12 @@ 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 = ["notify-rs"]
|
github = [
|
||||||
|
"CelestialAmber", # cwextab, rlwinmdec
|
||||||
|
"Decompollaborate", # rabbitizer
|
||||||
|
"enarx", # flagset
|
||||||
|
"encounter",
|
||||||
|
]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
# bitbucket.org organizations to allow git sources for
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ supports-color = "3.0"
|
|||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
typed-path = "0.10"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! For now, this only adds a --version/-V option which causes early-exit.
|
//! For now, this only adds a --version/-V option which causes early-exit.
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand};
|
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
|
||||||
|
|
||||||
struct ArgsOrVersion<T>(T)
|
struct ArgsOrVersion<T>(T)
|
||||||
where T: FromArgs;
|
where T: FromArgs;
|
||||||
|
|||||||
@@ -1,73 +1,74 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
io::stdout,
|
io::stdout,
|
||||||
mem,
|
mem,
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
},
|
},
|
||||||
task::{Wake, Waker},
|
task::{Wake, Waker},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event,
|
event,
|
||||||
event::{DisableMouseCapture, EnableMouseCapture},
|
event::{DisableMouseCapture, EnableMouseCapture},
|
||||||
terminal::{
|
terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle,
|
EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::diff::DiffResult,
|
bindings::diff::DiffResult,
|
||||||
build::{
|
build::{
|
||||||
watcher::{create_watcher, Watcher},
|
|
||||||
BuildConfig,
|
BuildConfig,
|
||||||
|
watcher::{Watcher, create_watcher},
|
||||||
|
},
|
||||||
|
config::{
|
||||||
|
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
||||||
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
},
|
},
|
||||||
config::{build_globset, ProjectConfig, ProjectObject},
|
|
||||||
diff,
|
|
||||||
diff::{
|
diff::{
|
||||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
|
||||||
|
ObjectDiff,
|
||||||
},
|
},
|
||||||
jobs::{
|
jobs::{
|
||||||
objdiff::{start_build, ObjDiffConfig},
|
|
||||||
Job, JobQueue, JobResult,
|
Job, JobQueue, JobResult,
|
||||||
|
objdiff::{ObjDiffConfig, start_build},
|
||||||
},
|
},
|
||||||
obj,
|
obj::{self, Object},
|
||||||
obj::ObjInfo,
|
|
||||||
};
|
};
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
util::{
|
util::{
|
||||||
output::{write_output, OutputFormat},
|
output::{OutputFormat, write_output},
|
||||||
term::crossterm_panic_handler,
|
term::crossterm_panic_handler,
|
||||||
},
|
},
|
||||||
views::{function_diff::FunctionDiffUi, EventControlFlow, EventResult, UiView},
|
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Diff two object files. (Interactive or one-shot mode)
|
/// Diff two object files. (Interactive or one-shot mode)
|
||||||
#[argp(subcommand, name = "diff")]
|
#[argp(subcommand, name = "diff")]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[argp(option, short = '1')]
|
#[argp(option, short = '1', from_str_fn(platform_path))]
|
||||||
/// Target object file
|
/// Target object file
|
||||||
target: Option<PathBuf>,
|
target: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = '2')]
|
#[argp(option, short = '2', from_str_fn(platform_path))]
|
||||||
/// Base object file
|
/// Base object file
|
||||||
base: Option<PathBuf>,
|
base: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'u')]
|
#[argp(option, short = 'u')]
|
||||||
/// Unit name within project
|
/// Unit name within project
|
||||||
unit: Option<String>,
|
unit: Option<String>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file (one-shot mode) ("-" for stdout)
|
/// Output file (one-shot mode) ("-" for stdout)
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option)]
|
#[argp(option)]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
@@ -89,86 +90,61 @@ pub struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let (target_path, base_path, project_config) = match (
|
let (target_path, base_path, project_config) =
|
||||||
&args.target,
|
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||||
&args.base,
|
|
||||||
&args.project,
|
|
||||||
&args.unit,
|
|
||||||
) {
|
|
||||||
(Some(_), Some(_), None, None)
|
(Some(_), Some(_), None, None)
|
||||||
| (Some(_), None, None, None)
|
| (Some(_), None, None, None)
|
||||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||||
(None, None, p, u) => {
|
(None, None, p, u) => {
|
||||||
let project = match p {
|
let project = match p {
|
||||||
Some(project) => project.clone(),
|
Some(project) => project.clone(),
|
||||||
_ => std::env::current_dir().context("Failed to get the current directory")?,
|
_ => check_path_buf(
|
||||||
|
std::env::current_dir().context("Failed to get the current directory")?,
|
||||||
|
)
|
||||||
|
.context("Current directory is not valid UTF-8")?,
|
||||||
};
|
};
|
||||||
let Some((project_config, project_config_info)) =
|
let Some((project_config, project_config_info)) =
|
||||||
objdiff_core::config::try_project_config(&project)
|
objdiff_core::config::try_project_config(project.as_ref())
|
||||||
else {
|
else {
|
||||||
bail!("Project config not found in {}", &project.display())
|
bail!("Project config not found in {}", &project)
|
||||||
};
|
};
|
||||||
let mut project_config = project_config.with_context(|| {
|
let project_config = project_config.with_context(|| {
|
||||||
format!("Reading project config {}", project_config_info.path.display())
|
format!("Reading project config {}", project_config_info.path.display())
|
||||||
})?;
|
})?;
|
||||||
let object = {
|
let target_obj_dir = project_config
|
||||||
let resolve_paths = |o: &mut ProjectObject| {
|
.target_dir
|
||||||
o.resolve_paths(
|
.as_ref()
|
||||||
&project,
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
project_config.target_dir.as_deref(),
|
let base_obj_dir = project_config
|
||||||
project_config.base_dir.as_deref(),
|
.base_dir
|
||||||
)
|
.as_ref()
|
||||||
};
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
if let Some(u) = u {
|
let objects = project_config
|
||||||
let unit_path =
|
|
||||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
|
||||||
|
|
||||||
let Some(object) = project_config
|
|
||||||
.units
|
.units
|
||||||
.as_deref_mut()
|
.iter()
|
||||||
.unwrap_or_default()
|
.flatten()
|
||||||
.iter_mut()
|
.map(|o| {
|
||||||
.find_map(|obj| {
|
ObjectConfig::new(
|
||||||
if obj.name.as_deref() == Some(u) {
|
o,
|
||||||
resolve_paths(obj);
|
&project,
|
||||||
return Some(obj);
|
target_obj_dir.as_deref(),
|
||||||
}
|
base_obj_dir.as_deref(),
|
||||||
|
)
|
||||||
let up = unit_path.as_deref()?;
|
|
||||||
|
|
||||||
resolve_paths(obj);
|
|
||||||
|
|
||||||
if [&obj.base_path, &obj.target_path]
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
|
||||||
.any(|p| p == up)
|
|
||||||
{
|
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
else {
|
.collect::<Vec<_>>();
|
||||||
bail!("Unit not found: {}", u)
|
let object = if let Some(u) = u {
|
||||||
};
|
objects
|
||||||
|
.iter()
|
||||||
object
|
.find(|obj| obj.name == *u)
|
||||||
|
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||||
} else if let Some(symbol_name) = &args.symbol {
|
} else if let Some(symbol_name) = &args.symbol {
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (i, obj) in project_config
|
for (i, obj) in objects.iter().enumerate() {
|
||||||
.units
|
|
||||||
.as_deref_mut()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
resolve_paths(obj);
|
|
||||||
|
|
||||||
if obj
|
if obj
|
||||||
.target_path
|
.target_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|o| obj::read::has_function(o, symbol_name))
|
.map(|o| obj::read::has_function(o.as_ref(), symbol_name))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@@ -181,7 +157,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
match (count, idx) {
|
match (count, idx) {
|
||||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||||
(1, Some(i)) => &mut project_config.units_mut()[i],
|
(1, Some(i)) => &objects[i],
|
||||||
(2.., Some(_)) => bail!(
|
(2.., Some(_)) => bail!(
|
||||||
"Multiple instances of {} were found, try specifying a unit",
|
"Multiple instances of {} were found, try specifying a unit",
|
||||||
symbol_name
|
symbol_name
|
||||||
@@ -190,7 +166,6 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let target_path = object.target_path.clone();
|
let target_path = object.target_path.clone();
|
||||||
let base_path = object.base_path.clone();
|
let base_path = object.base_path.clone();
|
||||||
@@ -245,24 +220,24 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
|
|||||||
|
|
||||||
fn run_oneshot(
|
fn run_oneshot(
|
||||||
args: &Args,
|
args: &Args,
|
||||||
output: &Path,
|
output: &Utf8PlatformPath,
|
||||||
target_path: Option<&Path>,
|
target_path: Option<&Utf8PlatformPath>,
|
||||||
base_path: Option<&Path>,
|
base_path: Option<&Utf8PlatformPath>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||||
let target = target_path
|
let target = target_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = base_path
|
let base = base_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||||
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
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)));
|
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)?;
|
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
|
||||||
@@ -272,13 +247,13 @@ fn run_oneshot(
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub waker: Arc<TermWaker>,
|
pub waker: Arc<TermWaker>,
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
pub project_config: Option<ProjectConfig>,
|
pub project_config: Option<ProjectConfig>,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub left_obj: Option<(ObjInfo, ObjDiff)>,
|
pub left_obj: Option<(Object, ObjectDiff)>,
|
||||||
pub right_obj: Option<(ObjInfo, ObjDiff)>,
|
pub right_obj: Option<(Object, ObjectDiff)>,
|
||||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
pub prev_obj: Option<(Object, ObjectDiff)>,
|
||||||
pub reload_time: Option<time::OffsetDateTime>,
|
pub reload_time: Option<time::OffsetDateTime>,
|
||||||
pub time_format: Vec<time::format_description::FormatItem<'static>>,
|
pub time_format: Vec<time::format_description::FormatItem<'static>>,
|
||||||
pub watcher: Option<Watcher>,
|
pub watcher: Option<Watcher>,
|
||||||
@@ -315,6 +290,49 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The configuration for a single object file.
|
||||||
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ObjectConfig {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
pub metadata: ProjectObjectMetadata,
|
||||||
|
pub complete: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectConfig {
|
||||||
|
pub fn new(
|
||||||
|
object: &ProjectObject,
|
||||||
|
project_dir: &Utf8PlatformPath,
|
||||||
|
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
) -> Self {
|
||||||
|
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
object.target_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||||
|
};
|
||||||
|
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
object.base_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
name: object.name().to_string(),
|
||||||
|
target_path,
|
||||||
|
base_path,
|
||||||
|
metadata: object.metadata.clone().unwrap_or_default(),
|
||||||
|
complete: object.complete(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn reload(&mut self) -> Result<()> {
|
fn reload(&mut self) -> Result<()> {
|
||||||
let config = create_objdiff_config(self);
|
let config = create_objdiff_config(self);
|
||||||
@@ -355,8 +373,8 @@ impl Wake for TermWaker {
|
|||||||
|
|
||||||
fn run_interactive(
|
fn run_interactive(
|
||||||
args: Args,
|
args: Args,
|
||||||
target_path: Option<PathBuf>,
|
target_path: Option<Utf8PlatformPathBuf>,
|
||||||
base_path: Option<PathBuf>,
|
base_path: Option<Utf8PlatformPathBuf>,
|
||||||
project_config: Option<ProjectConfig>,
|
project_config: Option<ProjectConfig>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||||
@@ -384,7 +402,7 @@ fn run_interactive(
|
|||||||
let watch_patterns = project_config.build_watch_patterns()?;
|
let watch_patterns = project_config.build_watch_patterns()?;
|
||||||
state.watcher = Some(create_watcher(
|
state.watcher = Some(create_watcher(
|
||||||
state.modified.clone(),
|
state.modified.clone(),
|
||||||
project_dir,
|
project_dir.as_ref(),
|
||||||
build_globset(&watch_patterns)?,
|
build_globset(&watch_patterns)?,
|
||||||
Waker::from(state.waker.clone()),
|
Waker::from(state.waker.clone()),
|
||||||
)?);
|
)?);
|
||||||
@@ -407,7 +425,8 @@ fn run_interactive(
|
|||||||
let mut result = EventResult { redraw: true, ..Default::default() };
|
let mut result = EventResult { redraw: true, ..Default::default() };
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
if result.redraw {
|
if result.redraw {
|
||||||
terminal.draw(|f| loop {
|
terminal.draw(|f| {
|
||||||
|
loop {
|
||||||
result.redraw = false;
|
result.redraw = false;
|
||||||
view.draw(&state, f, &mut result);
|
view.draw(&state, f, &mut result);
|
||||||
result.click_xy = None;
|
result.click_xy = None;
|
||||||
@@ -416,6 +435,7 @@ fn run_interactive(
|
|||||||
}
|
}
|
||||||
// Clear buffer on redraw
|
// Clear buffer on redraw
|
||||||
f.buffer_mut().reset();
|
f.buffer_mut().reset();
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
use std::{
|
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
|
||||||
collections::HashSet,
|
|
||||||
fs::File,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::report::{
|
bindings::report::{
|
||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
||||||
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
REPORT_VERSION,
|
|
||||||
},
|
},
|
||||||
config::ProjectObject,
|
config::path::platform_path,
|
||||||
diff, obj,
|
diff, obj,
|
||||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
obj::{SectionKind, SymbolFlag},
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::util::output::{write_output, OutputFormat};
|
use crate::{
|
||||||
|
cmd::diff::ObjectConfig,
|
||||||
|
util::output::{OutputFormat, write_output},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
@@ -43,12 +40,12 @@ pub enum SubCommand {
|
|||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
#[argp(subcommand, name = "generate")]
|
#[argp(subcommand, name = "generate")]
|
||||||
pub struct GenerateArgs {
|
pub struct GenerateArgs {
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(switch, short = 'd')]
|
#[argp(switch, short = 'd')]
|
||||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
@@ -61,15 +58,15 @@ pub struct GenerateArgs {
|
|||||||
/// List any changes from a previous report.
|
/// List any changes from a previous report.
|
||||||
#[argp(subcommand, name = "changes")]
|
#[argp(subcommand, name = "changes")]
|
||||||
pub struct ChangesArgs {
|
pub struct ChangesArgs {
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Previous report file
|
/// Previous report file
|
||||||
previous: PathBuf,
|
previous: Utf8PlatformPathBuf,
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Current report file
|
/// Current report file
|
||||||
current: PathBuf,
|
current: Utf8PlatformPathBuf,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'f')]
|
#[argp(option, short = 'f')]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
@@ -84,10 +81,10 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
info!("Loading project {}", project_dir.display());
|
info!("Loading project {}", project_dir);
|
||||||
|
|
||||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||||
Some((Ok(config), _)) => config,
|
Some((Ok(config), _)) => config,
|
||||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||||
None => bail!("No project configuration found"),
|
None => bail!("No project configuration found"),
|
||||||
@@ -98,37 +95,33 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_obj_dir =
|
||||||
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let base_obj_dir =
|
||||||
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let objects = project
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut units = vec![];
|
let mut units = vec![];
|
||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in project.units.as_deref_mut().unwrap_or_default() {
|
for object in &objects {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
Some(&mut existing_functions),
|
|
||||||
)? {
|
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = objects
|
||||||
.units
|
.par_iter()
|
||||||
.as_deref_mut()
|
.map(|object| report_object(object, None))
|
||||||
.unwrap_or_default()
|
|
||||||
.par_iter_mut()
|
|
||||||
.map(|object| {
|
|
||||||
report_object(
|
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
@@ -151,59 +144,54 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &mut ProjectObject,
|
object: &ObjectConfig,
|
||||||
project_dir: &Path,
|
|
||||||
target_dir: Option<&Path>,
|
|
||||||
base_dir: Option<&Path>,
|
|
||||||
mut existing_functions: Option<&mut HashSet<String>>,
|
mut existing_functions: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||||
warn!("Skipping object without target: {}", object.name());
|
warn!("Skipping object without target: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("Skipping object without target or base: {}", object.name());
|
warn!("Skipping object without target or base: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let diff_config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
let diff_config = diff::DiffObjConfig {
|
||||||
|
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let mapping_config = diff::MappingConfig::default();
|
let mapping_config = diff::MappingConfig::default();
|
||||||
let target = object
|
let target = object
|
||||||
.target_path
|
.target_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config)
|
obj::read::read(p.as_ref(), &diff_config)
|
||||||
.with_context(|| format!("Failed to open {}", p.display()))
|
.with_context(|| format!("Failed to open {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = object
|
let base = object
|
||||||
.base_path
|
.base_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config)
|
obj::read::read(p.as_ref(), &diff_config)
|
||||||
.with_context(|| format!("Failed to open {}", p.display()))
|
.with_context(|| format!("Failed to open {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.complete(),
|
complete: object.metadata.complete,
|
||||||
module_name: target
|
module_name: target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.split_meta.as_ref())
|
.and_then(|o| o.split_meta.as_ref())
|
||||||
.and_then(|m| m.module_name.clone()),
|
.and_then(|m| m.module_name.clone()),
|
||||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||||
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||||
progress_categories: object
|
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||||
.metadata
|
auto_generated: object.metadata.auto_generated,
|
||||||
.as_ref()
|
|
||||||
.and_then(|m| m.progress_categories.clone())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
|
||||||
};
|
};
|
||||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
@@ -211,15 +199,13 @@ fn report_object(
|
|||||||
|
|
||||||
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
||||||
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
||||||
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
|
for ((section_idx, section), section_diff) in
|
||||||
|
obj.sections.iter().enumerate().zip(&obj_diff.sections)
|
||||||
|
{
|
||||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
sections.push(ReportItem {
|
sections.push(ReportItem {
|
||||||
name: section.name.clone(),
|
name: section.name.clone(),
|
||||||
@@ -232,23 +218,26 @@ fn report_object(
|
|||||||
});
|
});
|
||||||
|
|
||||||
match section.kind {
|
match section.kind {
|
||||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss => {
|
||||||
measures.total_data += section.size;
|
measures.total_data += section.size;
|
||||||
if section_match_percent == 100.0 {
|
if section_match_percent == 100.0 {
|
||||||
measures.matched_data += section.size;
|
measures.matched_data += section.size;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Code => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
|
||||||
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
if symbol.section != Some(section_idx)
|
||||||
|
|| symbol.size == 0
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(existing_functions) = &mut existing_functions {
|
if let Some(existing_functions) = &mut existing_functions {
|
||||||
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|
if (symbol.flags.contains(SymbolFlag::Global)
|
||||||
|| symbol.flags.0.contains(ObjSymbolFlags::Weak))
|
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||||
&& !existing_functions.insert(symbol.name.clone())
|
&& !existing_functions.insert(symbol.name.clone())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -257,11 +246,7 @@ fn report_object(
|
|||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
||||||
measures.total_code += symbol.size;
|
measures.total_code += symbol.size;
|
||||||
@@ -291,7 +276,7 @@ fn report_object(
|
|||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
Ok(Some(ReportUnit {
|
Ok(Some(ReportUnit {
|
||||||
name: object.name().to_string(),
|
name: object.name.clone(),
|
||||||
measures: Some(measures),
|
measures: Some(measures),
|
||||||
sections,
|
sections,
|
||||||
functions,
|
functions,
|
||||||
@@ -301,7 +286,7 @@ fn report_object(
|
|||||||
|
|
||||||
fn changes(args: ChangesArgs) -> Result<()> {
|
fn changes(args: ChangesArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
|
let (previous, current) = if args.previous == "-" && args.current == "-" {
|
||||||
// Special case for comparing two reports from stdin
|
// Special case for comparing two reports from stdin
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
@@ -416,15 +401,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_report(path: &Path) -> Result<Report> {
|
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||||
if path == Path::new("-") {
|
if path == Utf8PlatformPath::new("-") {
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||||
}
|
}
|
||||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?;
|
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
||||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
let mmap =
|
||||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
||||||
Report::parse(mmap.as_ref())
|
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
||||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
mod argp_version;
|
mod argp_version;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -15,7 +17,7 @@ use anyhow::{Error, Result};
|
|||||||
use argp::{FromArgValue, FromArgs};
|
use argp::{FromArgValue, FromArgs};
|
||||||
use enable_ansi_support::enable_ansi_support;
|
use enable_ansi_support::enable_ansi_support;
|
||||||
use supports_color::Stream;
|
use supports_color::Stream;
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
@@ -34,9 +34,12 @@ impl OutputFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||||
where T: serde::Serialize + prost::Message {
|
where
|
||||||
match output {
|
T: serde::Serialize + prost::Message,
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
match output.as_ref().map(|p| p.as_ref()) {
|
||||||
Some(output) if output != Path::new("-") => {
|
Some(output) if output != Path::new("-") => {
|
||||||
info!("Writing to {}", output.display());
|
info!("Writing to {}", output.display());
|
||||||
let file = File::options()
|
let file = File::options()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{io::stdout, panic};
|
|||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::Show,
|
cursor::Show,
|
||||||
event::DisableMouseCapture,
|
event::DisableMouseCapture,
|
||||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
terminal::{LeaveAlternateScreen, disable_raw_mode},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn crossterm_panic_handler() {
|
pub fn crossterm_panic_handler() {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use anyhow::{bail, Result};
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
|
use anyhow::{Result, bail};
|
||||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{
|
diff::{
|
||||||
display::{display_diff, DiffText, HighlightKind},
|
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
|
||||||
ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
display::{DiffText, DiffTextColor, HighlightKind, display_row},
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef},
|
obj::Object,
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||||
Frame,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{EventControlFlow, EventResult, UiView};
|
use super::{EventControlFlow, EventResult, UiView};
|
||||||
@@ -28,9 +30,9 @@ pub struct FunctionDiffUi {
|
|||||||
pub scroll_state_y: ScrollbarState,
|
pub scroll_state_y: ScrollbarState,
|
||||||
pub per_page: usize,
|
pub per_page: usize,
|
||||||
pub num_rows: usize,
|
pub num_rows: usize,
|
||||||
pub left_sym: Option<SymbolRef>,
|
pub left_sym: Option<usize>,
|
||||||
pub right_sym: Option<SymbolRef>,
|
pub right_sym: Option<usize>,
|
||||||
pub prev_sym: Option<SymbolRef>,
|
pub prev_sym: Option<usize>,
|
||||||
pub open_options: bool,
|
pub open_options: bool,
|
||||||
pub three_way: bool,
|
pub three_way: bool,
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,8 @@ impl UiView for FunctionDiffUi {
|
|||||||
f.render_widget(line_l, header_chunks[0]);
|
f.render_widget(line_l, header_chunks[0]);
|
||||||
|
|
||||||
let mut line_r = Line::default();
|
let mut line_r = Line::default();
|
||||||
if let Some(percent) =
|
if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||||
get_symbol(state.right_obj.as_ref(), self.right_sym).and_then(|(_, d)| d.match_percent)
|
.and_then(|(_, _, d)| d.match_percent)
|
||||||
{
|
{
|
||||||
line_r.spans.push(Span::styled(
|
line_r.spans.push(Span::styled(
|
||||||
format!("{:.2}% ", percent),
|
format!("{:.2}% ", percent),
|
||||||
@@ -106,13 +108,17 @@ impl UiView for FunctionDiffUi {
|
|||||||
let mut left_text = None;
|
let mut left_text = None;
|
||||||
let mut left_highlight = None;
|
let mut left_highlight = None;
|
||||||
let mut max_width = 0;
|
let mut max_width = 0;
|
||||||
if let Some((symbol, symbol_diff)) = get_symbol(state.left_obj.as_ref(), self.left_sym) {
|
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
get_symbol(state.left_obj.as_ref(), self.left_sym)
|
||||||
|
{
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[0].inner(Margin::new(0, 1));
|
let rect = content_chunks[0].inner(Margin::new(0, 1));
|
||||||
left_highlight = self.print_sym(
|
left_highlight = self.print_sym(
|
||||||
&mut text,
|
&mut text,
|
||||||
symbol,
|
obj,
|
||||||
|
symbol_idx,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
rect,
|
rect,
|
||||||
&self.left_highlight,
|
&self.left_highlight,
|
||||||
result,
|
result,
|
||||||
@@ -125,13 +131,17 @@ impl UiView for FunctionDiffUi {
|
|||||||
let mut right_text = None;
|
let mut right_text = None;
|
||||||
let mut right_highlight = None;
|
let mut right_highlight = None;
|
||||||
let mut margin_text = None;
|
let mut margin_text = None;
|
||||||
if let Some((symbol, symbol_diff)) = get_symbol(state.right_obj.as_ref(), self.right_sym) {
|
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||||
|
{
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[2].inner(Margin::new(0, 1));
|
let rect = content_chunks[2].inner(Margin::new(0, 1));
|
||||||
right_highlight = self.print_sym(
|
right_highlight = self.print_sym(
|
||||||
&mut text,
|
&mut text,
|
||||||
symbol,
|
obj,
|
||||||
|
symbol_idx,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
rect,
|
rect,
|
||||||
&self.right_highlight,
|
&self.right_highlight,
|
||||||
result,
|
result,
|
||||||
@@ -150,14 +160,17 @@ 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((symbol, symbol_diff)) = get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
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,
|
||||||
symbol,
|
obj,
|
||||||
|
symbol_idx,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
rect,
|
rect,
|
||||||
&self.right_highlight,
|
&self.right_highlight,
|
||||||
result,
|
result,
|
||||||
@@ -368,10 +381,15 @@ impl UiView for FunctionDiffUi {
|
|||||||
self.scroll_x = self.scroll_x.saturating_sub(1);
|
self.scroll_x = self.scroll_x.saturating_sub(1);
|
||||||
result.redraw = true;
|
result.redraw = true;
|
||||||
}
|
}
|
||||||
// Toggle relax relocation diffs
|
// Cycle through function relocation diff mode
|
||||||
KeyCode::Char('x') => {
|
KeyCode::Char('x') => {
|
||||||
state.diff_obj_config.relax_reloc_diffs =
|
state.diff_obj_config.function_reloc_diffs =
|
||||||
!state.diff_obj_config.relax_reloc_diffs;
|
match state.diff_obj_config.function_reloc_diffs {
|
||||||
|
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
|
||||||
|
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
|
||||||
|
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||||
|
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||||
|
};
|
||||||
result.redraw = true;
|
result.redraw = true;
|
||||||
return EventControlFlow::Reload;
|
return EventControlFlow::Reload;
|
||||||
}
|
}
|
||||||
@@ -430,9 +448,11 @@ impl UiView for FunctionDiffUi {
|
|||||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||||
) {
|
) {
|
||||||
(Some((_l, ld)), Some((_r, rd))) => ld.instructions.len().max(rd.instructions.len()),
|
(Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
|
||||||
(Some((_l, ld)), None) => ld.instructions.len(),
|
ld.instruction_rows.len().max(rd.instruction_rows.len())
|
||||||
(None, Some((_r, rd))) => rd.instructions.len(),
|
}
|
||||||
|
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
|
||||||
|
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
|
||||||
(None, None) => bail!("Symbol not found: {}", self.symbol_name),
|
(None, None) => bail!("Symbol not found: {}", self.symbol_name),
|
||||||
};
|
};
|
||||||
self.left_sym = left_sym;
|
self.left_sym = left_sym;
|
||||||
@@ -475,113 +495,82 @@ impl FunctionDiffUi {
|
|||||||
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn print_sym(
|
fn print_sym(
|
||||||
&self,
|
&self,
|
||||||
out: &mut Text<'static>,
|
out: &mut Text<'static>,
|
||||||
symbol: &ObjSymbol,
|
obj: &Object,
|
||||||
symbol_diff: &ObjSymbolDiff,
|
symbol_index: usize,
|
||||||
|
symbol_diff: &SymbolDiff,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
highlight: &HighlightKind,
|
highlight: &HighlightKind,
|
||||||
result: &EventResult,
|
result: &EventResult,
|
||||||
only_changed: bool,
|
only_changed: bool,
|
||||||
) -> Option<HighlightKind> {
|
) -> Option<HighlightKind> {
|
||||||
let base_addr = symbol.address;
|
|
||||||
let mut new_highlight = None;
|
let mut new_highlight = None;
|
||||||
for (y, ins_diff) in symbol_diff
|
for (y, ins_row) in symbol_diff
|
||||||
.instructions
|
.instruction_rows
|
||||||
.iter()
|
.iter()
|
||||||
.skip(self.scroll_y)
|
.skip(self.scroll_y)
|
||||||
.take(rect.height as usize)
|
.take(rect.height as usize)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
if only_changed && ins_diff.kind == ObjInsDiffKind::None {
|
if only_changed && ins_row.kind == InstructionDiffKind::None {
|
||||||
out.lines.push(Line::default());
|
out.lines.push(Line::default());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut sx = rect.x;
|
let mut sx = rect.x;
|
||||||
let sy = rect.y + y as u16;
|
let sy = rect.y + y as u16;
|
||||||
let mut line = Line::default();
|
let mut line = Line::default();
|
||||||
display_diff(ins_diff, base_addr, |text| -> Result<()> {
|
display_row(obj, symbol_index, ins_row, diff_config, |segment| {
|
||||||
let label_text;
|
let highlight_kind = HighlightKind::from(&segment.text);
|
||||||
let mut base_color = match ins_diff.kind {
|
let label_text = match segment.text {
|
||||||
ObjInsDiffKind::None
|
DiffText::Basic(text) => text.to_string(),
|
||||||
| ObjInsDiffKind::OpMismatch
|
DiffText::Line(num) => format!("{num} "),
|
||||||
| ObjInsDiffKind::ArgMismatch => Color::Gray,
|
DiffText::Address(addr) => format!("{:x}:", addr),
|
||||||
ObjInsDiffKind::Replace => Color::Cyan,
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
ObjInsDiffKind::Delete => Color::Red,
|
DiffText::Argument(arg) => arg.to_string(),
|
||||||
ObjInsDiffKind::Insert => Color::Green,
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
};
|
DiffText::Symbol(sym) => {
|
||||||
let mut pad_to = 0;
|
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||||
match text {
|
|
||||||
DiffText::Basic(text) => {
|
|
||||||
label_text = text.to_string();
|
|
||||||
}
|
|
||||||
DiffText::BasicColor(s, idx) => {
|
|
||||||
label_text = s.to_string();
|
|
||||||
base_color = COLOR_ROTATION[idx % COLOR_ROTATION.len()];
|
|
||||||
}
|
|
||||||
DiffText::Line(num) => {
|
|
||||||
label_text = format!("{num} ");
|
|
||||||
base_color = Color::DarkGray;
|
|
||||||
pad_to = 5;
|
|
||||||
}
|
|
||||||
DiffText::Address(addr) => {
|
|
||||||
label_text = format!("{:x}:", addr);
|
|
||||||
pad_to = 5;
|
|
||||||
}
|
|
||||||
DiffText::Opcode(mnemonic, _op) => {
|
|
||||||
label_text = mnemonic.to_string();
|
|
||||||
if ins_diff.kind == ObjInsDiffKind::OpMismatch {
|
|
||||||
base_color = Color::Blue;
|
|
||||||
}
|
|
||||||
pad_to = 8;
|
|
||||||
}
|
|
||||||
DiffText::Argument(arg, diff) => {
|
|
||||||
label_text = arg.to_string();
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffText::BranchDest(addr, diff) => {
|
|
||||||
label_text = format!("{addr:x}");
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffText::Symbol(sym, diff) => {
|
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
|
||||||
label_text = name.clone();
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
|
||||||
} else {
|
|
||||||
base_color = Color::White;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||||
|
Ordering::Greater => format!("+{:#x}", addend),
|
||||||
|
Ordering::Less => format!("-{:#x}", -addend),
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
DiffText::Spacing(n) => {
|
DiffText::Spacing(n) => {
|
||||||
line.spans.push(Span::raw(" ".repeat(n)));
|
line.spans.push(Span::raw(" ".repeat(n as usize)));
|
||||||
sx += n as u16;
|
sx += n as u16;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
DiffText::Eol => {
|
DiffText::Eol => return Ok(()),
|
||||||
return Ok(());
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
let len = label_text.len();
|
let len = label_text.len();
|
||||||
let highlighted = *highlight == text;
|
let highlighted =
|
||||||
|
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 {
|
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
||||||
new_highlight = Some(text.into());
|
new_highlight = Some(highlight_kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut style = Style::new().fg(base_color);
|
let mut style = Style::new().fg(match segment.color {
|
||||||
|
DiffTextColor::Normal => Color::Gray,
|
||||||
|
DiffTextColor::Dim => Color::DarkGray,
|
||||||
|
DiffTextColor::Bright => Color::White,
|
||||||
|
DiffTextColor::Replace => Color::Cyan,
|
||||||
|
DiffTextColor::Delete => Color::Red,
|
||||||
|
DiffTextColor::Insert => Color::Green,
|
||||||
|
DiffTextColor::Rotating(i) => COLOR_ROTATION[i as usize % COLOR_ROTATION.len()],
|
||||||
|
});
|
||||||
if highlighted {
|
if highlighted {
|
||||||
style = style.bg(Color::DarkGray);
|
style = style.bg(Color::DarkGray);
|
||||||
}
|
}
|
||||||
line.spans.push(Span::styled(label_text, style));
|
line.spans.push(Span::styled(label_text, style));
|
||||||
sx += len as u16;
|
sx += len as u16;
|
||||||
if pad_to > len {
|
if segment.pad_to as usize > len {
|
||||||
let pad = (pad_to - len) as u16;
|
let pad = (segment.pad_to as usize - len) as u16;
|
||||||
line.spans.push(Span::raw(" ".repeat(pad as usize)));
|
line.spans.push(Span::raw(" ".repeat(pad as usize)));
|
||||||
sx += pad;
|
sx += pad;
|
||||||
}
|
}
|
||||||
@@ -593,12 +582,13 @@ impl FunctionDiffUi {
|
|||||||
new_highlight
|
new_highlight
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_margin(&self, out: &mut Text, symbol: &ObjSymbolDiff, rect: Rect) {
|
fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
|
||||||
for ins_diff in symbol.instructions.iter().skip(self.scroll_y).take(rect.height as usize) {
|
for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
|
||||||
if ins_diff.kind != ObjInsDiffKind::None {
|
{
|
||||||
out.lines.push(Line::raw(match ins_diff.kind {
|
if ins_row.kind != InstructionDiffKind::None {
|
||||||
ObjInsDiffKind::Delete => "<",
|
out.lines.push(Line::raw(match ins_row.kind {
|
||||||
ObjInsDiffKind::Insert => ">",
|
InstructionDiffKind::Delete => "<",
|
||||||
|
InstructionDiffKind::Insert => ">",
|
||||||
_ => "|",
|
_ => "|",
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
@@ -630,23 +620,18 @@ pub fn match_percent_color(match_percent: f32) -> Color {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_symbol(
|
fn get_symbol(
|
||||||
obj: Option<&(ObjInfo, ObjDiff)>,
|
obj: Option<&(Object, ObjectDiff)>,
|
||||||
sym: Option<SymbolRef>,
|
sym: Option<usize>,
|
||||||
) -> Option<(&ObjSymbol, &ObjSymbolDiff)> {
|
) -> Option<(&Object, usize, &SymbolDiff)> {
|
||||||
let (obj, diff) = obj?;
|
let (obj, diff) = obj?;
|
||||||
let sym = sym?;
|
let sym = sym?;
|
||||||
Some((obj.section_symbol(sym).1, diff.symbol_diff(sym)))
|
Some((obj, sym, &diff.symbols[sym]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
fn find_function(obj: &Object, name: &str) -> Option<usize> {
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
||||||
if section.kind != ObjSectionKind::Code {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
|
||||||
if symbol.name == name {
|
if symbol.name == name {
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
return Some(symbol_idx);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -12,54 +12,42 @@ A local diffing tool for decompilation projects.
|
|||||||
"""
|
"""
|
||||||
documentation = "https://docs.rs/objdiff-core"
|
documentation = "https://docs.rs/objdiff-core"
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["std"]
|
||||||
all = [
|
all = [
|
||||||
# Features
|
# Features
|
||||||
"bindings",
|
"bindings",
|
||||||
"build",
|
"build",
|
||||||
"config",
|
"config",
|
||||||
"dwarf",
|
"dwarf",
|
||||||
|
"serde",
|
||||||
# Architectures
|
# Architectures
|
||||||
|
"arm",
|
||||||
|
"arm64",
|
||||||
"mips",
|
"mips",
|
||||||
"ppc",
|
"ppc",
|
||||||
"x86",
|
"x86",
|
||||||
"arm",
|
|
||||||
"arm64",
|
|
||||||
]
|
]
|
||||||
# Implicit, used to check if any arch is enabled
|
# Implicit, used to check if any arch is enabled
|
||||||
any-arch = [
|
any-arch = [
|
||||||
"config",
|
|
||||||
"dep:bimap",
|
|
||||||
"dep:byteorder",
|
|
||||||
"dep:flagset",
|
"dep:flagset",
|
||||||
"dep:heck",
|
"dep:heck",
|
||||||
"dep:log",
|
"dep:log",
|
||||||
"dep:memmap2",
|
|
||||||
"dep:num-traits",
|
"dep:num-traits",
|
||||||
"dep:prettyplease",
|
"dep:prettyplease",
|
||||||
"dep:proc-macro2",
|
"dep:proc-macro2",
|
||||||
"dep:quote",
|
"dep:quote",
|
||||||
"dep:serde",
|
"dep:regex",
|
||||||
"dep:serde_json",
|
|
||||||
"dep:similar",
|
"dep:similar",
|
||||||
"dep:strum",
|
|
||||||
"dep:syn",
|
"dep:syn",
|
||||||
]
|
]
|
||||||
bindings = [
|
bindings = [
|
||||||
"dep:pbjson",
|
|
||||||
"dep:pbjson-build",
|
|
||||||
"dep:prost",
|
"dep:prost",
|
||||||
"dep:prost-build",
|
"dep:prost-build",
|
||||||
"dep:serde",
|
|
||||||
"dep:serde_json",
|
|
||||||
]
|
]
|
||||||
build = [
|
build = [
|
||||||
"dep:notify",
|
"dep:notify",
|
||||||
"dep:notify-debouncer-full",
|
"dep:notify-debouncer-full",
|
||||||
"dep:path-slash",
|
|
||||||
"dep:reqwest",
|
"dep:reqwest",
|
||||||
"dep:self_update",
|
"dep:self_update",
|
||||||
"dep:shell-escape",
|
"dep:shell-escape",
|
||||||
@@ -68,15 +56,29 @@ build = [
|
|||||||
"dep:winapi",
|
"dep:winapi",
|
||||||
]
|
]
|
||||||
config = [
|
config = [
|
||||||
"dep:bimap",
|
|
||||||
"dep:filetime",
|
|
||||||
"dep:globset",
|
"dep:globset",
|
||||||
"dep:semver",
|
"dep:semver",
|
||||||
"dep:serde",
|
"dep:typed-path",
|
||||||
"dep:serde_json",
|
|
||||||
"dep:serde_yaml",
|
|
||||||
]
|
]
|
||||||
dwarf = ["dep:gimli"]
|
dwarf = ["dep:gimli"]
|
||||||
|
serde = [
|
||||||
|
"dep:pbjson",
|
||||||
|
"dep:pbjson-build",
|
||||||
|
"dep:serde",
|
||||||
|
"dep:serde_json",
|
||||||
|
]
|
||||||
|
std = [
|
||||||
|
"anyhow/std",
|
||||||
|
"flagset?/std",
|
||||||
|
"log?/std",
|
||||||
|
"num-traits?/std",
|
||||||
|
"object/std",
|
||||||
|
"prost?/std",
|
||||||
|
"serde?/std",
|
||||||
|
"typed-path?/std",
|
||||||
|
"dep:filetime",
|
||||||
|
"dep:memmap2",
|
||||||
|
]
|
||||||
mips = [
|
mips = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:rabbitizer",
|
"dep:rabbitizer",
|
||||||
@@ -86,6 +88,7 @@ ppc = [
|
|||||||
"dep:cwdemangle",
|
"dep:cwdemangle",
|
||||||
"dep:cwextab",
|
"dep:cwextab",
|
||||||
"dep:ppc750cl",
|
"dep:ppc750cl",
|
||||||
|
"dep:rlwinmdec",
|
||||||
]
|
]
|
||||||
x86 = [
|
x86 = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
@@ -105,78 +108,64 @@ arm64 = [
|
|||||||
"dep:yaxpeax-arch",
|
"dep:yaxpeax-arch",
|
||||||
"dep:yaxpeax-arm",
|
"dep:yaxpeax-arm",
|
||||||
]
|
]
|
||||||
wasm = [
|
|
||||||
"any-arch",
|
|
||||||
"bindings",
|
|
||||||
"dep:console_error_panic_hook",
|
|
||||||
"dep:console_log",
|
|
||||||
"dep:log",
|
|
||||||
"dep:tsify-next",
|
|
||||||
"dep:wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = { version = "1.0", default-features = false }
|
||||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
|
||||||
byteorder = { version = "1.5", optional = true }
|
|
||||||
filetime = { version = "0.2", optional = true }
|
filetime = { version = "0.2", optional = true }
|
||||||
flagset = { version = "0.4", optional = true }
|
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
|
||||||
log = { version = "0.4", optional = true }
|
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
|
||||||
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
memmap2 = { version = "0.9", optional = true }
|
memmap2 = { version = "0.9", optional = true }
|
||||||
num-traits = { version = "0.2", optional = true }
|
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
|
||||||
pbjson = { version = "0.7", optional = true }
|
pbjson = { version = "0.7", default-features = false, optional = true }
|
||||||
prost = { version = "0.13", optional = true }
|
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
||||||
similar = { version = "2.6", default-features = false, optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
strum = { version = "0.26", features = ["derive"], optional = true }
|
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
||||||
wasm-bindgen = { version = "0.2", optional = true }
|
typed-path = { version = "0.10", default-features = false, optional = true }
|
||||||
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
|
||||||
console_log = { version = "1.0", optional = true }
|
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
|
||||||
|
|
||||||
# config
|
# config
|
||||||
globset = { version = "0.4", features = ["serde1"], optional = true }
|
globset = { version = "0.4", default-features = false, optional = true }
|
||||||
semver = { version = "1.0", optional = true }
|
semver = { version = "1.0", default-features = false, optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||||
serde_yaml = { version = "0.9", optional = true }
|
|
||||||
|
|
||||||
# dwarf
|
# dwarf
|
||||||
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
|
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
cwextab = { version = "1.0.2", optional = true }
|
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
|
||||||
ppc750cl = { version = "0.3", optional = true }
|
ppc750cl = { version = "0.3", optional = true }
|
||||||
|
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
rabbitizer = { version = "1.12", optional = true }
|
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
|
||||||
|
|
||||||
# x86
|
# x86
|
||||||
cpp_demangle = { version = "0.4", optional = true }
|
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
|
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||||
msvc-demangler = { version = "0.10", optional = true }
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.6", optional = true }
|
unarm = { version = "1.7", optional = true }
|
||||||
arm-attr = { version = "0.1", optional = true }
|
arm-attr = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||||
|
|
||||||
# build
|
# build
|
||||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", version = "6.1.1", optional = true }
|
notify = { version = "8.0.0", optional = true }
|
||||||
notify-debouncer-full = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", version = "0.4.0", optional = true }
|
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||||
shell-escape = { version = "0.1", optional = true }
|
shell-escape = { version = "0.1", optional = true }
|
||||||
tempfile = { version = "3.14", optional = true }
|
tempfile = { version = "3.17", optional = true }
|
||||||
time = { version = "0.3", optional = true }
|
time = { version = "0.3", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
path-slash = { version = "0.2", optional = true }
|
|
||||||
winapi = { version = "0.3", optional = true }
|
winapi = { version = "0.3", optional = true }
|
||||||
|
|
||||||
# For Linux static binaries, use rustls
|
# For Linux static binaries, use rustls
|
||||||
@@ -196,6 +185,11 @@ prettyplease = { version = "0.2", optional = true }
|
|||||||
proc-macro2 = { version = "1.0", optional = true }
|
proc-macro2 = { version = "1.0", optional = true }
|
||||||
prost-build = { version = "0.13", optional = true }
|
prost-build = { version = "0.13", optional = true }
|
||||||
quote = { version = "1.0", optional = true }
|
quote = { version = "1.0", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0" }
|
||||||
syn = { version = "2.0", optional = true }
|
syn = { version = "2.0", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Enable all features for tests
|
||||||
|
objdiff-core = { path = ".", features = ["all"] }
|
||||||
|
insta = "1.42"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
mod config_gen;
|
mod config_gen;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
compile_protos();
|
compile_protos();
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
config_gen::generate_diff_config();
|
config_gen::generate_diff_config();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
@@ -18,7 +19,7 @@ fn compile_protos() {
|
|||||||
.map(|m| m.modified().unwrap())
|
.map(|m| m.modified().unwrap())
|
||||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||||
let mut run_protoc = false;
|
let mut run_protoc = false;
|
||||||
let proto_files = vec![root.join("diff.proto"), root.join("report.proto")];
|
let proto_files = vec![root.join("report.proto")];
|
||||||
for proto_file in &proto_files {
|
for proto_file in &proto_files {
|
||||||
println!("cargo:rerun-if-changed={}", proto_file.display());
|
println!("cargo:rerun-if-changed={}", proto_file.display());
|
||||||
let mtime = match std::fs::metadata(proto_file) {
|
let mtime = match std::fs::metadata(proto_file) {
|
||||||
@@ -54,6 +55,8 @@ fn compile_protos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
{
|
||||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||||
pbjson_build::Builder::new()
|
pbjson_build::Builder::new()
|
||||||
.register_descriptors(&descriptor_set)
|
.register_descriptors(&descriptor_set)
|
||||||
@@ -62,3 +65,4 @@ fn compile_protos() {
|
|||||||
.build(&[".objdiff"])
|
.build(&[".objdiff"])
|
||||||
.expect("Failed to build pbjson");
|
.expect("Failed to build pbjson");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
{
|
{
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "relaxRelocDiffs",
|
"id": "functionRelocDiffs",
|
||||||
"type": "boolean",
|
"type": "choice",
|
||||||
"default": false,
|
"default": "name_address",
|
||||||
"name": "Relax relocation diffs",
|
"name": "Function relocation diffs",
|
||||||
"description": "Ignores differences in relocation targets. (Address, name, etc)"
|
"description": "How relocation targets will be diffed in the function view.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "none",
|
||||||
|
"name": "None"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "name_address",
|
||||||
|
"name": "Name or address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "data_value",
|
||||||
|
"name": "Data value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "all",
|
||||||
|
"name": "Name or address, data value"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "spaceBetweenArgs",
|
"id": "spaceBetweenArgs",
|
||||||
@@ -21,6 +39,13 @@
|
|||||||
"name": "Combine data sections",
|
"name": "Combine data sections",
|
||||||
"description": "Combines data sections with equal names."
|
"description": "Combines data sections with equal names."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "combineTextSections",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Combine text sections",
|
||||||
|
"description": "Combines all text sections into one."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "arm.archVersion",
|
"id": "arm.archVersion",
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -162,6 +187,20 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mips.registerPrefix",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Register '$' prefix",
|
||||||
|
"description": "Display MIPS register names with a '$' prefix."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ppc.calculatePoolRelocations",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"name": "Calculate pooled data references",
|
||||||
|
"description": "Display pooled data references in functions as fake relocations."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "x86.formatter",
|
"id": "x86.formatter",
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -193,9 +232,10 @@
|
|||||||
"id": "general",
|
"id": "general",
|
||||||
"name": "General",
|
"name": "General",
|
||||||
"properties": [
|
"properties": [
|
||||||
"relaxRelocDiffs",
|
"functionRelocDiffs",
|
||||||
"spaceBetweenArgs",
|
"spaceBetweenArgs",
|
||||||
"combineDataSections"
|
"combineDataSections",
|
||||||
|
"combineTextSections"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -216,7 +256,15 @@
|
|||||||
"name": "MIPS",
|
"name": "MIPS",
|
||||||
"properties": [
|
"properties": [
|
||||||
"mips.abi",
|
"mips.abi",
|
||||||
"mips.instrCategory"
|
"mips.instrCategory",
|
||||||
|
"mips.registerPrefix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ppc",
|
||||||
|
"name": "PowerPC",
|
||||||
|
"properties": [
|
||||||
|
"ppc.calculatePoolRelocations"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
let value = &item.value;
|
let value = &item.value;
|
||||||
variants.extend(quote! {
|
variants.extend(quote! {
|
||||||
#[serde(rename = #value, alias = #variant_name)]
|
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
|
||||||
#variant_ident,
|
#variant_ident,
|
||||||
});
|
});
|
||||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||||
@@ -134,8 +134,8 @@ pub fn generate_diff_config() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
enums.extend(quote! {
|
enums.extend(quote! {
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum #enum_ident {
|
pub enum #enum_ident {
|
||||||
#variants
|
#variants
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::str::FromStr for #enum_ident {
|
impl core::str::FromStr for #enum_ident {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
#variant_from_str
|
#variant_from_str
|
||||||
@@ -244,7 +244,7 @@ pub fn generate_diff_config() {
|
|||||||
let default = b.default;
|
let default = b.default;
|
||||||
if default {
|
if default {
|
||||||
property_fields.extend(quote! {
|
property_fields.extend(quote! {
|
||||||
#[serde(default = "default_true")]
|
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
property_fields.extend(quote! {
|
property_fields.extend(quote! {
|
||||||
@@ -412,7 +412,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::str::FromStr for ConfigPropertyId {
|
impl core::str::FromStr for ConfigPropertyId {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
#config_property_id_from_str
|
#config_property_id_from_str
|
||||||
@@ -433,6 +433,7 @@ pub fn generate_diff_config() {
|
|||||||
Choice(&'static str),
|
Choice(&'static str),
|
||||||
}
|
}
|
||||||
impl ConfigPropertyValue {
|
impl ConfigPropertyValue {
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
pub fn to_json(&self) -> serde_json::Value {
|
pub fn to_json(&self) -> serde_json::Value {
|
||||||
match self {
|
match self {
|
||||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||||
@@ -440,17 +441,25 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl core::fmt::Display for ConfigPropertyValue {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
||||||
|
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ConfigPropertyKind {
|
pub enum ConfigPropertyKind {
|
||||||
Boolean,
|
Boolean,
|
||||||
Choice(&'static [ConfigEnumVariantInfo]),
|
Choice(&'static [ConfigEnumVariantInfo]),
|
||||||
}
|
}
|
||||||
#enums
|
#enums
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_true() -> bool { true }
|
fn default_true() -> bool { true }
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
#[serde(default)]
|
|
||||||
pub struct DiffObjConfig {
|
pub struct DiffObjConfig {
|
||||||
#property_fields
|
#property_fields
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ message Relocation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message RelocationTarget {
|
message RelocationTarget {
|
||||||
Symbol symbol = 1;
|
uint32 symbol_index = 1;
|
||||||
int64 addend = 2;
|
int64 addend = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionDiff {
|
message InstructionDiffRow {
|
||||||
DiffKind diff_kind = 1;
|
DiffKind diff_kind = 1;
|
||||||
optional Instruction instruction = 2;
|
optional Instruction instruction = 2;
|
||||||
optional InstructionBranchFrom branch_from = 3;
|
optional InstructionBranchFrom branch_from = 3;
|
||||||
@@ -122,17 +122,12 @@ message InstructionBranchTo {
|
|||||||
uint32 branch_index = 2;
|
uint32 branch_index = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SymbolRef {
|
|
||||||
optional uint32 section_index = 1;
|
|
||||||
uint32 symbol_index = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SymbolDiff {
|
message SymbolDiff {
|
||||||
Symbol symbol = 1;
|
Symbol symbol = 1;
|
||||||
repeated InstructionDiff instructions = 2;
|
repeated InstructionDiffRow instruction_rows = 2;
|
||||||
optional float match_percent = 3;
|
optional float match_percent = 3;
|
||||||
// The symbol ref in the _other_ object that this symbol was diffed against
|
// The symbol index in the _other_ object that this symbol was diffed against
|
||||||
optional SymbolRef target = 5;
|
optional uint32 target_symbol = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DataDiff {
|
message DataDiff {
|
||||||
@@ -147,7 +142,7 @@ message SectionDiff {
|
|||||||
SectionKind kind = 2;
|
SectionKind kind = 2;
|
||||||
uint64 size = 3;
|
uint64 size = 3;
|
||||||
uint64 address = 4;
|
uint64 address = 4;
|
||||||
repeated SymbolDiff symbols = 5;
|
reserved 5;
|
||||||
repeated DataDiff data = 6;
|
repeated DataDiff data = 6;
|
||||||
optional float match_percent = 7;
|
optional float match_percent = 7;
|
||||||
}
|
}
|
||||||
@@ -157,11 +152,11 @@ enum SectionKind {
|
|||||||
SECTION_TEXT = 1;
|
SECTION_TEXT = 1;
|
||||||
SECTION_DATA = 2;
|
SECTION_DATA = 2;
|
||||||
SECTION_BSS = 3;
|
SECTION_BSS = 3;
|
||||||
SECTION_COMMON = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ObjectDiff {
|
message ObjectDiff {
|
||||||
repeated SectionDiff sections = 1;
|
repeated SectionDiff sections = 1;
|
||||||
|
repeated SymbolDiff symbols = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DiffResult {
|
message DiffResult {
|
||||||
|
|||||||
Binary file not shown.
@@ -1,39 +1,37 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
borrow::Cow,
|
collections::BTreeMap,
|
||||||
collections::{BTreeMap, HashMap},
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
|
||||||
use object::{
|
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||||
elf::{self, SHT_ARM_ATTRIBUTES},
|
use unarm::{args, arm, thumb};
|
||||||
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
|
||||||
SectionKind, Symbol, SymbolKind,
|
|
||||||
};
|
|
||||||
use unarm::{
|
|
||||||
args::{Argument, OffsetImm, OffsetReg, Register},
|
|
||||||
parse::{ArmVersion, ParseMode, Parser},
|
|
||||||
DisplayOptions, ParseFlags, ParsedIns, RegNames,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::Arch,
|
||||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig},
|
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{
|
||||||
|
InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||||
|
ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ObjArchArm {
|
#[derive(Debug)]
|
||||||
|
pub struct ArchArm {
|
||||||
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
||||||
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
|
||||||
detected_version: Option<ArmVersion>,
|
detected_version: Option<unarm::ArmVersion>,
|
||||||
endianness: object::Endianness,
|
endianness: object::Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArchArm {
|
impl ArchArm {
|
||||||
pub fn new(file: &File) -> Result<Self> {
|
pub fn new(file: &object::File) -> Result<Self> {
|
||||||
let endianness = file.endianness();
|
let endianness = file.endianness();
|
||||||
match file {
|
match file {
|
||||||
File::Elf32(_) => {
|
object::File::Elf32(_) => {
|
||||||
let disasm_modes = Self::elf_get_mapping_symbols(file);
|
let disasm_modes = Self::elf_get_mapping_symbols(file);
|
||||||
let detected_version = Self::elf_detect_arm_version(file)?;
|
let detected_version = Self::elf_detect_arm_version(file)?;
|
||||||
Ok(Self { disasm_modes, detected_version, endianness })
|
Ok(Self { disasm_modes, detected_version, endianness })
|
||||||
@@ -42,10 +40,11 @@ impl ObjArchArm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> {
|
fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::ArmVersion>> {
|
||||||
// Check ARM attributes
|
// Check ARM attributes
|
||||||
if let Some(arm_attrs) = file.sections().find(|s| {
|
if let Some(arm_attrs) = file.sections().find(|s| {
|
||||||
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes")
|
s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
|
||||||
|
&& s.name() == Ok(".ARM.attributes")
|
||||||
}) {
|
}) {
|
||||||
let attr_data = arm_attrs.uncompressed_data()?;
|
let attr_data = arm_attrs.uncompressed_data()?;
|
||||||
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
|
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
|
||||||
@@ -59,16 +58,12 @@ impl ObjArchArm {
|
|||||||
}
|
}
|
||||||
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
|
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
|
||||||
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
|
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
|
||||||
if let Tag::CpuArch(cpu_arch) = tag {
|
if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
|
||||||
Some(cpu_arch)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
match cpu_arch {
|
match cpu_arch {
|
||||||
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
|
Some(CpuArch::V4T) => return Ok(Some(unarm::ArmVersion::V4T)),
|
||||||
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
|
Some(CpuArch::V5TE) => return Ok(Some(unarm::ArmVersion::V5Te)),
|
||||||
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
|
Some(CpuArch::V6K) => return Ok(Some(unarm::ArmVersion::V6K)),
|
||||||
Some(arch) => bail!("ARM arch {} not supported", arch),
|
Some(arch) => bail!("ARM arch {} not supported", arch),
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
@@ -78,9 +73,9 @@ impl ObjArchArm {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
|
fn elf_get_mapping_symbols(file: &object::File) -> BTreeMap<usize, Vec<DisasmMode>> {
|
||||||
file.sections()
|
file.sections()
|
||||||
.filter(|s| s.kind() == SectionKind::Text)
|
.filter(|s| s.kind() == object::SectionKind::Text)
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let index = s.index();
|
let index = s.index();
|
||||||
let mut mapping_symbols: Vec<_> = file
|
let mut mapping_symbols: Vec<_> = file
|
||||||
@@ -89,39 +84,102 @@ impl ObjArchArm {
|
|||||||
.filter_map(|s| DisasmMode::from_symbol(&s))
|
.filter_map(|s| DisasmMode::from_symbol(&s))
|
||||||
.collect();
|
.collect();
|
||||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||||
(s.index(), mapping_symbols)
|
(s.index().0, mapping_symbols)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn endian(&self) -> unarm::Endian {
|
||||||
|
match self.endianness {
|
||||||
|
object::Endianness::Little => unarm::Endian::Little,
|
||||||
|
object::Endianness::Big => unarm::Endian::Big,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchArm {
|
fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
|
||||||
fn symbol_address(&self, symbol: &Symbol) -> u64 {
|
unarm::ParseFlags {
|
||||||
let address = symbol.address();
|
ual: diff_config.arm_unified_syntax,
|
||||||
if symbol.kind() == SymbolKind::Text {
|
version: match diff_config.arm_arch_version {
|
||||||
address & !1
|
ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::ArmVersion::V5Te),
|
||||||
|
ArmArchVersion::V4t => unarm::ArmVersion::V4T,
|
||||||
|
ArmArchVersion::V5te => unarm::ArmVersion::V5Te,
|
||||||
|
ArmArchVersion::V6k => unarm::ArmVersion::V6K,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_options(&self, diff_config: &DiffObjConfig) -> unarm::DisplayOptions {
|
||||||
|
unarm::DisplayOptions {
|
||||||
|
reg_names: unarm::RegNames {
|
||||||
|
av_registers: diff_config.arm_av_registers,
|
||||||
|
r9_use: match diff_config.arm_r9_usage {
|
||||||
|
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
|
||||||
|
ArmR9Usage::Sb => unarm::R9Use::Pid,
|
||||||
|
ArmR9Usage::Tr => unarm::R9Use::Tls,
|
||||||
|
},
|
||||||
|
explicit_stack_limit: diff_config.arm_sl_usage,
|
||||||
|
frame_pointer: diff_config.arm_fp_usage,
|
||||||
|
ip: diff_config.arm_ip_usage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ins_ref(
|
||||||
|
&self,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
code: &[u8],
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
|
||||||
|
let code = match (self.endianness, ins_ref.size) {
|
||||||
|
(object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
|
||||||
|
(object::Endianness::Little, 4) => {
|
||||||
|
u32::from_le_bytes([code[0], code[1], code[2], code[3]])
|
||||||
|
}
|
||||||
|
(object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
|
||||||
|
(object::Endianness::Big, 4) => {
|
||||||
|
u32::from_be_bytes([code[0], code[1], code[2], code[3]])
|
||||||
|
}
|
||||||
|
_ => bail!("Invalid instruction size {}", ins_ref.size),
|
||||||
|
};
|
||||||
|
let (ins, parsed_ins) = if ins_ref.opcode == u16::MAX {
|
||||||
|
let mut args = args::Arguments::default();
|
||||||
|
args[0] = args::Argument::UImm(code);
|
||||||
|
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
|
||||||
|
(unarm::Ins::Data, unarm::ParsedIns { mnemonic, args })
|
||||||
|
} else if ins_ref.opcode & (1 << 15) != 0 {
|
||||||
|
let ins = arm::Ins { code, op: arm::Opcode::from(ins_ref.opcode as u8) };
|
||||||
|
let parsed = ins.parse(&self.parse_flags(diff_config));
|
||||||
|
(unarm::Ins::Arm(ins), parsed)
|
||||||
} else {
|
} else {
|
||||||
address
|
let ins = thumb::Ins { code, op: thumb::Opcode::from(ins_ref.opcode as u8) };
|
||||||
|
let parsed = ins.parse(&self.parse_flags(diff_config));
|
||||||
|
if ins.is_half_bl() {
|
||||||
|
todo!("Combine thumb BL instructions");
|
||||||
|
} else {
|
||||||
|
(unarm::Ins::Thumb(ins), parsed)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((ins, parsed_ins))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_code(
|
impl Arch for ArchArm {
|
||||||
|
fn scan_instructions(
|
||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
section_index: usize,
|
section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
diff_config: &DiffObjConfig,
|
||||||
line_info: &BTreeMap<u64, u32>,
|
) -> Result<Vec<ScannedInstruction>> {
|
||||||
config: &DiffObjConfig,
|
|
||||||
) -> Result<ProcessCodeResult> {
|
|
||||||
let start_addr = address as u32;
|
let start_addr = address as u32;
|
||||||
let end_addr = start_addr + code.len() as u32;
|
let end_addr = start_addr + code.len() as u32;
|
||||||
|
|
||||||
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
|
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
|
||||||
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
|
let fallback_mappings =
|
||||||
|
[DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
|
||||||
let mapping_symbols = self
|
let mapping_symbols = self
|
||||||
.disasm_modes
|
.disasm_modes
|
||||||
.get(&SectionIndex(section_index))
|
.get(§ion_index)
|
||||||
.map(|x| x.as_slice())
|
.map(|x| x.as_slice())
|
||||||
.unwrap_or(&fallback_mappings);
|
.unwrap_or(&fallback_mappings);
|
||||||
let first_mapping_idx = mapping_symbols
|
let first_mapping_idx = mapping_symbols
|
||||||
@@ -134,39 +192,14 @@ impl ObjArch for ObjArchArm {
|
|||||||
let mut next_mapping = mappings_iter.next();
|
let mut next_mapping = mappings_iter.next();
|
||||||
|
|
||||||
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
|
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
|
||||||
|
|
||||||
let version = match config.arm_arch_version {
|
let endian = self.endian();
|
||||||
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
|
let parse_flags = self.parse_flags(diff_config);
|
||||||
ArmArchVersion::V4t => ArmVersion::V4T,
|
let mut parser = unarm::Parser::new(first_mapping, start_addr, endian, parse_flags, code);
|
||||||
ArmArchVersion::V5te => ArmVersion::V5Te,
|
|
||||||
ArmArchVersion::V6k => ArmVersion::V6K,
|
|
||||||
};
|
|
||||||
let endian = match self.endianness {
|
|
||||||
object::Endianness::Little => unarm::Endian::Little,
|
|
||||||
object::Endianness::Big => unarm::Endian::Big,
|
|
||||||
};
|
|
||||||
|
|
||||||
let parse_flags = ParseFlags { ual: config.arm_unified_syntax, version };
|
while let Some((address, ins, _parsed_ins)) = parser.next() {
|
||||||
|
let size = parser.mode.instruction_size(address);
|
||||||
let mut parser = Parser::new(first_mapping, start_addr, endian, parse_flags, code);
|
|
||||||
|
|
||||||
let display_options = DisplayOptions {
|
|
||||||
reg_names: RegNames {
|
|
||||||
av_registers: config.arm_av_registers,
|
|
||||||
r9_use: match config.arm_r9_usage {
|
|
||||||
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
|
|
||||||
ArmR9Usage::Sb => unarm::R9Use::Pid,
|
|
||||||
ArmR9Usage::Tr => unarm::R9Use::Tls,
|
|
||||||
},
|
|
||||||
explicit_stack_limit: config.arm_sl_usage,
|
|
||||||
frame_pointer: config.arm_fp_usage,
|
|
||||||
ip: config.arm_ip_usage,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some((address, ins, parsed_ins)) = parser.next() {
|
|
||||||
if let Some(next) = next_mapping {
|
if let Some(next) = next_mapping {
|
||||||
let next_address = parser.address;
|
let next_address = parser.address;
|
||||||
if next_address >= next.address {
|
if next_address >= next.address {
|
||||||
@@ -175,82 +208,91 @@ impl ObjArch for ObjArchArm {
|
|||||||
next_mapping = mappings_iter.next();
|
next_mapping = mappings_iter.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
|
let (opcode, branch_dest) = match ins {
|
||||||
|
unarm::Ins::Arm(x) => {
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
|
let opcode = x.op as u16 | (1 << 15);
|
||||||
|
let branch_dest = match x.op {
|
||||||
let mut reloc_arg = None;
|
arm::Opcode::B | arm::Opcode::Bl => {
|
||||||
if let Some(reloc) = &reloc {
|
address.checked_add_signed(x.field_branch_offset())
|
||||||
match reloc.flags {
|
|
||||||
// Calls
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
|
||||||
reloc_arg = parsed_ins
|
|
||||||
.args
|
|
||||||
.iter()
|
|
||||||
.rposition(|a| matches!(a, Argument::BranchDest(_)));
|
|
||||||
}
|
|
||||||
// Data
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
|
||||||
reloc_arg =
|
|
||||||
parsed_ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
arm::Opcode::BlxI => address.checked_add_signed(x.field_blx_offset()),
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
|
(opcode, branch_dest)
|
||||||
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
|
}
|
||||||
(vec![ObjInsArg::Reloc], None)
|
unarm::Ins::Thumb(x) => {
|
||||||
} else {
|
let opcode = x.op as u16;
|
||||||
push_args(&parsed_ins, config, reloc_arg, address, display_options)?
|
let branch_dest = match x.op {
|
||||||
|
thumb::Opcode::B | thumb::Opcode::Bl => {
|
||||||
|
address.checked_add_signed(x.field_branch_offset_8())
|
||||||
|
}
|
||||||
|
thumb::Opcode::BLong => {
|
||||||
|
address.checked_add_signed(x.field_branch_offset_11())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
|
(opcode, branch_dest)
|
||||||
ops.push(ins.opcode_id());
|
}
|
||||||
insts.push(ObjIns {
|
unarm::Ins::Data => (u16::MAX, None),
|
||||||
address: address as u64,
|
};
|
||||||
size: (parser.address - address) as u8,
|
ops.push(ScannedInstruction {
|
||||||
op: ins.opcode_id(),
|
ins_ref: InstructionRef { address: address as u64, size: size as u8, opcode },
|
||||||
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
|
branch_dest: branch_dest.map(|x| x as u64),
|
||||||
args,
|
|
||||||
reloc,
|
|
||||||
branch_dest,
|
|
||||||
line,
|
|
||||||
formatted: parsed_ins.display(display_options).to_string(),
|
|
||||||
orig: None,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
Ok(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (ins, parsed_ins) = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
|
||||||
|
cb(InstructionPart::opcode(parsed_ins.mnemonic, resolved.ins_ref.opcode))?;
|
||||||
|
if ins == unarm::Ins::Data && resolved.relocation.is_some() {
|
||||||
|
cb(InstructionPart::reloc())?;
|
||||||
|
} else {
|
||||||
|
push_args(
|
||||||
|
&parsed_ins,
|
||||||
|
resolved.relocation,
|
||||||
|
resolved.ins_ref.address as u32,
|
||||||
|
self.display_options(diff_config),
|
||||||
|
cb,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
_file: &File<'_>,
|
_file: &object::File<'_>,
|
||||||
section: &ObjSection,
|
section: &object::Section,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
_relocation: &object::Relocation,
|
||||||
|
flags: RelocationFlags,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
|
let section_data = section.data()?;
|
||||||
let address = address as usize;
|
let address = address as usize;
|
||||||
Ok(match reloc.flags() {
|
Ok(match flags {
|
||||||
// ARM calls
|
// ARM calls
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
RelocationFlags::Elf(elf::R_ARM_PC24)
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
| RelocationFlags::Elf(elf::R_ARM_XPC25)
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
|
||||||
let data = section.data[address..address + 4].try_into()?;
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
let addend = self.endianness.read_i32_bytes(data);
|
let addend = self.endianness.read_i32_bytes(data);
|
||||||
let imm24 = addend & 0xffffff;
|
let imm24 = addend & 0xffffff;
|
||||||
(imm24 << 2) << 8 >> 8
|
(imm24 << 2) << 8 >> 8
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumb calls
|
// Thumb calls
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
RelocationFlags::Elf(elf::R_ARM_THM_PC22)
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => {
|
| RelocationFlags::Elf(elf::R_ARM_THM_XPC22) => {
|
||||||
let data = section.data[address..address + 2].try_into()?;
|
let data = section_data[address..address + 2].try_into()?;
|
||||||
let high = self.endianness.read_i16_bytes(data) as i32;
|
let high = self.endianness.read_i16_bytes(data) as i32;
|
||||||
let data = section.data[address + 2..address + 4].try_into()?;
|
let data = section_data[address + 2..address + 4].try_into()?;
|
||||||
let low = self.endianness.read_i16_bytes(data) as i32;
|
let low = self.endianness.read_i16_bytes(data) as i32;
|
||||||
|
|
||||||
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
||||||
@@ -258,8 +300,8 @@ impl ObjArch for ObjArchArm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
|
||||||
let data = section.data[address..address + 4].try_into()?;
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
self.endianness.read_i32_bytes(data)
|
self.endianness.read_i32_bytes(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,51 +315,98 @@ impl ObjArch for ObjArchArm {
|
|||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
Cow::Owned(format!("<{flags:?}>"))
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_ARM_NONE => Some("R_ARM_NONE"),
|
||||||
|
elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
|
||||||
|
elf::R_ARM_REL32 => Some("R_ARM_REL32"),
|
||||||
|
elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
|
||||||
|
elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
|
||||||
|
elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
|
||||||
|
elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
|
||||||
|
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
|
||||||
|
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
|
||||||
|
elf::R_ARM_CALL => Some("R_ARM_CALL"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_ARM_NONE => 0,
|
||||||
|
elf::R_ARM_ABS32 => 4,
|
||||||
|
elf::R_ARM_REL32 => 4,
|
||||||
|
elf::R_ARM_ABS16 => 2,
|
||||||
|
elf::R_ARM_ABS8 => 1,
|
||||||
|
elf::R_ARM_THM_PC22 => 4,
|
||||||
|
elf::R_ARM_THM_XPC22 => 4,
|
||||||
|
elf::R_ARM_PC24 => 4,
|
||||||
|
elf::R_ARM_XPC25 => 4,
|
||||||
|
elf::R_ARM_CALL => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
|
||||||
|
if kind == SymbolKind::Function { address & !1 } else { address }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
|
let mut flags = SymbolFlagSet::default();
|
||||||
|
if DisasmMode::from_symbol(symbol).is_some() {
|
||||||
|
flags |= SymbolFlag::Hidden;
|
||||||
|
}
|
||||||
|
flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct DisasmMode {
|
struct DisasmMode {
|
||||||
address: u32,
|
address: u32,
|
||||||
mapping: ParseMode,
|
mapping: unarm::ParseMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisasmMode {
|
impl DisasmMode {
|
||||||
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
|
fn from_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
|
||||||
if let Ok(name) = sym.name() {
|
sym.name()
|
||||||
ParseMode::from_mapping_symbol(name)
|
.ok()
|
||||||
|
.and_then(unarm::ParseMode::from_mapping_symbol)
|
||||||
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
|
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_args(
|
fn push_args(
|
||||||
parsed_ins: &ParsedIns,
|
parsed_ins: &unarm::ParsedIns,
|
||||||
config: &DiffObjConfig,
|
relocation: Option<ResolvedRelocation>,
|
||||||
reloc_arg: Option<usize>,
|
|
||||||
cur_addr: u32,
|
cur_addr: u32,
|
||||||
display_options: DisplayOptions,
|
display_options: unarm::DisplayOptions,
|
||||||
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
let mut args = vec![];
|
) -> Result<()> {
|
||||||
let mut branch_dest = None;
|
let reloc_arg = find_reloc_arg(parsed_ins, relocation);
|
||||||
let mut writeback = false;
|
let mut writeback = false;
|
||||||
let mut deref = false;
|
let mut deref = false;
|
||||||
for (i, arg) in parsed_ins.args_iter().enumerate() {
|
for (i, arg) in parsed_ins.args_iter().enumerate() {
|
||||||
// Emit punctuation before separator
|
// Emit punctuation before separator
|
||||||
if deref {
|
if deref {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
|
args::Argument::OffsetImm(args::OffsetImm { post_indexed: true, value: _ })
|
||||||
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
|
| args::Argument::OffsetReg(args::OffsetReg {
|
||||||
| Argument::CoOption(_) => {
|
add: _,
|
||||||
|
post_indexed: true,
|
||||||
|
reg: _,
|
||||||
|
})
|
||||||
|
| args::Argument::CoOption(_) => {
|
||||||
deref = false;
|
deref = false;
|
||||||
args.push(ObjInsArg::PlainText("]".into()));
|
arg_cb(InstructionPart::basic("]"))?;
|
||||||
if writeback {
|
if writeback {
|
||||||
writeback = false;
|
writeback = false;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -325,117 +414,143 @@ fn push_args(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
arg_cb(InstructionPart::separator())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if reloc_arg == Some(i) {
|
if reloc_arg == Some(i) {
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
} else {
|
} else {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::None => {}
|
args::Argument::None => {}
|
||||||
Argument::Reg(reg) => {
|
args::Argument::Reg(reg) => {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
deref = true;
|
deref = true;
|
||||||
args.push(ObjInsArg::PlainText("[".into()));
|
arg_cb(InstructionPart::basic("["))?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
reg.reg.display(display_options.reg_names).to_string().into(),
|
reg.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
if reg.writeback {
|
if reg.writeback {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
writeback = true;
|
writeback = true;
|
||||||
} else {
|
} else {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::RegList(reg_list) => {
|
args::Argument::RegList(reg_list) => {
|
||||||
args.push(ObjInsArg::PlainText("{".into()));
|
arg_cb(InstructionPart::basic("{"))?;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
if (reg_list.regs & (1 << i)) != 0 {
|
if (reg_list.regs & (1 << i)) != 0 {
|
||||||
if !first {
|
if !first {
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
arg_cb(InstructionPart::separator())?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
Register::parse(i)
|
args::Register::parse(i)
|
||||||
.display(display_options.reg_names)
|
.display(display_options.reg_names)
|
||||||
.to_string()
|
.to_string(),
|
||||||
.into(),
|
))?;
|
||||||
)));
|
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::PlainText("}".into()));
|
arg_cb(InstructionPart::basic("}"))?;
|
||||||
if reg_list.user_mode {
|
if reg_list.user_mode {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
|
arg_cb(InstructionPart::opaque("^"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => {
|
args::Argument::UImm(value)
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
| args::Argument::CoOpcode(value)
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
| args::Argument::SatImm(value) => {
|
||||||
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
|
arg_cb(InstructionPart::unsigned(*value))?;
|
||||||
}
|
}
|
||||||
Argument::SImm(value)
|
args::Argument::SImm(value)
|
||||||
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
|
| args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
|
arg_cb(InstructionPart::signed(*value))?;
|
||||||
}
|
}
|
||||||
Argument::BranchDest(value) => {
|
args::Argument::BranchDest(value) => {
|
||||||
let dest = cur_addr.wrapping_add_signed(*value) as u64;
|
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(*value)))?;
|
||||||
args.push(ObjInsArg::BranchDest(dest));
|
|
||||||
branch_dest = Some(dest);
|
|
||||||
}
|
}
|
||||||
Argument::CoOption(value) => {
|
args::Argument::CoOption(value) => {
|
||||||
args.push(ObjInsArg::PlainText("{".into()));
|
arg_cb(InstructionPart::basic("{"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
arg_cb(InstructionPart::unsigned(*value))?;
|
||||||
args.push(ObjInsArg::PlainText("}".into()));
|
arg_cb(InstructionPart::basic("}"))?;
|
||||||
}
|
}
|
||||||
Argument::CoprocNum(value) => {
|
args::Argument::CoprocNum(value) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
|
arg_cb(InstructionPart::opaque(format!("p{}", value)))?;
|
||||||
}
|
}
|
||||||
Argument::ShiftImm(shift) => {
|
args::Argument::ShiftImm(shift) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
|
||||||
args.push(ObjInsArg::PlainText(" #".into()));
|
arg_cb(InstructionPart::basic(" #"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
|
arg_cb(InstructionPart::unsigned(shift.imm))?;
|
||||||
}
|
}
|
||||||
Argument::ShiftReg(shift) => {
|
args::Argument::ShiftReg(shift) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
|
||||||
args.push(ObjInsArg::PlainText(" ".into()));
|
arg_cb(InstructionPart::basic(" "))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
shift.reg.display(display_options.reg_names).to_string().into(),
|
shift.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
}
|
}
|
||||||
Argument::OffsetReg(offset) => {
|
args::Argument::OffsetReg(offset) => {
|
||||||
if !offset.add {
|
if !offset.add {
|
||||||
args.push(ObjInsArg::PlainText("-".into()));
|
arg_cb(InstructionPart::basic("-"))?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
offset.reg.display(display_options.reg_names).to_string().into(),
|
offset.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
}
|
}
|
||||||
Argument::CpsrMode(mode) => {
|
args::Argument::CpsrMode(mode) => {
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
|
arg_cb(InstructionPart::unsigned(mode.mode))?;
|
||||||
if mode.writeback {
|
if mode.writeback {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::CoReg(_)
|
args::Argument::CoReg(_)
|
||||||
| Argument::StatusReg(_)
|
| args::Argument::StatusReg(_)
|
||||||
| Argument::StatusMask(_)
|
| args::Argument::StatusMask(_)
|
||||||
| Argument::Shift(_)
|
| args::Argument::Shift(_)
|
||||||
| Argument::CpsrFlags(_)
|
| args::Argument::CpsrFlags(_)
|
||||||
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
| args::Argument::Endian(_) => {
|
||||||
arg.display(display_options, None).to_string().into(),
|
arg_cb(InstructionPart::opaque(
|
||||||
))),
|
arg.display(display_options, None).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deref {
|
if deref {
|
||||||
args.push(ObjInsArg::PlainText("]".into()));
|
arg_cb(InstructionPart::basic("]"))?;
|
||||||
if writeback {
|
if writeback {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((args, branch_dest))
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_reloc_arg(
|
||||||
|
parsed_ins: &unarm::ParsedIns,
|
||||||
|
relocation: Option<ResolvedRelocation>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
match resolved.relocation.flags {
|
||||||
|
// Calls
|
||||||
|
RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_PC24)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_XPC25)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
|
||||||
|
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::BranchDest(_)))
|
||||||
|
}
|
||||||
|
// Data
|
||||||
|
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
|
||||||
|
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::UImm(_)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,31 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use object::{
|
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||||
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation,
|
use rabbitizer::{
|
||||||
RelocationFlags, RelocationTarget,
|
IsaExtension, IsaVersion, Vram,
|
||||||
|
abi::Abi,
|
||||||
|
operands::{IU16, ValuedOperand},
|
||||||
|
registers_meta::Register,
|
||||||
};
|
};
|
||||||
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::Arch,
|
||||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
|
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{
|
||||||
|
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||||
|
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
|
#[derive(Debug)]
|
||||||
|
pub struct ArchMips {
|
||||||
fn configure_rabbitizer(abi: Abi) {
|
pub endianness: object::Endianness,
|
||||||
unsafe {
|
|
||||||
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ObjArchMips {
|
|
||||||
pub endianness: Endianness,
|
|
||||||
pub abi: Abi,
|
pub abi: Abi,
|
||||||
pub instr_category: InstrCategory,
|
pub isa_extension: Option<IsaExtension>,
|
||||||
pub ri_gp_value: i32,
|
pub ri_gp_value: i32,
|
||||||
|
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EF_MIPS_ABI: u32 = 0x0000F000;
|
const EF_MIPS_ABI: u32 = 0x0000F000;
|
||||||
@@ -36,13 +36,13 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
|
|||||||
|
|
||||||
const R_MIPS15_S3: u32 = 119;
|
const R_MIPS15_S3: u32 = 119;
|
||||||
|
|
||||||
impl ObjArchMips {
|
impl ArchMips {
|
||||||
pub fn new(object: &File) -> Result<Self> {
|
pub fn new(object: &object::File) -> Result<Self> {
|
||||||
let mut abi = Abi::NUMERIC;
|
let mut abi = Abi::O32;
|
||||||
let mut instr_category = InstrCategory::CPU;
|
let mut isa_extension = None;
|
||||||
match object.flags() {
|
match object.flags() {
|
||||||
FileFlags::None => {}
|
object::FileFlags::None => {}
|
||||||
FileFlags::Elf { e_flags, .. } => {
|
object::FileFlags::Elf { e_flags, .. } => {
|
||||||
abi = match e_flags & EF_MIPS_ABI {
|
abi = match e_flags & EF_MIPS_ABI {
|
||||||
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
||||||
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
||||||
@@ -50,14 +50,14 @@ impl ObjArchMips {
|
|||||||
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
||||||
Abi::N32
|
Abi::N32
|
||||||
} else {
|
} else {
|
||||||
Abi::NUMERIC
|
Abi::O32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
instr_category = match e_flags & EF_MIPS_MACH {
|
isa_extension = match e_flags & EF_MIPS_MACH {
|
||||||
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
|
EF_MIPS_MACH_ALLEGREX => Some(IsaExtension::R4000ALLEGREX),
|
||||||
EF_MIPS_MACH_5900 => InstrCategory::R5900,
|
EF_MIPS_MACH_5900 => Some(IsaExtension::R5900EE),
|
||||||
_ => InstrCategory::CPU,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported MIPS file flags"),
|
_ => bail!("Unsupported MIPS file flags"),
|
||||||
@@ -65,176 +65,186 @@ impl ObjArchMips {
|
|||||||
|
|
||||||
// Parse the ri_gp_value stored in .reginfo to be able to correctly
|
// Parse the ri_gp_value stored in .reginfo to be able to correctly
|
||||||
// calculate R_MIPS_GPREL16 relocations later. The value is stored
|
// calculate R_MIPS_GPREL16 relocations later. The value is stored
|
||||||
// 0x14 bytes into .reginfo (on 32 bit platforms)
|
// 0x14 bytes into .reginfo (on 32-bit platforms)
|
||||||
|
let endianness = object.endianness();
|
||||||
let ri_gp_value = object
|
let ri_gp_value = object
|
||||||
.section_by_name(".reginfo")
|
.section_by_name(".reginfo")
|
||||||
.and_then(|section| section.data().ok())
|
.and_then(|section| section.data().ok())
|
||||||
.and_then(|data| data.get(0x14..0x18))
|
.and_then(|data| data.get(0x14..0x18))
|
||||||
.and_then(|s| s.try_into().ok())
|
.and_then(|s| s.try_into().ok())
|
||||||
.map(|bytes| object.endianness().read_i32_bytes(bytes))
|
.map(|bytes| endianness.read_i32_bytes(bytes))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
Ok(Self { endianness: object.endianness(), abi, instr_category, ri_gp_value })
|
// Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
|
||||||
|
// have 16-bit immediate fields, the 32-bit addend is split across the two relocations.
|
||||||
|
// R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen
|
||||||
|
// R_MIPS_HI16 addend.
|
||||||
|
// See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18
|
||||||
|
let mut paired_relocations = Vec::with_capacity(object.sections().count() + 1);
|
||||||
|
for obj_section in object.sections() {
|
||||||
|
let data = obj_section.data()?;
|
||||||
|
let mut last_hi = None;
|
||||||
|
let mut last_hi_addend = 0;
|
||||||
|
let mut addends = BTreeMap::new();
|
||||||
|
for (addr, reloc) in obj_section.relocations() {
|
||||||
|
if !reloc.has_implicit_addend() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
match reloc.flags() {
|
||||||
|
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
|
||||||
|
let code = data[addr as usize..addr as usize + 4].try_into()?;
|
||||||
|
let addend = ((endianness.read_u32_bytes(code) & 0x0000FFFF) << 16) as i32;
|
||||||
|
last_hi = Some(addr);
|
||||||
|
last_hi_addend = addend;
|
||||||
|
}
|
||||||
|
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
|
||||||
|
let code = data[addr as usize..addr as usize + 4].try_into()?;
|
||||||
|
let addend = (endianness.read_u32_bytes(code) & 0x0000FFFF) as i16 as i32;
|
||||||
|
let full_addend = (last_hi_addend + addend) as i64;
|
||||||
|
if let Some(hi_addr) = last_hi.take() {
|
||||||
|
addends.insert(hi_addr, full_addend);
|
||||||
|
}
|
||||||
|
addends.insert(addr, full_addend);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
last_hi = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let section_index = obj_section.index().0;
|
||||||
|
if section_index >= paired_relocations.len() {
|
||||||
|
paired_relocations.resize_with(section_index + 1, BTreeMap::new);
|
||||||
|
}
|
||||||
|
paired_relocations[section_index] = addends;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchMips {
|
Ok(Self { endianness, abi, isa_extension, ri_gp_value, paired_relocations })
|
||||||
fn process_code(
|
}
|
||||||
&self,
|
|
||||||
address: u64,
|
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
|
||||||
code: &[u8],
|
let isa_extension = match diff_config.mips_instr_category {
|
||||||
section_index: usize,
|
MipsInstrCategory::Auto => self.isa_extension,
|
||||||
relocations: &[ObjReloc],
|
MipsInstrCategory::Cpu => None,
|
||||||
line_info: &BTreeMap<u64, u32>,
|
MipsInstrCategory::Rsp => Some(IsaExtension::RSP),
|
||||||
config: &DiffObjConfig,
|
MipsInstrCategory::R3000gte => Some(IsaExtension::R3000GTE),
|
||||||
) -> Result<ProcessCodeResult> {
|
MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX),
|
||||||
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
|
MipsInstrCategory::R5900 => Some(IsaExtension::R5900EE),
|
||||||
configure_rabbitizer(match config.mips_abi {
|
};
|
||||||
|
match isa_extension {
|
||||||
|
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
|
||||||
|
None => rabbitizer::InstructionFlags::new_isa(IsaVersion::MIPS_III, None),
|
||||||
|
}
|
||||||
|
.with_abi(match diff_config.mips_abi {
|
||||||
MipsAbi::Auto => self.abi,
|
MipsAbi::Auto => self.abi,
|
||||||
MipsAbi::O32 => Abi::O32,
|
MipsAbi::O32 => Abi::O32,
|
||||||
MipsAbi::N32 => Abi::N32,
|
MipsAbi::N32 => Abi::N32,
|
||||||
MipsAbi::N64 => Abi::N64,
|
MipsAbi::N64 => Abi::N64,
|
||||||
});
|
})
|
||||||
let instr_category = match config.mips_instr_category {
|
}
|
||||||
MipsInstrCategory::Auto => self.instr_category,
|
|
||||||
MipsInstrCategory::Cpu => InstrCategory::CPU,
|
|
||||||
MipsInstrCategory::Rsp => InstrCategory::RSP,
|
|
||||||
MipsInstrCategory::R3000gte => InstrCategory::R3000GTE,
|
|
||||||
MipsInstrCategory::R4000allegrex => InstrCategory::R4000ALLEGREX,
|
|
||||||
MipsInstrCategory::R5900 => InstrCategory::R5900,
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_address = address;
|
fn instruction_display_flags(
|
||||||
let end_address = address + code.len() as u64;
|
&self,
|
||||||
let ins_count = code.len() / 4;
|
diff_config: &DiffObjConfig,
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
) -> rabbitizer::InstructionDisplayFlags {
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
rabbitizer::InstructionDisplayFlags::default()
|
||||||
let mut cur_addr = start_address as u32;
|
.with_unknown_instr_comment(false)
|
||||||
|
.with_use_dollar(diff_config.mips_register_prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ins_ref(
|
||||||
|
&self,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
code: &[u8],
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<rabbitizer::Instruction> {
|
||||||
|
Ok(rabbitizer::Instruction::new(
|
||||||
|
self.endianness.read_u32_bytes(code.try_into()?),
|
||||||
|
Vram::new(ins_ref.address as u32),
|
||||||
|
self.instruction_flags(diff_config),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchMips {
|
||||||
|
fn scan_instructions(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
_section_index: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<ScannedInstruction>> {
|
||||||
|
let instruction_flags = self.instruction_flags(diff_config);
|
||||||
|
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
|
||||||
|
let mut cur_addr = address as u32;
|
||||||
for chunk in code.chunks_exact(4) {
|
for chunk in code.chunks_exact(4) {
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
|
||||||
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
|
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
|
||||||
let instruction = Instruction::new(code, cur_addr, instr_category);
|
let instruction =
|
||||||
|
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
|
||||||
let formatted = instruction.disassemble(None, 0);
|
let opcode = instruction.opcode() as u16;
|
||||||
let op = instruction.unique_id as u16;
|
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
|
||||||
ops.push(op);
|
ops.push(ScannedInstruction {
|
||||||
|
ins_ref: InstructionRef { address: cur_addr as u64, size: 4, opcode },
|
||||||
let mnemonic = instruction.opcode_name();
|
|
||||||
let is_branch = instruction.is_branch();
|
|
||||||
let branch_offset = instruction.branch_offset();
|
|
||||||
let mut branch_dest = if is_branch {
|
|
||||||
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let operands = instruction.get_operands_slice();
|
|
||||||
let mut args = Vec::with_capacity(operands.len() + 1);
|
|
||||||
for (idx, op) in operands.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
match op {
|
|
||||||
OperandType::cpu_immediate
|
|
||||||
| OperandType::cpu_label
|
|
||||||
| OperandType::cpu_branch_target_label => {
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
// If the relocation target is within the current function, we can
|
|
||||||
// convert it into a relative branch target. Note that we check
|
|
||||||
// target_address > start_address instead of >= so that recursive
|
|
||||||
// tail calls are not considered branch targets.
|
|
||||||
let target_address =
|
|
||||||
reloc.target.address.checked_add_signed(reloc.addend);
|
|
||||||
if reloc.target.orig_section_index == Some(section_index)
|
|
||||||
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
|
|
||||||
{
|
|
||||||
let target_address = target_address.unwrap();
|
|
||||||
args.push(ObjInsArg::BranchDest(target_address));
|
|
||||||
branch_dest = Some(target_address);
|
|
||||||
} else {
|
|
||||||
push_reloc(&mut args, reloc)?;
|
|
||||||
branch_dest = None;
|
|
||||||
}
|
|
||||||
} else if let Some(branch_dest) = branch_dest {
|
|
||||||
args.push(ObjInsArg::BranchDest(branch_dest));
|
|
||||||
} else {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
op.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OperandType::cpu_immediate_base => {
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
push_reloc(&mut args, reloc)?;
|
|
||||||
} else {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
OperandType::cpu_immediate.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
args.push(ObjInsArg::PlainText("(".into()));
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
OperandType::cpu_rs.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
|
||||||
}
|
|
||||||
// OperandType::r5900_immediate15 => match reloc {
|
|
||||||
// Some(reloc)
|
|
||||||
// if reloc.flags == RelocationFlags::Elf { r_type: R_MIPS15_S3 } =>
|
|
||||||
// {
|
|
||||||
// push_reloc(&mut args, reloc)?;
|
|
||||||
// }
|
|
||||||
// _ => {
|
|
||||||
// args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
// op.disassemble(&instruction, None).into(),
|
|
||||||
// )));
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
_ => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
op.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
|
||||||
insts.push(ObjIns {
|
|
||||||
address: cur_addr as u64,
|
|
||||||
size: 4,
|
|
||||||
op,
|
|
||||||
mnemonic: Cow::Borrowed(mnemonic),
|
|
||||||
args,
|
|
||||||
reloc: reloc.cloned(),
|
|
||||||
branch_dest,
|
branch_dest,
|
||||||
line,
|
|
||||||
formatted,
|
|
||||||
orig: None,
|
|
||||||
});
|
});
|
||||||
cur_addr += 4;
|
cur_addr += 4;
|
||||||
}
|
}
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
Ok(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
|
||||||
|
let display_flags = self.instruction_display_flags(diff_config);
|
||||||
|
let opcode = instruction.opcode();
|
||||||
|
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
|
||||||
|
let start_address = resolved.symbol.address;
|
||||||
|
let function_range = start_address..start_address + resolved.symbol.size;
|
||||||
|
push_args(
|
||||||
|
&instruction,
|
||||||
|
resolved.relocation,
|
||||||
|
function_range,
|
||||||
|
resolved.section_index,
|
||||||
|
&display_flags,
|
||||||
|
cb,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
file: &File<'_>,
|
file: &object::File<'_>,
|
||||||
section: &ObjSection,
|
section: &object::Section,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
reloc: &object::Relocation,
|
||||||
|
flags: RelocationFlags,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
let data = section.data[address as usize..address as usize + 4].try_into()?;
|
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
|
||||||
let addend = self.endianness.read_u32_bytes(data);
|
if let RelocationFlags::Elf(elf::R_MIPS_HI16 | elf::R_MIPS_LO16) = flags {
|
||||||
Ok(match reloc.flags() {
|
if let Some(addend) = self
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_32 } => addend as i64,
|
.paired_relocations
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
|
.get(section.index().0)
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
|
.and_then(|m| m.get(&address).copied())
|
||||||
((addend & 0x0000FFFF) << 16) as i32 as i64
|
{
|
||||||
|
return Ok(addend);
|
||||||
}
|
}
|
||||||
RelocationFlags::Elf {
|
}
|
||||||
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16,
|
|
||||||
} => (addend & 0x0000FFFF) as i16 as i64,
|
let data = section.data()?;
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL } => {
|
let code = data[address as usize..address as usize + 4].try_into()?;
|
||||||
let RelocationTarget::Symbol(idx) = reloc.target() else {
|
let addend = self.endianness.read_u32_bytes(code);
|
||||||
|
Ok(match flags {
|
||||||
|
RelocationFlags::Elf(elf::R_MIPS_32) => addend as i64,
|
||||||
|
RelocationFlags::Elf(elf::R_MIPS_26) => ((addend & 0x03FFFFFF) << 2) as i64,
|
||||||
|
RelocationFlags::Elf(elf::R_MIPS_HI16) => ((addend & 0x0000FFFF) << 16) as i32 as i64,
|
||||||
|
RelocationFlags::Elf(elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16) => {
|
||||||
|
(addend & 0x0000FFFF) as i16 as i64
|
||||||
|
}
|
||||||
|
RelocationFlags::Elf(elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL) => {
|
||||||
|
let object::RelocationTarget::Symbol(idx) = reloc.target() else {
|
||||||
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
|
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
|
||||||
};
|
};
|
||||||
let sym = file.symbol_by_index(idx)?;
|
let sym = file.symbol_by_index(idx)?;
|
||||||
@@ -247,66 +257,174 @@ impl ObjArch for ObjArchMips {
|
|||||||
(addend & 0x0000FFFF) as i16 as i64
|
(addend & 0x0000FFFF) as i16 as i64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation
|
RelocationFlags::Elf(elf::R_MIPS_PC16) => 0, // PC-relative relocation
|
||||||
RelocationFlags::Elf { r_type: R_MIPS15_S3 } => ((addend & 0x001FFFC0) >> 3) as i64,
|
RelocationFlags::Elf(R_MIPS15_S3) => ((addend & 0x001FFFC0) >> 3) as i64,
|
||||||
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
|
elf::R_MIPS_NONE => Some("R_MIPS_NONE"),
|
||||||
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
|
elf::R_MIPS_16 => Some("R_MIPS_16"),
|
||||||
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
|
elf::R_MIPS_32 => Some("R_MIPS_32"),
|
||||||
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
|
elf::R_MIPS_26 => Some("R_MIPS_26"),
|
||||||
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
|
elf::R_MIPS_HI16 => Some("R_MIPS_HI16"),
|
||||||
elf::R_MIPS_LITERAL => Cow::Borrowed("R_MIPS_LITERAL"),
|
elf::R_MIPS_LO16 => Some("R_MIPS_LO16"),
|
||||||
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
|
elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"),
|
||||||
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"),
|
elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"),
|
||||||
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
|
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
|
||||||
R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"),
|
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
|
||||||
|
R_MIPS15_S3 => Some("R_MIPS15_S3"),
|
||||||
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_MIPS_16 => 2,
|
||||||
|
elf::R_MIPS_32 => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
_ => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
fn push_args(
|
||||||
|
instruction: &rabbitizer::Instruction,
|
||||||
|
relocation: Option<ResolvedRelocation>,
|
||||||
|
function_range: Range<u64>,
|
||||||
|
section_index: usize,
|
||||||
|
display_flags: &rabbitizer::InstructionDisplayFlags,
|
||||||
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let operands = instruction.valued_operands_iter();
|
||||||
|
for (idx, op) in operands.enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
arg_cb(InstructionPart::separator())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match op {
|
||||||
|
ValuedOperand::core_immediate(imm) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else {
|
||||||
|
arg_cb(match imm {
|
||||||
|
IU16::Integer(s) => InstructionPart::signed(s),
|
||||||
|
IU16::Unsigned(u) => InstructionPart::unsigned(u),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
// If the relocation target is within the current function, we can
|
||||||
|
// convert it into a relative branch target. Note that we check
|
||||||
|
// target_address > start_address instead of >= so that recursive
|
||||||
|
// tail calls are not considered branch targets.
|
||||||
|
let target_address =
|
||||||
|
resolved.symbol.address.checked_add_signed(resolved.relocation.addend);
|
||||||
|
if resolved.symbol.section == Some(section_index)
|
||||||
|
&& target_address.is_some_and(|addr| {
|
||||||
|
addr > function_range.start && addr < function_range.end
|
||||||
|
})
|
||||||
|
{
|
||||||
|
// TODO move this logic up a level
|
||||||
|
let target_address = target_address.unwrap();
|
||||||
|
arg_cb(InstructionPart::branch_dest(target_address))?;
|
||||||
|
} else {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
}
|
||||||
|
} else if let Some(branch_dest) = instruction
|
||||||
|
.get_branch_offset_generic()
|
||||||
|
.map(|o| (instruction.vram() + o).inner() as u64)
|
||||||
|
{
|
||||||
|
arg_cb(InstructionPart::branch_dest(branch_dest))?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::opaque(
|
||||||
|
op.display(instruction, display_flags, None::<&str>).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuedOperand::core_immediate_base(imm, base) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::Arg(InstructionArg::Value(match imm {
|
||||||
|
IU16::Integer(s) => InstructionArgValue::Signed(s as i64),
|
||||||
|
IU16::Unsigned(u) => InstructionArgValue::Unsigned(u as u64),
|
||||||
|
})))?;
|
||||||
|
}
|
||||||
|
arg_cb(InstructionPart::basic("("))?;
|
||||||
|
arg_cb(InstructionPart::opaque(base.either_name(
|
||||||
|
instruction.flags().abi(),
|
||||||
|
display_flags.named_gpr(),
|
||||||
|
!display_flags.use_dollar(),
|
||||||
|
)))?;
|
||||||
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
|
}
|
||||||
|
// ValuedOperand::r5900_immediate15(..) => match relocation {
|
||||||
|
// Some(resolved)
|
||||||
|
// if resolved.relocation.flags == RelocationFlags::Elf(R_MIPS15_S3) =>
|
||||||
|
// {
|
||||||
|
// push_reloc(&resolved.relocation, &mut arg_cb)?;
|
||||||
|
// }
|
||||||
|
// _ => {
|
||||||
|
// arg_cb(InstructionPart::opaque(op.disassemble(&instruction, None)))?;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
_ => {
|
||||||
|
arg_cb(InstructionPart::opaque(
|
||||||
|
op.display(instruction, display_flags, None::<&str>).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_reloc(
|
||||||
|
reloc: &Relocation,
|
||||||
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
match reloc.flags {
|
match reloc.flags {
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
elf::R_MIPS_HI16 => {
|
elf::R_MIPS_HI16 => {
|
||||||
args.push(ObjInsArg::PlainText("%hi(".into()));
|
arg_cb(InstructionPart::basic("%hi("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_LO16 => {
|
elf::R_MIPS_LO16 => {
|
||||||
args.push(ObjInsArg::PlainText("%lo(".into()));
|
arg_cb(InstructionPart::basic("%lo("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_GOT16 => {
|
elf::R_MIPS_GOT16 => {
|
||||||
args.push(ObjInsArg::PlainText("%got(".into()));
|
arg_cb(InstructionPart::basic("%got("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_CALL16 => {
|
elf::R_MIPS_CALL16 => {
|
||||||
args.push(ObjInsArg::PlainText("%call16(".into()));
|
arg_cb(InstructionPart::basic("%call16("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_GPREL16 => {
|
elf::R_MIPS_GPREL16 => {
|
||||||
args.push(ObjInsArg::PlainText("%gp_rel(".into()));
|
arg_cb(InstructionPart::basic("%gp_rel("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_32
|
elf::R_MIPS_32
|
||||||
| elf::R_MIPS_26
|
| elf::R_MIPS_26
|
||||||
| elf::R_MIPS_LITERAL
|
| elf::R_MIPS_LITERAL
|
||||||
| elf::R_MIPS_PC16
|
| elf::R_MIPS_PC16
|
||||||
| R_MIPS15_S3 => {
|
| R_MIPS15_S3 => {
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
|
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
|
||||||
|
use core::{ffi::CStr, fmt, fmt::Debug};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use byteorder::ByteOrder;
|
use object::Endian as _;
|
||||||
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::DiffObjConfig,
|
diff::{
|
||||||
obj::{ObjIns, ObjReloc, ObjSection},
|
DiffObjConfig,
|
||||||
|
display::{ContextItem, HoverItem, InstructionPart},
|
||||||
|
},
|
||||||
|
obj::{
|
||||||
|
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
|
||||||
|
ResolvedInstructionRef, ScannedInstruction, Symbol, SymbolFlagSet, SymbolKind,
|
||||||
|
},
|
||||||
util::ReallySigned,
|
util::ReallySigned,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
mod arm;
|
pub mod arm;
|
||||||
#[cfg(feature = "arm64")]
|
#[cfg(feature = "arm64")]
|
||||||
mod arm64;
|
pub mod arm64;
|
||||||
#[cfg(feature = "mips")]
|
#[cfg(feature = "mips")]
|
||||||
pub mod mips;
|
pub mod mips;
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
@@ -27,18 +33,43 @@ pub enum DataType {
|
|||||||
Int16,
|
Int16,
|
||||||
Int32,
|
Int32,
|
||||||
Int64,
|
Int64,
|
||||||
Int128,
|
|
||||||
Float,
|
Float,
|
||||||
Double,
|
Double,
|
||||||
Bytes,
|
Bytes,
|
||||||
String,
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DataType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DataType::Int8 => write!(f, "Int8"),
|
||||||
|
DataType::Int16 => write!(f, "Int16"),
|
||||||
|
DataType::Int32 => write!(f, "Int32"),
|
||||||
|
DataType::Int64 => write!(f, "Int64"),
|
||||||
|
DataType::Float => write!(f, "Float"),
|
||||||
|
DataType::Double => write!(f, "Double"),
|
||||||
|
DataType::Bytes => write!(f, "Bytes"),
|
||||||
|
DataType::String => write!(f, "String"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DataType {
|
impl DataType {
|
||||||
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||||
|
let mut strs = Vec::new();
|
||||||
|
for literal in self.display_literals(endian, bytes) {
|
||||||
|
strs.push(format!("{}: {}", self, literal))
|
||||||
|
}
|
||||||
|
strs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||||
|
let mut strs = Vec::new();
|
||||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||||
log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it.");
|
log::warn!(
|
||||||
return None;
|
"Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."
|
||||||
|
);
|
||||||
|
return strs;
|
||||||
}
|
}
|
||||||
let mut bytes = bytes;
|
let mut bytes = bytes;
|
||||||
if self.required_len().is_some_and(|l| bytes.len() > l) {
|
if self.required_len().is_some_and(|l| bytes.len() > l) {
|
||||||
@@ -56,58 +87,61 @@ impl DataType {
|
|||||||
match self {
|
match self {
|
||||||
DataType::Int8 => {
|
DataType::Int8 => {
|
||||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push(format!("{:#x}", i));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push(format!("{:#x}", ReallySigned(i)));
|
||||||
} else {
|
|
||||||
format!("Int8: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int16 => {
|
DataType::Int16 => {
|
||||||
let i = Endian::read_i16(bytes);
|
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push(format!("{:#x}", i));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push(format!("{:#x}", ReallySigned(i)));
|
||||||
} else {
|
|
||||||
format!("Int16: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int32 => {
|
DataType::Int32 => {
|
||||||
let i = Endian::read_i32(bytes);
|
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push(format!("{:#x}", i));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push(format!("{:#x}", ReallySigned(i)));
|
||||||
} else {
|
|
||||||
format!("Int32: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int64 => {
|
DataType::Int64 => {
|
||||||
let i = Endian::read_i64(bytes);
|
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push(format!("{:#x}", i));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push(format!("{:#x}", ReallySigned(i)));
|
||||||
} else {
|
|
||||||
format!("Int64: {:#x}", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType::Int128 => {
|
|
||||||
let i = Endian::read_i128(bytes);
|
|
||||||
if i < 0 {
|
|
||||||
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
|
|
||||||
} else {
|
|
||||||
format!("Int128: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Float => {
|
DataType::Float => {
|
||||||
format!("Float: {:?}f", Endian::read_f32(bytes))
|
let bytes: [u8; 4] = bytes.try_into().unwrap();
|
||||||
|
strs.push(format!("{:?}f", match endian {
|
||||||
|
object::Endianness::Little => f32::from_le_bytes(bytes),
|
||||||
|
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
DataType::Double => {
|
DataType::Double => {
|
||||||
format!("Double: {:?}", Endian::read_f64(bytes))
|
let bytes: [u8; 8] = bytes.try_into().unwrap();
|
||||||
|
strs.push(format!("{:?}", match endian {
|
||||||
|
object::Endianness::Little => f64::from_le_bytes(bytes),
|
||||||
|
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
DataType::Bytes => {
|
DataType::Bytes => {
|
||||||
format!("Bytes: {:#?}", bytes)
|
strs.push(format!("{:#?}", bytes));
|
||||||
}
|
}
|
||||||
DataType::String => {
|
DataType::String => {
|
||||||
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?)
|
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
|
||||||
|
strs.push(format!("{:?}", cstr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into()
|
}
|
||||||
|
|
||||||
|
strs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_len(&self) -> Option<usize> {
|
fn required_len(&self) -> Option<usize> {
|
||||||
@@ -116,7 +150,6 @@ impl DataType {
|
|||||||
DataType::Int16 => Some(2),
|
DataType::Int16 => Some(2),
|
||||||
DataType::Int32 => Some(4),
|
DataType::Int32 => Some(4),
|
||||||
DataType::Int64 => Some(8),
|
DataType::Int64 => Some(8),
|
||||||
DataType::Int128 => Some(16),
|
|
||||||
DataType::Float => Some(4),
|
DataType::Float => Some(4),
|
||||||
DataType::Double => Some(8),
|
DataType::Double => Some(8),
|
||||||
DataType::Bytes => None,
|
DataType::Bytes => None,
|
||||||
@@ -125,59 +158,173 @@ impl DataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ObjArch: Send + Sync {
|
pub trait Arch: Send + Sync + Debug {
|
||||||
fn process_code(
|
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||||
|
///
|
||||||
|
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
|
||||||
|
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
|
||||||
|
/// possible branch destination (for visual representation). As needed, instructions are parsed
|
||||||
|
/// via `process_instruction` to compare their arguments.
|
||||||
|
fn scan_instructions(
|
||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
section_index: usize,
|
section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
diff_config: &DiffObjConfig,
|
||||||
line_info: &BTreeMap<u64, u32>,
|
) -> Result<Vec<ScannedInstruction>>;
|
||||||
config: &DiffObjConfig,
|
|
||||||
) -> Result<ProcessCodeResult>;
|
/// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
|
||||||
|
///
|
||||||
|
/// This is called only when we need to compare the arguments of an instruction.
|
||||||
|
fn process_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<ParsedInstruction> {
|
||||||
|
let mut mnemonic = None;
|
||||||
|
let mut args = Vec::with_capacity(8);
|
||||||
|
self.display_instruction(resolved, diff_config, &mut |part| {
|
||||||
|
match part {
|
||||||
|
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
|
||||||
|
InstructionPart::Arg(arg) => args.push(arg.into_static()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
// If the instruction has a relocation, but we didn't format it in the display, add it to
|
||||||
|
// the end of the arguments list.
|
||||||
|
if resolved.relocation.is_some() && !args.contains(&InstructionArg::Reloc) {
|
||||||
|
args.push(InstructionArg::Reloc);
|
||||||
|
}
|
||||||
|
Ok(ParsedInstruction {
|
||||||
|
ins_ref: resolved.ins_ref,
|
||||||
|
mnemonic: mnemonic.unwrap_or_default(),
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format an instruction for display.
|
||||||
|
///
|
||||||
|
/// Implementations should call the callback for each part of the instruction: usually the
|
||||||
|
/// mnemonic and arguments, plus any separators and visual formatting.
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Generate a list of fake relocations from the given code that represent pooled data accesses.
|
||||||
|
fn generate_pooled_relocations(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
_code: &[u8],
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
_symbols: &[Symbol],
|
||||||
|
) -> Vec<Relocation> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
file: &File<'_>,
|
file: &object::File<'_>,
|
||||||
section: &ObjSection,
|
section: &object::Section,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
relocation: &object::Relocation,
|
||||||
|
flags: RelocationFlags,
|
||||||
) -> Result<i64>;
|
) -> Result<i64>;
|
||||||
|
|
||||||
fn demangle(&self, _name: &str) -> Option<String> { None }
|
fn demangle(&self, _name: &str) -> Option<String> { None }
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
|
||||||
|
|
||||||
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
|
||||||
|
|
||||||
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
|
fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
|
||||||
|
|
||||||
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
|
fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
Some(format!("Bytes: {:#x?}", bytes))
|
SymbolFlagSet::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downcast methods
|
fn guess_data_type(&self, _resolved: ResolvedInstructionRef) -> Option<DataType> { None }
|
||||||
#[cfg(feature = "ppc")]
|
|
||||||
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
|
fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
|
||||||
|
|
||||||
|
fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec<ContextItem> { Vec::new() }
|
||||||
|
|
||||||
|
fn instruction_hover(
|
||||||
|
&self,
|
||||||
|
_obj: &Object,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessCodeResult {
|
fn instruction_context(
|
||||||
pub ops: Vec<u16>,
|
&self,
|
||||||
pub insts: Vec<ObjIns>,
|
_obj: &Object,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
|
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
|
||||||
|
use object::Object as _;
|
||||||
Ok(match object.architecture() {
|
Ok(match object.architecture() {
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?),
|
object::Architecture::PowerPc => Box::new(ppc::ArchPpc::new(object)?),
|
||||||
#[cfg(feature = "mips")]
|
#[cfg(feature = "mips")]
|
||||||
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
|
object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
|
||||||
#[cfg(feature = "x86")]
|
#[cfg(feature = "x86")]
|
||||||
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
|
object::Architecture::I386 | object::Architecture::X86_64 => {
|
||||||
|
Box::new(x86::ArchX86::new(object)?)
|
||||||
|
}
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
|
object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
|
||||||
#[cfg(feature = "arm64")]
|
#[cfg(feature = "arm64")]
|
||||||
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
|
object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
|
||||||
arch => bail!("Unsupported architecture: {arch:?}"),
|
arch => bail!("Unsupported architecture: {arch:?}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ArchDummy {}
|
||||||
|
|
||||||
|
impl ArchDummy {
|
||||||
|
pub fn new() -> Box<Self> { Box::new(Self {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchDummy {
|
||||||
|
fn scan_instructions(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
_code: &[u8],
|
||||||
|
_section_index: usize,
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<ScannedInstruction>> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
_cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implcit_addend(
|
||||||
|
&self,
|
||||||
|
_file: &object::File<'_>,
|
||||||
|
_section: &object::Section,
|
||||||
|
_address: u64,
|
||||||
|
_relocation: &object::Relocation,
|
||||||
|
_flags: RelocationFlags,
|
||||||
|
) -> Result<i64> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,256 +1,362 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
borrow::Cow,
|
collections::{BTreeMap, BTreeSet},
|
||||||
collections::{BTreeMap, HashMap},
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Result};
|
use anyhow::{Result, bail, ensure};
|
||||||
use byteorder::BigEndian;
|
use cwextab::{ExceptionTableData, decode_extab};
|
||||||
use cwextab::{decode_extab, ExceptionTableData};
|
use flagset::Flags;
|
||||||
use object::{
|
use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
|
|
||||||
Symbol, SymbolKind,
|
|
||||||
};
|
|
||||||
use ppc750cl::{Argument, InsIter, Opcode, ParsedIns, GPR};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{DataType, ObjArch, ProcessCodeResult},
|
arch::{Arch, DataType},
|
||||||
diff::DiffObjConfig,
|
diff::{
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
|
DiffObjConfig,
|
||||||
|
data::resolve_relocation,
|
||||||
|
display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
|
||||||
|
},
|
||||||
|
obj::{
|
||||||
|
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
|
||||||
|
ResolvedRelocation, ScannedInstruction, Symbol, SymbolFlag, SymbolFlagSet,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Relative relocation, can be Simm, Offset or BranchDest
|
// Relative relocation, can be Simm, Offset or BranchDest
|
||||||
fn is_relative_arg(arg: &Argument) -> bool {
|
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
|
||||||
matches!(arg, Argument::Simm(_) | Argument::Offset(_) | Argument::BranchDest(_))
|
matches!(
|
||||||
|
arg,
|
||||||
|
ppc750cl::Argument::Simm(_)
|
||||||
|
| ppc750cl::Argument::Offset(_)
|
||||||
|
| ppc750cl::Argument::BranchDest(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relative or absolute relocation, can be Uimm, Simm or Offset
|
// Relative or absolute relocation, can be Uimm, Simm or Offset
|
||||||
fn is_rel_abs_arg(arg: &Argument) -> bool {
|
fn is_rel_abs_arg(arg: &ppc750cl::Argument) -> bool {
|
||||||
matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_))
|
matches!(
|
||||||
|
arg,
|
||||||
|
ppc750cl::Argument::Uimm(_) | ppc750cl::Argument::Simm(_) | ppc750cl::Argument::Offset(_)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
|
fn is_offset_arg(arg: &ppc750cl::Argument) -> bool { matches!(arg, ppc750cl::Argument::Offset(_)) }
|
||||||
|
|
||||||
pub struct ObjArchPpc {
|
#[derive(Debug)]
|
||||||
|
pub struct ArchPpc {
|
||||||
/// Exception info
|
/// Exception info
|
||||||
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
|
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArchPpc {
|
impl ArchPpc {
|
||||||
pub fn new(file: &File) -> Result<Self> { Ok(Self { extab: decode_exception_info(file)? }) }
|
pub fn new(file: &object::File) -> Result<Self> {
|
||||||
|
Ok(Self { extab: decode_exception_info(file)? })
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchPpc {
|
fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result<ppc750cl::Ins> {
|
||||||
fn process_code(
|
let mut code = u32::from_be_bytes(resolved.code.try_into()?);
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
code = zero_reloc(code, reloc.relocation);
|
||||||
|
}
|
||||||
|
let op = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
|
||||||
|
Ok(ppc750cl::Ins { code, op })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_reloc_arg(
|
||||||
|
&self,
|
||||||
|
ins: &ppc750cl::ParsedIns,
|
||||||
|
resolved: Option<ResolvedRelocation>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
match resolved?.relocation.flags {
|
||||||
|
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => Some(1),
|
||||||
|
RelocationFlags::Elf(elf::R_PPC_REL24 | elf::R_PPC_REL14) => {
|
||||||
|
ins.args.iter().rposition(is_relative_arg)
|
||||||
|
}
|
||||||
|
RelocationFlags::Elf(
|
||||||
|
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
||||||
|
) => ins.args.iter().rposition(is_rel_abs_arg),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchPpc {
|
||||||
|
fn scan_instructions(
|
||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
_section_index: usize,
|
_section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
_diff_config: &DiffObjConfig,
|
||||||
line_info: &BTreeMap<u64, u32>,
|
) -> Result<Vec<ScannedInstruction>> {
|
||||||
config: &DiffObjConfig,
|
ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
|
||||||
) -> Result<ProcessCodeResult> {
|
|
||||||
let ins_count = code.len() / 4;
|
let ins_count = code.len() / 4;
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
let mut insts = Vec::<ScannedInstruction>::with_capacity(ins_count);
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
for (cur_addr, ins) in ppc750cl::InsIter::new(code, address as u32) {
|
||||||
let fake_pool_reloc_for_addr =
|
insts.push(ScannedInstruction {
|
||||||
generate_fake_pool_reloc_for_addr_mapping(address, code, relocations);
|
ins_ref: InstructionRef {
|
||||||
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
|
address: cur_addr as u64,
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
size: 4,
|
||||||
if let Some(reloc) = reloc {
|
opcode: u8::from(ins.op) as u16,
|
||||||
// Zero out relocations
|
},
|
||||||
ins.code = match reloc.flags {
|
branch_dest: ins.branch_dest(cur_addr).map(u64::from),
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => ins.code & !0x1FFFFF,
|
});
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 } => ins.code & !0x3FFFFFC,
|
}
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL14 } => ins.code & !0xFFFC,
|
Ok(insts)
|
||||||
RelocationFlags::Elf {
|
|
||||||
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
|
||||||
} => ins.code & !0xFFFF,
|
|
||||||
_ => ins.code,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let orig = ins.basic().to_string();
|
fn display_instruction(
|
||||||
let simplified = ins.simplified();
|
&self,
|
||||||
let formatted = simplified.to_string();
|
resolved: ResolvedInstructionRef,
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ins = self.parse_ins_ref(resolved)?.simplified();
|
||||||
|
|
||||||
let mut reloc_arg = None;
|
cb(InstructionPart::opcode(ins.mnemonic, resolved.ins_ref.opcode))?;
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
match reloc.flags {
|
let reloc_arg = self.find_reloc_arg(&ins, resolved.relocation);
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => {
|
|
||||||
reloc_arg = Some(1);
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 | elf::R_PPC_REL14 } => {
|
|
||||||
reloc_arg = simplified.args.iter().rposition(is_relative_arg);
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf {
|
|
||||||
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
|
||||||
} => {
|
|
||||||
reloc_arg = simplified.args.iter().rposition(is_rel_abs_arg);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args = vec![];
|
|
||||||
let mut branch_dest = None;
|
|
||||||
let mut writing_offset = false;
|
let mut writing_offset = false;
|
||||||
for (idx, arg) in simplified.args_iter().enumerate() {
|
for (idx, arg) in ins.args_iter().enumerate() {
|
||||||
if idx > 0 && !writing_offset {
|
if idx > 0 && !writing_offset {
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
cb(InstructionPart::separator())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if reloc_arg == Some(idx) {
|
if reloc_arg == Some(idx) {
|
||||||
let reloc = reloc.unwrap();
|
let reloc = resolved.relocation.unwrap();
|
||||||
push_reloc(&mut args, reloc)?;
|
display_reloc(reloc, cb)?;
|
||||||
// For @sda21, we can omit the register argument
|
// For @sda21, we can omit the register argument
|
||||||
if matches!(reloc.flags, RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 })
|
if matches!(reloc.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21))
|
||||||
// Sanity check: the next argument should be r0
|
// Sanity check: the next argument should be r0
|
||||||
&& matches!(simplified.args.get(idx + 1), Some(Argument::GPR(GPR(0))))
|
&& matches!(ins.args.get(idx + 1), Some(ppc750cl::Argument::GPR(ppc750cl::GPR(0))))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::Simm(simm) => {
|
ppc750cl::Argument::Simm(simm) => cb(InstructionPart::signed(simm.0)),
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0 as i64)));
|
ppc750cl::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)),
|
||||||
}
|
ppc750cl::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)),
|
||||||
Argument::Uimm(uimm) => {
|
ppc750cl::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest(
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0 as u64)));
|
(resolved.ins_ref.address as u32).wrapping_add_signed(dest.0),
|
||||||
}
|
)),
|
||||||
Argument::Offset(offset) => {
|
_ => cb(InstructionPart::opaque(arg.to_string())),
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(offset.0 as i64)));
|
}?;
|
||||||
}
|
|
||||||
Argument::BranchDest(dest) => {
|
|
||||||
let dest = cur_addr.wrapping_add_signed(dest.0) as u64;
|
|
||||||
args.push(ObjInsArg::BranchDest(dest));
|
|
||||||
branch_dest = Some(dest);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
arg.to_string().into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if writing_offset {
|
if writing_offset {
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
cb(InstructionPart::basic(")"))?;
|
||||||
writing_offset = false;
|
writing_offset = false;
|
||||||
}
|
}
|
||||||
if is_offset_arg(arg) {
|
if is_offset_arg(arg) {
|
||||||
args.push(ObjInsArg::PlainText("(".into()));
|
cb(InstructionPart::basic("("))?;
|
||||||
writing_offset = true;
|
writing_offset = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ops.push(ins.op as u16);
|
Ok(())
|
||||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
|
||||||
insts.push(ObjIns {
|
|
||||||
address: cur_addr as u64,
|
|
||||||
size: 4,
|
|
||||||
mnemonic: Cow::Borrowed(simplified.mnemonic),
|
|
||||||
args,
|
|
||||||
reloc: reloc.or(fake_pool_reloc_for_addr.get(&cur_addr)).cloned(),
|
|
||||||
op: ins.op as u16,
|
|
||||||
branch_dest,
|
|
||||||
line,
|
|
||||||
formatted,
|
|
||||||
orig: Some(orig),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
|
||||||
|
fn generate_pooled_relocations(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
relocations: &[Relocation],
|
||||||
|
symbols: &[Symbol],
|
||||||
|
) -> Vec<Relocation> {
|
||||||
|
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
_file: &File<'_>,
|
_file: &object::File<'_>,
|
||||||
_section: &ObjSection,
|
_section: &object::Section,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
_relocation: &object::Relocation,
|
||||||
|
flags: RelocationFlags,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, reloc.flags())
|
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
fn demangle(&self, name: &str) -> Option<String> {
|
||||||
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
|
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
elf::R_PPC_NONE => Cow::Borrowed("R_PPC_NONE"), // We use this for fake pool relocs
|
elf::R_PPC_NONE => Some("R_PPC_NONE"), // We use this for fake pool relocs
|
||||||
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
|
elf::R_PPC_ADDR16_LO => Some("R_PPC_ADDR16_LO"),
|
||||||
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
|
elf::R_PPC_ADDR16_HI => Some("R_PPC_ADDR16_HI"),
|
||||||
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
|
elf::R_PPC_ADDR16_HA => Some("R_PPC_ADDR16_HA"),
|
||||||
elf::R_PPC_EMB_SDA21 => Cow::Borrowed("R_PPC_EMB_SDA21"),
|
elf::R_PPC_EMB_SDA21 => Some("R_PPC_EMB_SDA21"),
|
||||||
elf::R_PPC_ADDR32 => Cow::Borrowed("R_PPC_ADDR32"),
|
elf::R_PPC_ADDR32 => Some("R_PPC_ADDR32"),
|
||||||
elf::R_PPC_UADDR32 => Cow::Borrowed("R_PPC_UADDR32"),
|
elf::R_PPC_UADDR32 => Some("R_PPC_UADDR32"),
|
||||||
elf::R_PPC_REL24 => Cow::Borrowed("R_PPC_REL24"),
|
elf::R_PPC_REL24 => Some("R_PPC_REL24"),
|
||||||
elf::R_PPC_REL14 => Cow::Borrowed("R_PPC_REL14"),
|
elf::R_PPC_REL14 => Some("R_PPC_REL14"),
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_PPC_ADDR32 => 4,
|
||||||
|
elf::R_PPC_UADDR32 => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
|
if self.extab.as_ref().is_some_and(|extab| extab.contains_key(&(symbol.index().0 - 1))) {
|
||||||
|
SymbolFlag::HasExtra.into()
|
||||||
|
} else {
|
||||||
|
SymbolFlag::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guess_data_type(&self, resolved: ResolvedInstructionRef) -> Option<DataType> {
|
||||||
|
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
|
||||||
return Some(DataType::String);
|
return Some(DataType::String);
|
||||||
}
|
}
|
||||||
|
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
|
||||||
|
guess_data_type_from_load_store_inst_op(opcode)
|
||||||
|
}
|
||||||
|
|
||||||
let op = Opcode::from(instruction.op as u8);
|
fn symbol_hover(&self, _obj: &Object, symbol_index: usize) -> Vec<HoverItem> {
|
||||||
if let Some(ty) = guess_data_type_from_load_store_inst_op(op) {
|
let mut out = Vec::new();
|
||||||
Some(ty)
|
if let Some(extab) = self.extab_for_symbol(symbol_index) {
|
||||||
} else if op == Opcode::Addi {
|
out.push(HoverItem::Text {
|
||||||
// Assume that any addi instruction that references a local symbol is loading a string.
|
label: "extab symbol".into(),
|
||||||
// This hack is not ideal and results in tons of false positives where it will show
|
value: extab.etb_symbol.name.clone(),
|
||||||
// garbage strings (e.g. misinterpreting arrays, float literals, etc).
|
color: HoverItemColor::Special,
|
||||||
// But not all strings are in the @stringBase pool, so the condition above that checks
|
});
|
||||||
// the target symbol name would miss some.
|
out.push(HoverItem::Text {
|
||||||
Some(DataType::String)
|
label: "extabindex symbol".into(),
|
||||||
} else {
|
value: extab.eti_symbol.name.clone(),
|
||||||
None
|
color: HoverItemColor::Special,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_context(&self, _obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if let Some(_extab) = self.extab_for_symbol(symbol_index) {
|
||||||
|
out.push(ContextItem::Navigate {
|
||||||
|
label: "Decode exception table".to_string(),
|
||||||
|
symbol_index,
|
||||||
|
kind: SymbolNavigationKind::Extab,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instruction_hover(&self, _obj: &Object, resolved: ResolvedInstructionRef) -> Vec<HoverItem> {
|
||||||
|
let Ok(ins) = self.parse_ins_ref(resolved) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let orig = ins.basic().to_string();
|
||||||
|
let simplified = ins.simplified().to_string();
|
||||||
|
let show_orig = orig != simplified;
|
||||||
|
let rlwinm_decoded = rlwinmdec::decode(&orig);
|
||||||
|
let mut out = Vec::with_capacity(2);
|
||||||
|
if show_orig {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Original".into(),
|
||||||
|
value: orig,
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(decoded) = rlwinm_decoded {
|
||||||
|
for line in decoded.lines() {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: line.to_string(),
|
||||||
|
color: HoverItemColor::Special,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instruction_context(
|
||||||
|
&self,
|
||||||
|
_obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let Ok(ins) = self.parse_ins_ref(resolved) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let orig = ins.basic().to_string();
|
||||||
|
let simplified = ins.simplified().to_string();
|
||||||
|
let show_orig = orig != simplified;
|
||||||
|
let mut out = Vec::with_capacity(2);
|
||||||
|
out.push(ContextItem::Copy { value: simplified, label: None });
|
||||||
|
if show_orig {
|
||||||
|
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
|
||||||
|
}
|
||||||
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
|
impl ArchPpc {
|
||||||
ty.display_bytes::<BigEndian>(bytes)
|
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
|
||||||
}
|
self.extab.as_ref()?.get(&symbol_index)
|
||||||
|
|
||||||
fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjArchPpc {
|
|
||||||
pub fn extab_for_symbol(&self, symbol: &ObjSymbol) -> Option<&ExceptionInfo> {
|
|
||||||
symbol.original_index.and_then(|i| self.extab.as_ref()?.get(&i))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
|
||||||
match reloc.flags {
|
match reloc.flags {
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => code & !0x1FFFFF,
|
||||||
|
RelocationFlags::Elf(elf::R_PPC_REL24) => code & !0x3FFFFFC,
|
||||||
|
RelocationFlags::Elf(elf::R_PPC_REL14) => code & !0xFFFC,
|
||||||
|
RelocationFlags::Elf(
|
||||||
|
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
||||||
|
) => code & !0xFFFF,
|
||||||
|
_ => code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_reloc(
|
||||||
|
resolved: ResolvedRelocation,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
match resolved.relocation.flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
elf::R_PPC_ADDR16_LO => {
|
elf::R_PPC_ADDR16_LO => {
|
||||||
args.push(ObjInsArg::Reloc);
|
cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText("@l".into()));
|
cb(InstructionPart::basic("@l"))?;
|
||||||
}
|
}
|
||||||
elf::R_PPC_ADDR16_HI => {
|
elf::R_PPC_ADDR16_HI => {
|
||||||
args.push(ObjInsArg::Reloc);
|
cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText("@h".into()));
|
cb(InstructionPart::basic("@h"))?;
|
||||||
}
|
}
|
||||||
elf::R_PPC_ADDR16_HA => {
|
elf::R_PPC_ADDR16_HA => {
|
||||||
args.push(ObjInsArg::Reloc);
|
cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText("@ha".into()));
|
cb(InstructionPart::basic("@ha"))?;
|
||||||
}
|
}
|
||||||
elf::R_PPC_EMB_SDA21 => {
|
elf::R_PPC_EMB_SDA21 => {
|
||||||
args.push(ObjInsArg::Reloc);
|
cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText("@sda21".into()));
|
cb(InstructionPart::basic("@sda21"))?;
|
||||||
}
|
}
|
||||||
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
|
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
|
||||||
args.push(ObjInsArg::Reloc);
|
cb(InstructionPart::reloc())?;
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
|
elf::R_PPC_NONE => {
|
||||||
|
// Fake pool relocation.
|
||||||
|
cb(InstructionPart::basic("<"))?;
|
||||||
|
cb(InstructionPart::reloc())?;
|
||||||
|
cb(InstructionPart::basic(">"))?;
|
||||||
|
}
|
||||||
|
_ => cb(InstructionPart::reloc())?,
|
||||||
},
|
},
|
||||||
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
|
_ => cb(InstructionPart::reloc())?,
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -270,7 +376,9 @@ pub struct ExceptionInfo {
|
|||||||
pub dtors: Vec<ExtabSymbolRef>,
|
pub dtors: Vec<ExtabSymbolRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
|
fn decode_exception_info(
|
||||||
|
file: &object::File<'_>,
|
||||||
|
) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
|
||||||
let Some(extab_section) = file.section_by_name("extab") else {
|
let Some(extab_section) = file.section_by_name("extab") else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
@@ -279,13 +387,14 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut result = BTreeMap::new();
|
let mut result = BTreeMap::new();
|
||||||
let extab_relocations = extab_section.relocations().collect::<BTreeMap<u64, Relocation>>();
|
let extab_relocations =
|
||||||
|
extab_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
|
||||||
let extabindex_relocations =
|
let extabindex_relocations =
|
||||||
extabindex_section.relocations().collect::<BTreeMap<u64, Relocation>>();
|
extabindex_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
|
||||||
|
|
||||||
for extabindex in file.symbols().filter(|symbol| {
|
for extabindex in file.symbols().filter(|symbol| {
|
||||||
symbol.section_index() == Some(extabindex_section.index())
|
symbol.section_index() == Some(extabindex_section.index())
|
||||||
&& symbol.kind() == SymbolKind::Data
|
&& symbol.kind() == object::SymbolKind::Data
|
||||||
}) {
|
}) {
|
||||||
if extabindex.size() != 12 {
|
if extabindex.size() != 12 {
|
||||||
log::warn!("Invalid extabindex entry size {}", extabindex.size());
|
log::warn!("Invalid extabindex entry size {}", extabindex.size());
|
||||||
@@ -346,7 +455,7 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Add the new entry to the list
|
//Add the new entry to the list
|
||||||
result.insert(extab_func.index().0, ExceptionInfo {
|
result.insert(extab_func.index().0 - 1, ExceptionInfo {
|
||||||
eti_symbol: make_symbol_ref(&extabindex)?,
|
eti_symbol: make_symbol_ref(&extabindex)?,
|
||||||
etb_symbol: make_symbol_ref(&extab)?,
|
etb_symbol: make_symbol_ref(&extab)?,
|
||||||
data,
|
data,
|
||||||
@@ -358,16 +467,16 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn relocation_symbol<'data, 'file>(
|
fn relocation_symbol<'data, 'file>(
|
||||||
file: &'file File<'data>,
|
file: &'file object::File<'data>,
|
||||||
relocation: &Relocation,
|
relocation: &object::Relocation,
|
||||||
) -> Result<Option<Symbol<'data, 'file>>> {
|
) -> Result<Option<object::Symbol<'data, 'file>>> {
|
||||||
let addend = relocation.addend();
|
let addend = relocation.addend();
|
||||||
match relocation.target() {
|
match relocation.target() {
|
||||||
RelocationTarget::Symbol(idx) => {
|
object::RelocationTarget::Symbol(idx) => {
|
||||||
ensure!(addend == 0, "Symbol relocations must have zero addend");
|
ensure!(addend == 0, "Symbol relocations must have zero addend");
|
||||||
Ok(Some(file.symbol_by_index(idx)?))
|
Ok(Some(file.symbol_by_index(idx)?))
|
||||||
}
|
}
|
||||||
RelocationTarget::Section(idx) => {
|
object::RelocationTarget::Section(idx) => {
|
||||||
ensure!(addend >= 0, "Section relocations must have non-negative addend");
|
ensure!(addend >= 0, "Section relocations must have non-negative addend");
|
||||||
let addend = addend as u64;
|
let addend = addend as u64;
|
||||||
Ok(file
|
Ok(file
|
||||||
@@ -378,13 +487,14 @@ fn relocation_symbol<'data, 'file>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
|
fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
|
||||||
let name = symbol.name()?.to_string();
|
let name = symbol.name()?.to_string();
|
||||||
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
|
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
|
||||||
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
|
Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType> {
|
fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
|
||||||
|
use ppc750cl::Opcode;
|
||||||
match inst_op {
|
match inst_op {
|
||||||
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
||||||
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
||||||
@@ -402,19 +512,26 @@ fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given an instruction, determine if it could accessing data at the address in a register.
|
struct PoolReference {
|
||||||
// If so, return the offset added to the register's address, the register containing that address,
|
addr_src_gpr: ppc750cl::GPR,
|
||||||
// and (optionally) which destination register the address is being copied into.
|
addr_offset: i16,
|
||||||
fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
addr_dst_gpr: Option<ppc750cl::GPR>,
|
||||||
opcode: Opcode,
|
}
|
||||||
simplified: &ParsedIns,
|
|
||||||
) -> Option<(i16, GPR, Option<GPR>)> {
|
// Given an instruction, check if it could be accessing pooled data at the address in a register.
|
||||||
|
// If so, return information pertaining to where the instruction is getting that address from and
|
||||||
|
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
|
||||||
|
fn get_pool_reference_for_inst(
|
||||||
|
opcode: ppc750cl::Opcode,
|
||||||
|
simplified: &ppc750cl::ParsedIns,
|
||||||
|
) -> Option<PoolReference> {
|
||||||
|
use ppc750cl::{Argument, Opcode};
|
||||||
let args = &simplified.args;
|
let args = &simplified.args;
|
||||||
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
if guess_data_type_from_load_store_inst_op(opcode).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((offset.0, addr_src_gpr, None))
|
Some(PoolReference { addr_src_gpr, addr_offset: offset.0, 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.
|
||||||
@@ -422,7 +539,7 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
|||||||
// It may be possible to show all elements by figuring out the stride of the array
|
// It may be possible to show all elements by figuring out the stride of the array
|
||||||
// from the calculations performed on the index before it's put into offset_gpr, but
|
// from the calculations performed on the index before it's put into offset_gpr, but
|
||||||
// this would be much more complicated, so it's not currently done.
|
// this would be much more complicated, so it's not currently done.
|
||||||
Some((0, addr_src_gpr, None))
|
Some(PoolReference { addr_src_gpr, addr_offset: 0, addr_dst_gpr: None })
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -440,124 +557,242 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
|||||||
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((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
|
) => Some(PoolReference {
|
||||||
|
addr_src_gpr,
|
||||||
|
addr_offset: simm.0,
|
||||||
|
addr_dst_gpr: Some(addr_dst_gpr),
|
||||||
|
}),
|
||||||
(
|
(
|
||||||
|
// `mr` or `mr.`
|
||||||
Opcode::Or,
|
Opcode::Or,
|
||||||
Argument::GPR(addr_dst_gpr),
|
Argument::GPR(addr_dst_gpr),
|
||||||
Argument::GPR(addr_src_gpr),
|
Argument::GPR(addr_src_gpr),
|
||||||
Argument::None,
|
Argument::None,
|
||||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
|
) => Some(PoolReference {
|
||||||
|
addr_src_gpr,
|
||||||
|
addr_offset: 0,
|
||||||
|
addr_dst_gpr: Some(addr_dst_gpr),
|
||||||
|
}),
|
||||||
|
(
|
||||||
|
Opcode::Add,
|
||||||
|
Argument::GPR(addr_dst_gpr),
|
||||||
|
Argument::GPR(addr_src_gpr),
|
||||||
|
Argument::GPR(_offset_gpr),
|
||||||
|
) => Some(PoolReference {
|
||||||
|
addr_src_gpr,
|
||||||
|
addr_offset: 0,
|
||||||
|
addr_dst_gpr: Some(addr_dst_gpr),
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
||||||
|
// that register to hold some other value, unrelated to pool relocation addresses.
|
||||||
|
fn clear_overwritten_gprs(ins: ppc750cl::Ins, gpr_pool_relocs: &mut BTreeMap<u8, Relocation>) {
|
||||||
|
use ppc750cl::{Argument, Arguments, Opcode};
|
||||||
|
let mut def_args = Arguments::default();
|
||||||
|
ins.parse_defs(&mut def_args);
|
||||||
|
for arg in def_args {
|
||||||
|
if let Argument::GPR(gpr) = arg {
|
||||||
|
if ins.op == Opcode::Lmw {
|
||||||
|
// `lmw` overwrites all registers from rd to r31.
|
||||||
|
// ppc750cl only returns rd itself, so we manually clear the rest of them.
|
||||||
|
for reg in gpr.0..31 {
|
||||||
|
gpr_pool_relocs.remove(®);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gpr_pool_relocs.remove(&gpr.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
||||||
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
||||||
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
||||||
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
|
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
|
||||||
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
|
// 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(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
|
fn make_fake_pool_reloc(
|
||||||
let offset_from_pool = pool_reloc.addend + offset as i64;
|
offset: i16,
|
||||||
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
|
cur_addr: u32,
|
||||||
let orig_section_index = pool_reloc.target.orig_section_index?;
|
pool_reloc: &Relocation,
|
||||||
// We also need to create a fake target symbol to go inside our fake relocation.
|
symbols: &[Symbol],
|
||||||
// This is because we don't have access to list of all symbols in this section, so we can't find
|
) -> Option<Relocation> {
|
||||||
// the real symbol yet. Instead we make a placeholder that has the correct `orig_section_index`
|
let pool_reloc = resolve_relocation(symbols, pool_reloc);
|
||||||
// and `address` fields, and then later on when this information is displayed to the user, we
|
let offset_from_pool = pool_reloc.relocation.addend + offset as i64;
|
||||||
// can find the real symbol by searching through the object's section's symbols for one that
|
let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
|
||||||
// contains this address.
|
let target_symbol;
|
||||||
let fake_target_symbol = ObjSymbol {
|
let addend;
|
||||||
name: "".to_string(),
|
if let Some(section_index) = pool_reloc.symbol.section {
|
||||||
demangled_name: None,
|
// Find the exact data symbol within the pool being accessed here based on the address.
|
||||||
address: target_address,
|
target_symbol = symbols.iter().position(|s| {
|
||||||
section_address: 0,
|
s.section == Some(section_index)
|
||||||
size: 0,
|
&& s.size > 0
|
||||||
size_known: false,
|
&& (s.address..s.address + s.size).contains(&target_address)
|
||||||
kind: Default::default(),
|
})?;
|
||||||
flags: Default::default(),
|
addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;
|
||||||
orig_section_index: Some(orig_section_index),
|
} else {
|
||||||
virtual_address: None,
|
// If the target symbol is in a different object (extern), we simply copy the pool
|
||||||
original_index: None,
|
// relocation's target. This is because it's not possible to locate the actual symbol if
|
||||||
bytes: vec![],
|
// it's extern. And doing that for external symbols would also be unnecessary, because when
|
||||||
};
|
// the compiler generates an instruction that accesses an external "pool" plus some offset,
|
||||||
// The addend is also fake because we don't know yet if the `target_address` here is the exact
|
// that won't be a normal pool that contains other symbols within it that we want to
|
||||||
// start of the symbol or if it's in the middle of it.
|
// display. It will be something like a vtable for a class with multiple inheritance (for
|
||||||
let fake_addend = 0;
|
// example, dCcD_Cyl in The Wind Waker). So just showing that vtable symbol plus an addend
|
||||||
Some(ObjReloc {
|
// to represent the offset into it works fine in this case.
|
||||||
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
|
target_symbol = pool_reloc.relocation.target_symbol;
|
||||||
|
addend = pool_reloc.relocation.addend + offset_from_pool;
|
||||||
|
}
|
||||||
|
Some(Relocation {
|
||||||
|
flags: RelocationFlags::Elf(elf::R_PPC_NONE),
|
||||||
address: cur_addr as u64,
|
address: cur_addr as u64,
|
||||||
target: fake_target_symbol,
|
target_symbol,
|
||||||
addend: fake_addend,
|
addend,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches through all instructions in a function, determining which registers have the addresses
|
// Searches through all instructions in a function, determining which registers have the addresses
|
||||||
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
||||||
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
|
// and returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
|
||||||
// simulates what that instruction's relocation would look like if data hadn't been pooled.
|
// would look like if data hadn't been pooled.
|
||||||
// Limitations: This method currently only goes through the instructions in a function in linear
|
// This method tries to follow the function's proper control flow. It keeps track of a queue of
|
||||||
// order, from start to finish. It does *not* follow any branches. This means that it could have
|
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
|
||||||
// false positives or false negatives in determining which relocation is currently loaded in which
|
// which registers hold which pool relocations at that point.
|
||||||
// register at any given point in the function, as control flow is not respected.
|
// When a conditional or unconditional branch is encountered, the destination of the branch is added
|
||||||
// There are currently no known examples of this method producing inaccurate results in reality, but
|
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
|
||||||
// if examples are found, it may be possible to update this method to also follow all branches so
|
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
|
||||||
// that it produces more accurate results.
|
// after the branch instruction.
|
||||||
fn generate_fake_pool_reloc_for_addr_mapping(
|
// Limitations: This method does not currently read switch statement jump tables.
|
||||||
address: u64,
|
// Instead, we guess that any parts of a function we missed were switch cases, and traverse them as
|
||||||
|
// if the last `bctr` before that address had branched there. This should be fairly accurate in
|
||||||
|
// practice - in testing the only instructions it seems to miss are double branches that the
|
||||||
|
// compiler generates in error which can never be reached during normal execution anyway.
|
||||||
|
// It should be possible to implement jump tables properly by reading them out of .data. But this
|
||||||
|
// will require keeping track of what value is loaded into each register so we can retrieve the jump
|
||||||
|
// table symbol when we encounter a `bctr`.
|
||||||
|
fn generate_fake_pool_relocations_for_function(
|
||||||
|
func_address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
relocations: &[ObjReloc],
|
relocations: &[Relocation],
|
||||||
) -> HashMap<u32, ObjReloc> {
|
symbols: &[Symbol],
|
||||||
let mut active_pool_relocs = HashMap::new();
|
) -> Vec<Relocation> {
|
||||||
let mut pool_reloc_for_addr = HashMap::new();
|
use ppc750cl::{Argument, InsIter, Opcode};
|
||||||
for (cur_addr, ins) in InsIter::new(code, address as u32) {
|
let mut visited_ins_addrs = BTreeSet::new();
|
||||||
let simplified = ins.simplified();
|
let mut pool_reloc_for_addr = BTreeMap::new();
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
let mut ins_iters_with_gpr_state =
|
||||||
|
vec![(InsIter::new(code, func_address as u32), BTreeMap::new())];
|
||||||
|
let mut gpr_state_at_bctr = BTreeMap::new();
|
||||||
|
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
||||||
|
for (cur_addr, ins) in ins_iter {
|
||||||
|
if visited_ins_addrs.contains(&cur_addr) {
|
||||||
|
// Avoid getting stuck in an infinite loop when following looping branches.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
visited_ins_addrs.insert(cur_addr);
|
||||||
|
|
||||||
|
let simplified = ins.simplified();
|
||||||
|
|
||||||
|
// First handle traversing the function's control flow.
|
||||||
|
let mut branch_dest = None;
|
||||||
|
for arg in simplified.args_iter() {
|
||||||
|
if let Argument::BranchDest(dest) = arg {
|
||||||
|
let dest = cur_addr.wrapping_add_signed(dest.0);
|
||||||
|
branch_dest = Some(dest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(branch_dest) = branch_dest {
|
||||||
|
if branch_dest >= func_address as u32
|
||||||
|
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||||
|
{
|
||||||
|
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||||
|
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||||
|
match ins.op {
|
||||||
|
Opcode::Bc => {
|
||||||
|
// Conditional branch.
|
||||||
|
// Add the branch destination to the queue to do later.
|
||||||
|
ins_iters_with_gpr_state.push((
|
||||||
|
InsIter::new(dest_code_slice, branch_dest),
|
||||||
|
gpr_pool_relocs.clone(),
|
||||||
|
));
|
||||||
|
// Then continue on with the current iterator.
|
||||||
|
}
|
||||||
|
Opcode::B => {
|
||||||
|
if simplified.mnemonic != "bl" {
|
||||||
|
// Unconditional branch.
|
||||||
|
// Add the branch destination to the queue.
|
||||||
|
ins_iters_with_gpr_state.push((
|
||||||
|
InsIter::new(dest_code_slice, branch_dest),
|
||||||
|
gpr_pool_relocs.clone(),
|
||||||
|
));
|
||||||
|
// Break out of the current iterator so we can do the newly added one.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Opcode::Bcctr = ins.op {
|
||||||
|
if simplified.mnemonic == "bctr" {
|
||||||
|
// Unconditional branch to count register.
|
||||||
|
// Likely a jump table.
|
||||||
|
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then handle keeping track of which GPR contains which pool relocation.
|
||||||
|
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||||
if let Some(reloc) = reloc {
|
if let Some(reloc) = reloc {
|
||||||
// This instruction has a real relocation, so it may be a pool load we want to keep
|
// This instruction has a real relocation, so it may be a pool load we want to keep
|
||||||
// track of.
|
// track of.
|
||||||
let args = &simplified.args;
|
let args = &simplified.args;
|
||||||
match (ins.op, args[0], args[1], args[2]) {
|
match (ins.op, args[0], args[1], args[2]) {
|
||||||
(
|
(
|
||||||
|
// `lis` + `addi`
|
||||||
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),
|
||||||
) => {
|
) => {
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
|
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
|
// `lis` + `ori`
|
||||||
Opcode::Ori,
|
Opcode::Ori,
|
||||||
Argument::GPR(addr_dst_gpr),
|
Argument::GPR(addr_dst_gpr),
|
||||||
Argument::GPR(_addr_src_gpr),
|
Argument::GPR(_addr_src_gpr),
|
||||||
Argument::Uimm(_uimm),
|
Argument::Uimm(_uimm),
|
||||||
) => {
|
) => {
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
|
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||||
}
|
}
|
||||||
(Opcode::B, _, _, _) => {
|
(Opcode::B, _, _, _) => {
|
||||||
if simplified.mnemonic == "bl" {
|
if simplified.mnemonic == "bl" {
|
||||||
// When encountering a function call, clear any active pool relocations from
|
// When encountering a function call, clear any active pool relocations from
|
||||||
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
|
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
|
||||||
active_pool_relocs.remove(&0);
|
gpr_pool_relocs.remove(&0);
|
||||||
for gpr in 3..12 {
|
for gpr in 3..12 {
|
||||||
active_pool_relocs.remove(&gpr);
|
gpr_pool_relocs.remove(&gpr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {
|
||||||
|
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||||
}
|
}
|
||||||
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
}
|
||||||
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins.op, &simplified) {
|
||||||
{
|
|
||||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
// 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) = active_pool_relocs.get(&addr_src_gpr.0) {
|
if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
|
||||||
if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
|
if let Some(fake_pool_reloc) =
|
||||||
|
make_fake_pool_reloc(pool_ref.addr_offset, cur_addr, pool_reloc, symbols)
|
||||||
|
{
|
||||||
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
||||||
}
|
}
|
||||||
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
if let Some(addr_dst_gpr) = pool_ref.addr_dst_gpr {
|
||||||
// If the address of the pool relocation got copied into another register, we
|
// If the address of the pool relocation got copied into another register, we
|
||||||
// need to keep track of it in that register too as future instructions may
|
// need to keep track of it in that register too as future instructions may
|
||||||
// reference the symbol indirectly via this new register, instead of the
|
// reference the symbol indirectly via this new register, instead of the
|
||||||
@@ -567,12 +802,45 @@ fn generate_fake_pool_reloc_for_addr_mapping(
|
|||||||
// 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 += offset as i64;
|
new_reloc.addend += pool_ref.addr_offset as i64;
|
||||||
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||||
|
} else {
|
||||||
|
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, if we're about to finish the outer loop and don't have any more control flow to
|
||||||
|
// follow, we check if there are any instruction addresses in this function that we missed.
|
||||||
|
// If so, and if there were any `bctr` instructions before those points in this function,
|
||||||
|
// then we try to traverse those missing spots as switch cases.
|
||||||
|
if ins_iters_with_gpr_state.is_empty() {
|
||||||
|
let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
|
||||||
|
.step_by(4)
|
||||||
|
.filter(|addr| !visited_ins_addrs.contains(addr));
|
||||||
|
for unseen_addr in unseen_addrs {
|
||||||
|
let prev_bctr_gpr_state = gpr_state_at_bctr
|
||||||
|
.iter()
|
||||||
|
.filter(|&(&addr, _)| addr < unseen_addr)
|
||||||
|
.min_by_key(|&(&addr, _)| addr)
|
||||||
|
.map(|(_, gpr_state)| gpr_state);
|
||||||
|
if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
|
||||||
|
let dest_offset_into_func = unseen_addr - func_address as u32;
|
||||||
|
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||||
|
ins_iters_with_gpr_state.push((
|
||||||
|
InsIter::new(dest_code_slice, unseen_addr),
|
||||||
|
gpr_pool_relocs.clone(),
|
||||||
|
));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pool_reloc_for_addr
|
pool_reloc_for_addr.values().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,141 +1,156 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap};
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use iced_x86::{
|
use iced_x86::{
|
||||||
Decoder, DecoderOptions, DecoratorKind, Formatter, FormatterOutput, FormatterTextKind,
|
Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
|
||||||
GasFormatter, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind,
|
Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
|
||||||
PrefixKind, Register,
|
|
||||||
};
|
};
|
||||||
use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags};
|
use object::{Endian as _, Object as _, ObjectSection as _, pe};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::Arch,
|
||||||
diff::{DiffObjConfig, X86Formatter},
|
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{InstructionRef, RelocationFlags, ResolvedInstructionRef, ScannedInstruction},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ObjArchX86 {
|
#[derive(Debug)]
|
||||||
|
pub struct ArchX86 {
|
||||||
bits: u32,
|
bits: u32,
|
||||||
endianness: Endianness,
|
endianness: object::Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArchX86 {
|
impl ArchX86 {
|
||||||
pub fn new(object: &File) -> Result<Self> {
|
pub fn new(object: &object::File) -> Result<Self> {
|
||||||
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
|
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decoder<'a>(&self, code: &'a [u8], address: u64) -> Decoder<'a> {
|
||||||
|
Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchX86 {
|
fn formatter(&self, diff_config: &DiffObjConfig) -> Box<dyn iced_x86::Formatter> {
|
||||||
fn process_code(
|
let mut formatter: Box<dyn iced_x86::Formatter> = match diff_config.x86_formatter {
|
||||||
&self,
|
|
||||||
address: u64,
|
|
||||||
code: &[u8],
|
|
||||||
_section_index: usize,
|
|
||||||
relocations: &[ObjReloc],
|
|
||||||
line_info: &BTreeMap<u64, u32>,
|
|
||||||
config: &DiffObjConfig,
|
|
||||||
) -> Result<ProcessCodeResult> {
|
|
||||||
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
|
|
||||||
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
|
|
||||||
let mut formatter: Box<dyn Formatter> = match config.x86_formatter {
|
|
||||||
X86Formatter::Intel => Box::new(IntelFormatter::new()),
|
X86Formatter::Intel => Box::new(IntelFormatter::new()),
|
||||||
X86Formatter::Gas => Box::new(GasFormatter::new()),
|
X86Formatter::Gas => Box::new(GasFormatter::new()),
|
||||||
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
|
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
|
||||||
X86Formatter::Masm => Box::new(MasmFormatter::new()),
|
X86Formatter::Masm => Box::new(MasmFormatter::new()),
|
||||||
};
|
};
|
||||||
formatter.options_mut().set_space_after_operand_separator(config.space_between_args);
|
formatter.options_mut().set_space_after_operand_separator(diff_config.space_between_args);
|
||||||
|
formatter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut output = InstructionFormatterOutput {
|
impl Arch for ArchX86 {
|
||||||
formatted: String::new(),
|
fn scan_instructions(
|
||||||
ins: ObjIns {
|
&self,
|
||||||
address: 0,
|
address: u64,
|
||||||
size: 0,
|
code: &[u8],
|
||||||
op: 0,
|
_section_index: usize,
|
||||||
mnemonic: Cow::Borrowed("<invalid>"),
|
_diff_config: &DiffObjConfig,
|
||||||
args: vec![],
|
) -> Result<Vec<ScannedInstruction>> {
|
||||||
reloc: None,
|
let mut out = Vec::with_capacity(code.len() / 2);
|
||||||
branch_dest: None,
|
let mut decoder = self.decoder(code, address);
|
||||||
line: None,
|
|
||||||
formatted: String::new(),
|
|
||||||
orig: None,
|
|
||||||
},
|
|
||||||
error: None,
|
|
||||||
ins_operands: vec![],
|
|
||||||
};
|
|
||||||
let mut instruction = Instruction::default();
|
let mut instruction = Instruction::default();
|
||||||
while decoder.can_decode() {
|
while decoder.can_decode() {
|
||||||
decoder.decode_out(&mut instruction);
|
decoder.decode_out(&mut instruction);
|
||||||
|
let branch_dest = match instruction.op0_kind() {
|
||||||
let address = instruction.ip();
|
OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
|
||||||
let op = instruction.mnemonic() as u16;
|
OpKind::NearBranch32 => Some(instruction.near_branch32() as u64),
|
||||||
let reloc = relocations
|
OpKind::NearBranch64 => Some(instruction.near_branch64()),
|
||||||
.iter()
|
_ => None,
|
||||||
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
|
|
||||||
let line = line_info.range(..=address).last().map(|(_, &b)| b);
|
|
||||||
output.ins = ObjIns {
|
|
||||||
address,
|
|
||||||
size: instruction.len() as u8,
|
|
||||||
op,
|
|
||||||
mnemonic: Cow::Borrowed("<invalid>"),
|
|
||||||
args: vec![],
|
|
||||||
reloc: reloc.cloned(),
|
|
||||||
branch_dest: None,
|
|
||||||
line,
|
|
||||||
formatted: String::new(),
|
|
||||||
orig: None,
|
|
||||||
};
|
};
|
||||||
// Run the formatter, which will populate output.ins
|
out.push(ScannedInstruction {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: instruction.ip(),
|
||||||
|
size: instruction.len() as u8,
|
||||||
|
opcode: instruction.mnemonic() as u16,
|
||||||
|
},
|
||||||
|
branch_dest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut decoder = self.decoder(resolved.code, resolved.ins_ref.address);
|
||||||
|
let mut formatter = self.formatter(diff_config);
|
||||||
|
let mut instruction = Instruction::default();
|
||||||
|
decoder.decode_out(&mut instruction);
|
||||||
|
|
||||||
|
// Determine where to insert relocation in instruction output.
|
||||||
|
// We replace the immediate or displacement with a placeholder value since the formatter
|
||||||
|
// doesn't provide enough information to know which number is the displacement inside a
|
||||||
|
// memory operand.
|
||||||
|
let mut reloc_replace = None;
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll.
|
||||||
|
// guaranteed to be random.
|
||||||
|
let reloc_offset = reloc.relocation.address - resolved.ins_ref.address;
|
||||||
|
let reloc_size = reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX);
|
||||||
|
let offsets = decoder.get_constant_offsets(&instruction);
|
||||||
|
if reloc_offset == offsets.displacement_offset() as u64
|
||||||
|
&& reloc_size == offsets.displacement_size()
|
||||||
|
{
|
||||||
|
instruction.set_memory_displacement64(PLACEHOLDER);
|
||||||
|
// Formatter always writes the displacement as Int32
|
||||||
|
reloc_replace = Some((OpKind::Memory, NumberKind::Int32, PLACEHOLDER));
|
||||||
|
} else if reloc_offset == offsets.immediate_offset() as u64
|
||||||
|
&& reloc_size == offsets.immediate_size()
|
||||||
|
{
|
||||||
|
let is_branch = matches!(
|
||||||
|
instruction.op0_kind(),
|
||||||
|
OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64
|
||||||
|
);
|
||||||
|
let op_kind = if is_branch {
|
||||||
|
instruction.op0_kind()
|
||||||
|
} else {
|
||||||
|
match reloc_size {
|
||||||
|
2 => OpKind::Immediate16,
|
||||||
|
4 => OpKind::Immediate32,
|
||||||
|
8 => OpKind::Immediate64,
|
||||||
|
_ => OpKind::default(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let number_kind = match reloc_size {
|
||||||
|
2 => NumberKind::UInt16,
|
||||||
|
4 => NumberKind::UInt32,
|
||||||
|
8 => NumberKind::UInt64,
|
||||||
|
_ => NumberKind::default(),
|
||||||
|
};
|
||||||
|
if is_branch {
|
||||||
|
instruction.set_near_branch64(PLACEHOLDER);
|
||||||
|
} else {
|
||||||
|
instruction.set_immediate32(PLACEHOLDER as u32);
|
||||||
|
}
|
||||||
|
reloc_replace = Some((op_kind, number_kind, PLACEHOLDER));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output =
|
||||||
|
InstructionFormatterOutput { cb, reloc_replace, error: None, skip_next: false };
|
||||||
formatter.format(&instruction, &mut output);
|
formatter.format(&instruction, &mut output);
|
||||||
if let Some(error) = output.error.take() {
|
if let Some(error) = output.error.take() {
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
ensure!(output.ins_operands.len() == output.ins.args.len());
|
Ok(())
|
||||||
output.ins.formatted.clone_from(&output.formatted);
|
|
||||||
|
|
||||||
// Make sure we've put the relocation somewhere in the instruction
|
|
||||||
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
|
|
||||||
let mut found = replace_arg(
|
|
||||||
OpKind::Memory,
|
|
||||||
ObjInsArg::Reloc,
|
|
||||||
&mut output.ins.args,
|
|
||||||
&instruction,
|
|
||||||
&output.ins_operands,
|
|
||||||
)?;
|
|
||||||
if !found {
|
|
||||||
found = replace_arg(
|
|
||||||
OpKind::Immediate32,
|
|
||||||
ObjInsArg::Reloc,
|
|
||||||
&mut output.ins.args,
|
|
||||||
&instruction,
|
|
||||||
&output.ins_operands,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
ensure!(found, "x86: Failed to find operand for Absolute relocation");
|
|
||||||
}
|
|
||||||
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
|
|
||||||
bail!("Failed to find relocation in instruction");
|
|
||||||
}
|
|
||||||
|
|
||||||
result.ops.push(op);
|
|
||||||
result.insts.push(output.ins.clone());
|
|
||||||
|
|
||||||
// Clear for next iteration
|
|
||||||
output.formatted.clear();
|
|
||||||
output.ins_operands.clear();
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn implcit_addend(
|
||||||
&self,
|
&self,
|
||||||
_file: &File<'_>,
|
_file: &object::File<'_>,
|
||||||
section: &ObjSection,
|
section: &object::Section,
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
_relocation: &object::Relocation,
|
||||||
|
flags: RelocationFlags,
|
||||||
) -> Result<i64> {
|
) -> Result<i64> {
|
||||||
match reloc.flags() {
|
match flags {
|
||||||
RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 } => {
|
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
|
||||||
let data = section.data[address as usize..address as usize + 4].try_into()?;
|
let data = section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||||
}
|
}
|
||||||
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
|
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
|
||||||
@@ -152,166 +167,138 @@ impl ObjArch for ObjArchX86 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
match flags {
|
match flags {
|
||||||
RelocationFlags::Coff { typ } => match typ {
|
RelocationFlags::Coff(typ) => match typ {
|
||||||
pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"),
|
pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"),
|
||||||
pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"),
|
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
_ => None,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_arg(
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize { reloc_size(flags).unwrap_or(1) }
|
||||||
from: OpKind,
|
|
||||||
to: ObjInsArg,
|
|
||||||
args: &mut [ObjInsArg],
|
|
||||||
instruction: &Instruction,
|
|
||||||
ins_operands: &[Option<u32>],
|
|
||||||
) -> Result<bool> {
|
|
||||||
let mut replace = None;
|
|
||||||
for i in 0..instruction.op_count() {
|
|
||||||
let op_kind = instruction.op_kind(i);
|
|
||||||
if op_kind == from {
|
|
||||||
replace = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(i) = replace {
|
|
||||||
for (j, arg) in args.iter_mut().enumerate() {
|
|
||||||
if ins_operands[j] == Some(i) {
|
|
||||||
*arg = to;
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InstructionFormatterOutput {
|
fn reloc_size(flags: RelocationFlags) -> Option<usize> {
|
||||||
formatted: String,
|
match flags {
|
||||||
ins: ObjIns,
|
RelocationFlags::Coff(typ) => match typ {
|
||||||
|
pe::IMAGE_REL_I386_DIR16 | pe::IMAGE_REL_I386_REL16 => Some(2),
|
||||||
|
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct InstructionFormatterOutput<'a> {
|
||||||
|
cb: &'a mut dyn FnMut(InstructionPart<'_>) -> Result<()>,
|
||||||
|
reloc_replace: Option<(OpKind, NumberKind, u64)>,
|
||||||
error: Option<anyhow::Error>,
|
error: Option<anyhow::Error>,
|
||||||
ins_operands: Vec<Option<u32>>,
|
skip_next: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstructionFormatterOutput {
|
impl InstructionFormatterOutput<'_> {
|
||||||
fn push_signed(&mut self, value: i64) {
|
fn push_signed(&mut self, mut value: i64) {
|
||||||
// The formatter writes the '-' operator and then gives us a negative value,
|
if self.error.is_some() {
|
||||||
// so convert it to a positive value to avoid double negatives
|
|
||||||
if value < 0
|
|
||||||
&& matches!(self.ins.args.last(), Some(ObjInsArg::Arg(ObjInsArgValue::Opaque(v))) if v == "-")
|
|
||||||
{
|
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value.wrapping_abs())));
|
|
||||||
} else {
|
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FormatterOutput for InstructionFormatterOutput {
|
|
||||||
fn write(&mut self, text: &str, kind: FormatterTextKind) {
|
|
||||||
self.formatted.push_str(text);
|
|
||||||
// Skip whitespace after the mnemonic
|
|
||||||
if self.ins.args.is_empty() && kind == FormatterTextKind::Text {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.ins_operands.push(None);
|
// The formatter writes the '-' operator and then gives us a negative value,
|
||||||
|
// so convert it to a positive value to avoid double negatives
|
||||||
|
if value < 0 {
|
||||||
|
value = value.wrapping_abs();
|
||||||
|
}
|
||||||
|
if let Err(e) = (self.cb)(InstructionPart::signed(value)) {
|
||||||
|
self.error = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatterOutput for InstructionFormatterOutput<'_> {
|
||||||
|
fn write(&mut self, text: &str, kind: FormatterTextKind) {
|
||||||
|
if self.error.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Skip whitespace after the mnemonic
|
||||||
|
if self.skip_next {
|
||||||
|
self.skip_next = false;
|
||||||
|
if kind == FormatterTextKind::Text && text == " " {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
match kind {
|
match kind {
|
||||||
FormatterTextKind::Text | FormatterTextKind::Punctuation => {
|
FormatterTextKind::Text | FormatterTextKind::Punctuation => {
|
||||||
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
|
if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
|
||||||
}
|
self.error = Some(e);
|
||||||
FormatterTextKind::Keyword | FormatterTextKind::Operator => {
|
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if self.error.is_none() {
|
|
||||||
self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FormatterTextKind::Prefix
|
||||||
|
| FormatterTextKind::Keyword
|
||||||
|
| FormatterTextKind::Operator => {
|
||||||
|
if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
|
||||||
|
self.error = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) {
|
fn write_mnemonic(&mut self, instruction: &Instruction, text: &str) {
|
||||||
self.formatted.push_str(text);
|
if self.error.is_some() {
|
||||||
self.ins_operands.push(None);
|
return;
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
|
|
||||||
}
|
}
|
||||||
|
if let Err(e) = (self.cb)(InstructionPart::opcode(text, instruction.mnemonic() as u16)) {
|
||||||
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
|
self.error = Some(e);
|
||||||
self.formatted.push_str(text);
|
}
|
||||||
// TODO: can iced-x86 guarantee 'static here?
|
// Skip whitespace after the mnemonic
|
||||||
self.ins.mnemonic = Cow::Owned(text.to_string());
|
self.skip_next = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_number(
|
fn write_number(
|
||||||
&mut self,
|
&mut self,
|
||||||
_instruction: &Instruction,
|
instruction: &Instruction,
|
||||||
_operand: u32,
|
_operand: u32,
|
||||||
instruction_operand: Option<u32>,
|
instruction_operand: Option<u32>,
|
||||||
text: &str,
|
_text: &str,
|
||||||
value: u64,
|
value: u64,
|
||||||
number_kind: NumberKind,
|
number_kind: NumberKind,
|
||||||
kind: FormatterTextKind,
|
kind: FormatterTextKind,
|
||||||
) {
|
) {
|
||||||
self.formatted.push_str(text);
|
if self.error.is_some() {
|
||||||
self.ins_operands.push(instruction_operand);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle relocations
|
if let (Some(operand), Some((target_op_kind, target_number_kind, target_value))) =
|
||||||
match kind {
|
(instruction_operand, self.reloc_replace)
|
||||||
FormatterTextKind::LabelAddress => {
|
{
|
||||||
if let Some(reloc) = self.ins.reloc.as_ref() {
|
if instruction.op_kind(operand) == target_op_kind
|
||||||
if matches!(reloc.flags, RelocationFlags::Coff {
|
&& number_kind == target_number_kind
|
||||||
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32
|
&& value == target_value
|
||||||
}) {
|
{
|
||||||
self.ins.args.push(ObjInsArg::Reloc);
|
if let Err(e) = (self.cb)(InstructionPart::reloc()) {
|
||||||
return;
|
self.error = Some(e);
|
||||||
} else if self.error.is_none() {
|
|
||||||
self.error = Some(anyhow!(
|
|
||||||
"x86: Unsupported LabelAddress relocation flags {:?}",
|
|
||||||
reloc.flags
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.ins.args.push(ObjInsArg::BranchDest(value));
|
|
||||||
self.ins.branch_dest = Some(value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FormatterTextKind::FunctionAddress => {
|
}
|
||||||
if let Some(reloc) = self.ins.reloc.as_ref() {
|
|
||||||
if matches!(reloc.flags, RelocationFlags::Coff {
|
if let FormatterTextKind::LabelAddress | FormatterTextKind::FunctionAddress = kind {
|
||||||
typ: pe::IMAGE_REL_I386_REL32
|
if let Err(e) = (self.cb)(InstructionPart::branch_dest(value)) {
|
||||||
}) {
|
self.error = Some(e);
|
||||||
self.ins.args.push(ObjInsArg::Reloc);
|
}
|
||||||
return;
|
return;
|
||||||
} else if self.error.is_none() {
|
|
||||||
self.error = Some(anyhow!(
|
|
||||||
"x86: Unsupported FunctionAddress relocation flags {:?}",
|
|
||||||
reloc.flags
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match number_kind {
|
match number_kind {
|
||||||
NumberKind::Int8 => {
|
NumberKind::Int8 => self.push_signed(value as i8 as i64),
|
||||||
self.push_signed(value as i8 as i64);
|
NumberKind::Int16 => self.push_signed(value as i16 as i64),
|
||||||
}
|
NumberKind::Int32 => self.push_signed(value as i32 as i64),
|
||||||
NumberKind::Int16 => {
|
NumberKind::Int64 => self.push_signed(value as i64),
|
||||||
self.push_signed(value as i16 as i64);
|
|
||||||
}
|
|
||||||
NumberKind::Int32 => {
|
|
||||||
self.push_signed(value as i32 as i64);
|
|
||||||
}
|
|
||||||
NumberKind::Int64 => {
|
|
||||||
self.push_signed(value as i64);
|
|
||||||
}
|
|
||||||
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
|
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(value)));
|
if let Err(e) = (self.cb)(InstructionPart::unsigned(value)) {
|
||||||
|
self.error = Some(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,25 +307,211 @@ impl FormatterOutput for InstructionFormatterOutput {
|
|||||||
&mut self,
|
&mut self,
|
||||||
_instruction: &Instruction,
|
_instruction: &Instruction,
|
||||||
_operand: u32,
|
_operand: u32,
|
||||||
instruction_operand: Option<u32>,
|
_instruction_operand: Option<u32>,
|
||||||
text: &str,
|
text: &str,
|
||||||
_decorator: DecoratorKind,
|
_decorator: DecoratorKind,
|
||||||
) {
|
) {
|
||||||
self.formatted.push_str(text);
|
if self.error.is_some() {
|
||||||
self.ins_operands.push(instruction_operand);
|
return;
|
||||||
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
|
}
|
||||||
|
if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
|
||||||
|
self.error = Some(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_register(
|
fn write_register(
|
||||||
&mut self,
|
&mut self,
|
||||||
_instruction: &Instruction,
|
_instruction: &Instruction,
|
||||||
_operand: u32,
|
_operand: u32,
|
||||||
instruction_operand: Option<u32>,
|
_instruction_operand: Option<u32>,
|
||||||
text: &str,
|
text: &str,
|
||||||
_register: Register,
|
_register: Register,
|
||||||
) {
|
) {
|
||||||
self.formatted.push_str(text);
|
if self.error.is_some() {
|
||||||
self.ins_operands.push(instruction_operand);
|
return;
|
||||||
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
|
}
|
||||||
|
if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
|
||||||
|
self.error = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::obj::{Relocation, ResolvedRelocation};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scan_instructions() {
|
||||||
|
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||||
|
let code = [
|
||||||
|
0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x04, 0x85, 0x00,
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
];
|
||||||
|
let scanned = arch.scan_instructions(0, &code, 0, &DiffObjConfig::default()).unwrap();
|
||||||
|
assert_eq!(scanned.len(), 2);
|
||||||
|
assert_eq!(scanned[0].ins_ref.address, 0);
|
||||||
|
assert_eq!(scanned[0].ins_ref.size, 10);
|
||||||
|
assert_eq!(scanned[0].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
|
||||||
|
assert_eq!(scanned[0].branch_dest, None);
|
||||||
|
assert_eq!(scanned[1].ins_ref.address, 10);
|
||||||
|
assert_eq!(scanned[1].ins_ref.size, 7);
|
||||||
|
assert_eq!(scanned[1].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
|
||||||
|
assert_eq!(scanned[1].branch_dest, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_instruction() {
|
||||||
|
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||||
|
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(parts, &[
|
||||||
|
InstructionPart::opcode("mov", opcode),
|
||||||
|
InstructionPart::opaque("dword"),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::opaque("ptr"),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::basic("["),
|
||||||
|
InstructionPart::opaque("ebp"),
|
||||||
|
InstructionPart::opaque("-"),
|
||||||
|
InstructionPart::signed(152i64),
|
||||||
|
InstructionPart::basic("]"),
|
||||||
|
InstructionPart::basic(","),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::unsigned(0u64),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_instruction_with_reloc_1() {
|
||||||
|
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||||
|
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
|
||||||
|
code: &code,
|
||||||
|
relocation: Some(ResolvedRelocation {
|
||||||
|
relocation: &Relocation {
|
||||||
|
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
|
||||||
|
address: 0x1234 + 6,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
symbol: &Default::default(),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(parts, &[
|
||||||
|
InstructionPart::opcode("mov", opcode),
|
||||||
|
InstructionPart::opaque("dword"),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::opaque("ptr"),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::basic("["),
|
||||||
|
InstructionPart::opaque("ebp"),
|
||||||
|
InstructionPart::opaque("-"),
|
||||||
|
InstructionPart::signed(152i64),
|
||||||
|
InstructionPart::basic("]"),
|
||||||
|
InstructionPart::basic(","),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::reloc(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_instruction_with_reloc_2() {
|
||||||
|
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||||
|
let code = [0x8b, 0x04, 0x85, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode },
|
||||||
|
code: &code,
|
||||||
|
relocation: Some(ResolvedRelocation {
|
||||||
|
relocation: &Relocation {
|
||||||
|
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
|
||||||
|
address: 0x1234 + 3,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
symbol: &Default::default(),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(parts, &[
|
||||||
|
InstructionPart::opcode("mov", opcode),
|
||||||
|
InstructionPart::opaque("eax"),
|
||||||
|
InstructionPart::basic(","),
|
||||||
|
InstructionPart::basic(" "),
|
||||||
|
InstructionPart::basic("["),
|
||||||
|
InstructionPart::opaque("eax"),
|
||||||
|
InstructionPart::opaque("*"),
|
||||||
|
InstructionPart::signed(4),
|
||||||
|
InstructionPart::opaque("+"),
|
||||||
|
InstructionPart::reloc(),
|
||||||
|
InstructionPart::basic("]"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_process_instruction_with_reloc_3() {
|
||||||
|
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||||
|
let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
|
||||||
|
let opcode = iced_x86::Mnemonic::Call as u16;
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode },
|
||||||
|
code: &code,
|
||||||
|
relocation: Some(ResolvedRelocation {
|
||||||
|
relocation: &Relocation {
|
||||||
|
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32),
|
||||||
|
address: 0x1234 + 1,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
symbol: &Default::default(),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,256 +1,242 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use crate::{
|
|
||||||
diff::{
|
use crate::{diff, obj};
|
||||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
|
||||||
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
|
|
||||||
},
|
|
||||||
obj,
|
|
||||||
obj::{
|
|
||||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
|
|
||||||
ObjSymbolFlagSet, ObjSymbolFlags,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Protobuf diff types
|
// Protobuf diff types
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
||||||
|
|
||||||
impl DiffResult {
|
impl DiffResult {
|
||||||
pub fn new(left: Option<(&ObjInfo, &ObjDiff)>, right: Option<(&ObjInfo, &ObjDiff)>) -> Self {
|
pub fn new(
|
||||||
|
_left: Option<(&obj::Object, &diff::ObjectDiff)>,
|
||||||
|
_right: Option<(&obj::Object, &diff::ObjectDiff)>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
// TODO
|
||||||
right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
// left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
||||||
|
// right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
||||||
|
left: None,
|
||||||
|
right: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectDiff {
|
// impl ObjectDiff {
|
||||||
pub fn new(obj: &ObjInfo, diff: &ObjDiff) -> Self {
|
// pub fn new(obj: &obj::Object, diff: &diff::ObjectDiff) -> Self {
|
||||||
Self {
|
// Self {
|
||||||
sections: diff
|
// sections: diff
|
||||||
.sections
|
// .sections
|
||||||
.iter()
|
// .iter()
|
||||||
.enumerate()
|
// .enumerate()
|
||||||
.map(|(i, d)| SectionDiff::new(obj, i, d))
|
// .map(|(i, d)| SectionDiff::new(obj, i, d))
|
||||||
.collect(),
|
// .collect(),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl SectionDiff {
|
// impl SectionDiff {
|
||||||
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self {
|
// pub fn new(obj: &obj::Object, section_index: usize, section_diff: &diff::SectionDiff) -> Self {
|
||||||
let section = &obj.sections[section_index];
|
// let section = &obj.sections[section_index];
|
||||||
let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect();
|
// 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();
|
// let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
|
||||||
Self {
|
// // TODO: section_diff.reloc_diff
|
||||||
name: section.name.to_string(),
|
// Self {
|
||||||
kind: SectionKind::from(section.kind) as i32,
|
// name: section.name.to_string(),
|
||||||
size: section.size,
|
// kind: SectionKind::from(section.kind) as i32,
|
||||||
address: section.address,
|
// size: section.size,
|
||||||
symbols,
|
// address: section.address,
|
||||||
data,
|
// symbols,
|
||||||
match_percent: section_diff.match_percent,
|
// data,
|
||||||
}
|
// match_percent: section_diff.match_percent,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
impl From<ObjSectionKind> for SectionKind {
|
//
|
||||||
fn from(value: ObjSectionKind) -> Self {
|
// impl From<obj::SectionKind> for SectionKind {
|
||||||
match value {
|
// fn from(value: obj::SectionKind) -> Self {
|
||||||
ObjSectionKind::Code => SectionKind::SectionText,
|
// match value {
|
||||||
ObjSectionKind::Data => SectionKind::SectionData,
|
// obj::SectionKind::Code => SectionKind::SectionText,
|
||||||
ObjSectionKind::Bss => SectionKind::SectionBss,
|
// obj::SectionKind::Data => SectionKind::SectionData,
|
||||||
// TODO common
|
// obj::SectionKind::Bss => SectionKind::SectionBss,
|
||||||
}
|
// // TODO common
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
impl From<obj::SymbolRef> for SymbolRef {
|
//
|
||||||
fn from(value: obj::SymbolRef) -> Self {
|
// impl SymbolDiff {
|
||||||
Self {
|
// pub fn new(object: &obj::Object, symbol_diff: &diff::SymbolDiff) -> Self {
|
||||||
section_index: if value.section_idx == obj::SECTION_COMMON {
|
// let symbol = object.symbols[symbol_diff.symbol_index];
|
||||||
None
|
// let instructions = symbol_diff
|
||||||
} else {
|
// .instruction_rows
|
||||||
Some(value.section_idx as u32)
|
// .iter()
|
||||||
},
|
// .map(|ins_diff| InstructionDiff::new(object, ins_diff))
|
||||||
symbol_index: value.symbol_idx as u32,
|
// .collect();
|
||||||
}
|
// Self {
|
||||||
}
|
// symbol: Some(Symbol::new(symbol)),
|
||||||
}
|
// instructions,
|
||||||
|
// match_percent: symbol_diff.match_percent,
|
||||||
impl SymbolDiff {
|
// target: symbol_diff.target_symbol.map(SymbolRef::from),
|
||||||
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self {
|
// }
|
||||||
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref);
|
// }
|
||||||
let instructions = symbol_diff
|
// }
|
||||||
.instructions
|
//
|
||||||
.iter()
|
// impl DataDiff {
|
||||||
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
|
// pub fn new(_object: &obj::Object, data_diff: &diff::DataDiff) -> Self {
|
||||||
.collect();
|
// Self {
|
||||||
Self {
|
// kind: DiffKind::from(data_diff.kind) as i32,
|
||||||
symbol: Some(Symbol::new(symbol)),
|
// data: data_diff.data.clone(),
|
||||||
instructions,
|
// size: data_diff.len as u64,
|
||||||
match_percent: symbol_diff.match_percent,
|
// }
|
||||||
target: symbol_diff.target_symbol.map(SymbolRef::from),
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
}
|
// impl Symbol {
|
||||||
|
// pub fn new(value: &ObjSymbol) -> Self {
|
||||||
impl DataDiff {
|
// Self {
|
||||||
pub fn new(_object: &ObjInfo, data_diff: &ObjDataDiff) -> Self {
|
// name: value.name.to_string(),
|
||||||
Self {
|
// demangled_name: value.demangled_name.clone(),
|
||||||
kind: DiffKind::from(data_diff.kind) as i32,
|
// address: value.address,
|
||||||
data: data_diff.data.clone(),
|
// size: value.size,
|
||||||
size: data_diff.len as u64,
|
// flags: symbol_flags(value.flags),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl Symbol {
|
// fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
||||||
pub fn new(value: &ObjSymbol) -> Self {
|
// let mut flags = 0u32;
|
||||||
Self {
|
// if value.0.contains(ObjSymbolFlags::Global) {
|
||||||
name: value.name.to_string(),
|
// flags |= SymbolFlag::SymbolGlobal as u32;
|
||||||
demangled_name: value.demangled_name.clone(),
|
// }
|
||||||
address: value.address,
|
// if value.0.contains(ObjSymbolFlags::Local) {
|
||||||
size: value.size,
|
// flags |= SymbolFlag::SymbolLocal as u32;
|
||||||
flags: symbol_flags(value.flags),
|
// }
|
||||||
}
|
// if value.0.contains(ObjSymbolFlags::Weak) {
|
||||||
}
|
// flags |= SymbolFlag::SymbolWeak as u32;
|
||||||
}
|
// }
|
||||||
|
// if value.0.contains(ObjSymbolFlags::Common) {
|
||||||
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
// flags |= SymbolFlag::SymbolCommon as u32;
|
||||||
let mut flags = 0u32;
|
// }
|
||||||
if value.0.contains(ObjSymbolFlags::Global) {
|
// if value.0.contains(ObjSymbolFlags::Hidden) {
|
||||||
flags |= SymbolFlag::SymbolGlobal as u32;
|
// flags |= SymbolFlag::SymbolHidden as u32;
|
||||||
}
|
// }
|
||||||
if value.0.contains(ObjSymbolFlags::Local) {
|
// flags
|
||||||
flags |= SymbolFlag::SymbolLocal as u32;
|
// }
|
||||||
}
|
//
|
||||||
if value.0.contains(ObjSymbolFlags::Weak) {
|
// impl Instruction {
|
||||||
flags |= SymbolFlag::SymbolWeak as u32;
|
// pub fn new(object: &obj::Object, instruction: &ObjIns) -> Self {
|
||||||
}
|
// Self {
|
||||||
if value.0.contains(ObjSymbolFlags::Common) {
|
// address: instruction.address,
|
||||||
flags |= SymbolFlag::SymbolCommon as u32;
|
// size: instruction.size as u32,
|
||||||
}
|
// opcode: instruction.op as u32,
|
||||||
if value.0.contains(ObjSymbolFlags::Hidden) {
|
// mnemonic: instruction.mnemonic.to_string(),
|
||||||
flags |= SymbolFlag::SymbolHidden as u32;
|
// formatted: instruction.formatted.clone(),
|
||||||
}
|
// arguments: instruction.args.iter().map(Argument::new).collect(),
|
||||||
flags
|
// relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
||||||
}
|
// branch_dest: instruction.branch_dest,
|
||||||
|
// line_number: instruction.line,
|
||||||
impl Instruction {
|
// original: instruction.orig.clone(),
|
||||||
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
|
// }
|
||||||
Self {
|
// }
|
||||||
address: instruction.address,
|
// }
|
||||||
size: instruction.size as u32,
|
//
|
||||||
opcode: instruction.op as u32,
|
// impl Argument {
|
||||||
mnemonic: instruction.mnemonic.to_string(),
|
// pub fn new(value: &ObjInsArg) -> Self {
|
||||||
formatted: instruction.formatted.clone(),
|
// Self {
|
||||||
arguments: instruction.args.iter().map(Argument::new).collect(),
|
// value: Some(match value {
|
||||||
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
// ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
||||||
branch_dest: instruction.branch_dest,
|
// ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
|
||||||
line_number: instruction.line,
|
// ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
||||||
original: instruction.orig.clone(),
|
// ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
||||||
}
|
// }),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
impl Argument {
|
//
|
||||||
pub fn new(value: &ObjInsArg) -> Self {
|
// impl ArgumentValue {
|
||||||
Self {
|
// pub fn new(value: &ObjInsArgValue) -> Self {
|
||||||
value: Some(match value {
|
// Self {
|
||||||
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
// value: Some(match value {
|
||||||
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
|
// ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
||||||
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
// ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v),
|
||||||
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
// ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()),
|
||||||
}),
|
// }),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl ArgumentValue {
|
// impl Relocation {
|
||||||
pub fn new(value: &ObjInsArgValue) -> Self {
|
// pub fn new(object: &obj::Object, reloc: &ObjReloc) -> Self {
|
||||||
Self {
|
// Self {
|
||||||
value: Some(match value {
|
// r#type: match reloc.flags {
|
||||||
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
// object::RelocationFlags::Elf { r_type } => r_type,
|
||||||
ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v),
|
// object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
||||||
ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()),
|
// 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 {
|
||||||
impl Relocation {
|
// symbol: Some(Symbol::new(&reloc.target)),
|
||||||
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
|
// addend: reloc.addend,
|
||||||
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,
|
// impl InstructionDiff {
|
||||||
_ => unreachable!(),
|
// pub fn new(object: &obj::Object, instruction_diff: &ObjInsDiff) -> Self {
|
||||||
},
|
// Self {
|
||||||
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
|
// instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
|
||||||
target: Some(RelocationTarget {
|
// diff_kind: DiffKind::from(instruction_diff.kind) as i32,
|
||||||
symbol: Some(Symbol::new(&reloc.target)),
|
// branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
|
||||||
addend: reloc.addend,
|
// branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
|
||||||
}),
|
// arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl InstructionDiff {
|
// impl ArgumentDiff {
|
||||||
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
|
// pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
|
||||||
Self {
|
// Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
||||||
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),
|
// impl From<ObjInsDiffKind> for DiffKind {
|
||||||
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
|
// fn from(value: ObjInsDiffKind) -> Self {
|
||||||
}
|
// match value {
|
||||||
}
|
// ObjInsDiffKind::None => DiffKind::DiffNone,
|
||||||
}
|
// ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
|
||||||
|
// ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
|
||||||
impl ArgumentDiff {
|
// ObjInsDiffKind::Replace => DiffKind::DiffReplace,
|
||||||
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
|
// ObjInsDiffKind::Delete => DiffKind::DiffDelete,
|
||||||
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
// ObjInsDiffKind::Insert => DiffKind::DiffInsert,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
impl From<ObjInsDiffKind> for DiffKind {
|
//
|
||||||
fn from(value: ObjInsDiffKind) -> Self {
|
// impl From<ObjDataDiffKind> for DiffKind {
|
||||||
match value {
|
// fn from(value: ObjDataDiffKind) -> Self {
|
||||||
ObjInsDiffKind::None => DiffKind::DiffNone,
|
// match value {
|
||||||
ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
|
// ObjDataDiffKind::None => DiffKind::DiffNone,
|
||||||
ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
|
// ObjDataDiffKind::Replace => DiffKind::DiffReplace,
|
||||||
ObjInsDiffKind::Replace => DiffKind::DiffReplace,
|
// ObjDataDiffKind::Delete => DiffKind::DiffDelete,
|
||||||
ObjInsDiffKind::Delete => DiffKind::DiffDelete,
|
// ObjDataDiffKind::Insert => DiffKind::DiffInsert,
|
||||||
ObjInsDiffKind::Insert => DiffKind::DiffInsert,
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// impl InstructionBranchFrom {
|
||||||
impl From<ObjDataDiffKind> for DiffKind {
|
// pub fn new(value: &ObjInsBranchFrom) -> Self {
|
||||||
fn from(value: ObjDataDiffKind) -> Self {
|
// Self {
|
||||||
match value {
|
// instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
||||||
ObjDataDiffKind::None => DiffKind::DiffNone,
|
// branch_index: value.branch_idx as u32,
|
||||||
ObjDataDiffKind::Replace => DiffKind::DiffReplace,
|
// }
|
||||||
ObjDataDiffKind::Delete => DiffKind::DiffDelete,
|
// }
|
||||||
ObjDataDiffKind::Insert => DiffKind::DiffInsert,
|
// }
|
||||||
}
|
//
|
||||||
}
|
// impl InstructionBranchTo {
|
||||||
}
|
// pub fn new(value: &ObjInsBranchTo) -> Self {
|
||||||
|
// Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
||||||
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,5 +1,3 @@
|
|||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub mod wasm;
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use std::ops::AddAssign;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::ops::AddAssign;
|
||||||
|
|
||||||
|
use anyhow::{Result, bail};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde_json::error::Category;
|
|
||||||
|
|
||||||
// Protobuf report types
|
// Protobuf report types
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||||
|
|
||||||
pub const REPORT_VERSION: u32 = 2;
|
pub const REPORT_VERSION: u32 = 2;
|
||||||
@@ -15,23 +21,30 @@ impl Report {
|
|||||||
/// Attempts to parse the report as binary protobuf or JSON.
|
/// Attempts to parse the report as binary protobuf or JSON.
|
||||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
bail!("Empty data");
|
||||||
}
|
}
|
||||||
let report = if data[0] == b'{' {
|
let report = if data[0] == b'{' {
|
||||||
// Load as JSON
|
// Load as JSON
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
{
|
||||||
Self::from_json(data)?
|
Self::from_json(data)?
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "serde"))]
|
||||||
|
bail!("JSON report parsing requires the `serde` feature")
|
||||||
} else {
|
} else {
|
||||||
// Load as binary protobuf
|
// Load as binary protobuf
|
||||||
Self::decode(data)?
|
Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
|
||||||
};
|
};
|
||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
match serde_json::from_slice::<Self>(bytes) {
|
match serde_json::from_slice::<Self>(bytes) {
|
||||||
Ok(report) => Ok(report),
|
Ok(report) => Ok(report),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
use serde_json::error::Category;
|
||||||
match e.classify() {
|
match e.classify() {
|
||||||
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
||||||
Category::Data => {
|
Category::Data => {
|
||||||
@@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Older JSON report types
|
// Older JSON report types
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReport {
|
struct LegacyReport {
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
total_code: u64,
|
total_code: u64,
|
||||||
@@ -341,7 +355,8 @@ impl From<LegacyReport> for Report {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportUnit {
|
struct LegacyReportUnit {
|
||||||
name: String,
|
name: String,
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
@@ -351,11 +366,11 @@ struct LegacyReportUnit {
|
|||||||
matched_data: u64,
|
matched_data: u64,
|
||||||
total_functions: u32,
|
total_functions: u32,
|
||||||
matched_functions: u32,
|
matched_functions: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
complete: Option<bool>,
|
complete: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_name: Option<String>,
|
module_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_id: Option<u32>,
|
module_id: Option<u32>,
|
||||||
sections: Vec<LegacyReportItem>,
|
sections: Vec<LegacyReportItem>,
|
||||||
functions: Vec<LegacyReportItem>,
|
functions: Vec<LegacyReportItem>,
|
||||||
@@ -389,16 +404,20 @@ impl From<LegacyReportUnit> for ReportUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportItem {
|
struct LegacyReportItem {
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
demangled_name: Option<String>,
|
demangled_name: Option<String>,
|
||||||
#[serde(
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(
|
||||||
default,
|
default,
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
serialize_with = "serialize_hex",
|
serialize_with = "serialize_hex",
|
||||||
deserialize_with = "deserialize_hex"
|
deserialize_with = "deserialize_hex"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
address: Option<u64>,
|
address: Option<u64>,
|
||||||
size: u64,
|
size: u64,
|
||||||
@@ -419,15 +438,13 @@ impl From<LegacyReportItem> for ReportItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
||||||
where S: serde::Serializer {
|
where S: serde::Serializer {
|
||||||
if let Some(x) = x {
|
if let Some(x) = x { s.serialize_str(&format!("{:#x}", x)) } else { s.serialize_none() }
|
||||||
s.serialize_str(&format!("{:#x}", x))
|
|
||||||
} else {
|
|
||||||
s.serialize_none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
||||||
where D: serde::Deserializer<'de> {
|
where D: serde::Deserializer<'de> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
use prost::Message;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
use crate::{bindings::diff::DiffResult, diff, obj};
|
|
||||||
|
|
||||||
fn parse_object(
|
|
||||||
data: Option<Box<[u8]>>,
|
|
||||||
config: &diff::DiffObjConfig,
|
|
||||||
) -> Result<Option<obj::ObjInfo>, JsError> {
|
|
||||||
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_and_run_diff(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
let target = parse_object(left, &diff_config)?;
|
|
||||||
let base = parse_object(right, &diff_config)?;
|
|
||||||
run_diff(target.as_ref(), base.as_ref(), diff_config, mapping_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_diff(
|
|
||||||
left: Option<&obj::ObjInfo>,
|
|
||||||
right: Option<&obj::ObjInfo>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
log::debug!("Running diff with config: {:?}", diff_config);
|
|
||||||
let result = diff::diff_objs(&diff_config, &mapping_config, left, right, None).to_js()?;
|
|
||||||
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
|
||||||
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
|
||||||
Ok(DiffResult::new(left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[wasm_bindgen]
|
|
||||||
// pub fn run_diff_json(
|
|
||||||
// left: Option<Box<[u8]>>,
|
|
||||||
// right: Option<Box<[u8]>>,
|
|
||||||
// config: diff::DiffObjConfig,
|
|
||||||
// ) -> Result<String, JsError> {
|
|
||||||
// let out = run_diff_opt_box(left, right, config)?;
|
|
||||||
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn run_diff_proto(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<Box<[u8]>, JsError> {
|
|
||||||
let out = parse_and_run_diff(left, right, diff_config, mapping_config)?;
|
|
||||||
Ok(out.encode_to_vec().into_boxed_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
fn start() -> Result<(), JsError> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
console_log::init_with_level(log::Level::Debug).to_js()?;
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
console_log::init_with_level(log::Level::Info).to_js()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
|
|
||||||
|
|
||||||
trait ToJsResult {
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<Self::Output, JsError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
|
|
||||||
use std::{
|
use std::process::Command;
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPath};
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BuildStatus {
|
pub struct BuildStatus {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
@@ -25,14 +24,14 @@ impl Default for BuildStatus {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BuildConfig {
|
pub struct BuildConfig {
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
|
||||||
let Some(cwd) = &config.project_dir else {
|
let Some(cwd) = &config.project_dir else {
|
||||||
return BuildStatus {
|
return BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -52,7 +51,6 @@ pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
|||||||
let mut command = {
|
let mut command = {
|
||||||
use std::os::windows::process::CommandExt;
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
use path_slash::PathExt;
|
|
||||||
let mut command = if config.selected_wsl_distro.is_some() {
|
let mut command = if config.selected_wsl_distro.is_some() {
|
||||||
Command::new("wsl")
|
Command::new("wsl")
|
||||||
} else {
|
} else {
|
||||||
@@ -62,21 +60,21 @@ pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
|||||||
// Strip distro root prefix \\wsl.localhost\{distro}
|
// Strip distro root prefix \\wsl.localhost\{distro}
|
||||||
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
||||||
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
||||||
Ok(new_cwd) => format!("/{}", new_cwd.to_slash_lossy().as_ref()),
|
Ok(new_cwd) => Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()),
|
||||||
Err(_) => cwd.to_string_lossy().to_string(),
|
Err(_) => cwd.with_unix_encoding(),
|
||||||
};
|
};
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("--cd")
|
.arg("--cd")
|
||||||
.arg(cwd)
|
.arg(cwd.as_str())
|
||||||
.arg("-d")
|
.arg("-d")
|
||||||
.arg(distro)
|
.arg(distro)
|
||||||
.arg("--")
|
.arg("--")
|
||||||
.arg(make)
|
.arg(make)
|
||||||
.args(make_args)
|
.args(make_args)
|
||||||
.arg(arg.to_slash_lossy().as_ref());
|
.arg(arg.as_str());
|
||||||
} else {
|
} else {
|
||||||
command.current_dir(cwd).args(make_args).arg(arg.to_slash_lossy().as_ref());
|
command.current_dir(cwd).args(make_args).arg(arg.as_str());
|
||||||
}
|
}
|
||||||
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
|
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
|
||||||
command
|
command
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::{
|
|||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
},
|
},
|
||||||
task::Waker,
|
task::Waker,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
@@ -11,7 +11,7 @@ use std::{
|
|||||||
|
|
||||||
use globset::GlobSet;
|
use globset::GlobSet;
|
||||||
use notify::RecursiveMode;
|
use notify::RecursiveMode;
|
||||||
use notify_debouncer_full::{new_debouncer_opt, DebounceEventResult};
|
use notify_debouncer_full::{DebounceEventResult, new_debouncer_opt};
|
||||||
|
|
||||||
pub type Watcher = notify_debouncer_full::Debouncer<
|
pub type Watcher = notify_debouncer_full::Debouncer<
|
||||||
notify::RecommendedWatcher,
|
notify::RecommendedWatcher,
|
||||||
|
|||||||
@@ -1,37 +1,47 @@
|
|||||||
use std::{
|
pub mod path;
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
fs,
|
string::{String, ToString},
|
||||||
fs::File,
|
vec::Vec,
|
||||||
io::{BufReader, BufWriter, Read},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use filetime::FileTime;
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use path::unix_path_serde_option;
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub min_version: Option<String>,
|
pub min_version: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub target_dir: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub base_dir: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub target_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_base: Option<bool>,
|
pub build_base: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_target: Option<bool>,
|
pub build_target: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub watch_patterns: Option<Vec<String>>,
|
pub watch_patterns: Option<Vec<String>>,
|
||||||
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
pub units: Option<Vec<ProjectObject>>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +49,6 @@ impl ProjectConfig {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
|
|
||||||
self.units.get_or_insert_with(Vec::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
||||||
self.progress_categories.as_deref().unwrap_or_default()
|
self.progress_categories.as_deref().unwrap_or_default()
|
||||||
@@ -66,55 +71,62 @@ impl ProjectConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub target_path: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub path: Option<Utf8UnixPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
#[cfg_attr(
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub target_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.complete")]
|
#[deprecated(note = "Use metadata.complete")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub symbol_mappings: Option<SymbolMappings>,
|
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", tsify_next::declare)]
|
#[derive(Default, Clone)]
|
||||||
pub type SymbolMappings = BTreeMap<String, String>;
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
|
||||||
pub struct ProjectObjectMetadata {
|
pub struct ProjectObjectMetadata {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub source_path: Option<String>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub source_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<String>>,
|
pub progress_categories: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub auto_generated: Option<bool>,
|
pub auto_generated: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectProgressCategory {
|
pub struct ProjectProgressCategory {
|
||||||
#[serde(default)]
|
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(default)]
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,33 +135,12 @@ impl ProjectObject {
|
|||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
name
|
name
|
||||||
} else if let Some(path) = &self.path {
|
} else if let Some(path) = &self.path {
|
||||||
path.to_str().unwrap_or("[invalid path]")
|
path.as_str()
|
||||||
} else {
|
} else {
|
||||||
"[unknown]"
|
"[unknown]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_paths(
|
|
||||||
&mut self,
|
|
||||||
project_dir: &Path,
|
|
||||||
target_obj_dir: Option<&Path>,
|
|
||||||
base_obj_dir: Option<&Path>,
|
|
||||||
) {
|
|
||||||
if let (Some(target_obj_dir), Some(path), None) =
|
|
||||||
(target_obj_dir, &self.path, &self.target_path)
|
|
||||||
{
|
|
||||||
self.target_path = Some(target_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.target_path {
|
|
||||||
self.target_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
|
|
||||||
{
|
|
||||||
self.base_path = Some(base_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.base_path {
|
|
||||||
self.base_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(&self) -> Option<bool> {
|
pub fn complete(&self) -> Option<bool> {
|
||||||
#[expect(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||||
@@ -164,25 +155,36 @@ impl ProjectObject {
|
|||||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_path(&self) -> Option<&String> {
|
pub fn source_path(&self) -> Option<&Utf8UnixPathBuf> {
|
||||||
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn progress_categories(&self) -> &[String] {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_generated(&self) -> Option<bool> {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
pub struct ScratchConfig {
|
pub struct ScratchConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub compiler: Option<String>,
|
pub compiler: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub ctx_path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub ctx_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_ctx: Option<bool>,
|
pub build_ctx: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +199,20 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ProjectConfigInfo {
|
pub struct ProjectConfigInfo {
|
||||||
pub path: PathBuf,
|
pub path: std::path::PathBuf,
|
||||||
pub timestamp: Option<FileTime>,
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
#[cfg(feature = "std")]
|
||||||
|
pub fn try_project_config(
|
||||||
|
dir: &std::path::Path,
|
||||||
|
) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||||
for filename in CONFIG_FILENAMES.iter() {
|
for filename in CONFIG_FILENAMES.iter() {
|
||||||
let config_path = dir.join(filename);
|
let config_path = dir.join(filename);
|
||||||
let Ok(file) = File::open(&config_path) else {
|
let Ok(file) = std::fs::File::open(&config_path) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let metadata = file.metadata();
|
let metadata = file.metadata();
|
||||||
@@ -214,12 +220,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
if !metadata.is_file() {
|
if !metadata.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = std::io::BufReader::new(file);
|
||||||
let mut result = match filename.contains("json") {
|
let mut result = read_json_config(&mut reader);
|
||||||
true => read_json_config(&mut reader),
|
|
||||||
false => read_yml_config(&mut reader),
|
|
||||||
};
|
|
||||||
if let Ok(config) = &result {
|
if let Ok(config) = &result {
|
||||||
// Validate min_version if present
|
// Validate min_version if present
|
||||||
if let Err(e) = validate_min_version(config) {
|
if let Err(e) = validate_min_version(config) {
|
||||||
@@ -232,40 +235,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub fn save_project_config(
|
pub fn save_project_config(
|
||||||
config: &ProjectConfig,
|
config: &ProjectConfig,
|
||||||
info: &ProjectConfigInfo,
|
info: &ProjectConfigInfo,
|
||||||
) -> Result<ProjectConfigInfo> {
|
) -> Result<ProjectConfigInfo> {
|
||||||
if let Some(last_ts) = info.timestamp {
|
if let Some(last_ts) = info.timestamp {
|
||||||
// Check if the file has changed since we last read it
|
// Check if the file has changed since we last read it
|
||||||
if let Ok(metadata) = fs::metadata(&info.path) {
|
if let Ok(metadata) = std::fs::metadata(&info.path) {
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
if ts != last_ts {
|
if ts != last_ts {
|
||||||
return Err(anyhow!("Config file has changed since last read"));
|
return Err(anyhow!("Config file has changed since last read"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut writer =
|
let mut writer = std::io::BufWriter::new(
|
||||||
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
|
std::fs::File::create(&info.path).context("Failed to create config file")?,
|
||||||
|
);
|
||||||
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
||||||
match ext {
|
match ext {
|
||||||
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
||||||
"yml" | "yaml" => {
|
|
||||||
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
|
|
||||||
}
|
|
||||||
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
||||||
}?;
|
}?;
|
||||||
let file = writer.into_inner().context("Failed to flush file")?;
|
let file = writer.into_inner().context("Failed to flush file")?;
|
||||||
let metadata = file.metadata().context("Failed to get file metadata")?;
|
let metadata = file.metadata().context("Failed to get file metadata")?;
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
||||||
let Some(min_version) = &config.min_version else { return Ok(()) };
|
let Some(min_version) = &config.min_version else { return Ok(()) };
|
||||||
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
.context("Failed to parse package version")?;
|
.context("Failed to parse package version")?;
|
||||||
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?;
|
let min_version = semver::Version::parse(min_version)
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
|
.context("Failed to parse min_version")?;
|
||||||
if version >= min_version {
|
if version >= min_version {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -273,15 +278,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
#[cfg(feature = "std")]
|
||||||
Ok(serde_yaml::from_reader(reader)?)
|
fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||||
}
|
|
||||||
|
|
||||||
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
|
||||||
Ok(serde_json::from_reader(reader)?)
|
Ok(serde_json::from_reader(reader)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
|
||||||
let mut builder = GlobSetBuilder::new();
|
let mut builder = GlobSetBuilder::new();
|
||||||
for glob in vec {
|
for glob in vec {
|
||||||
builder.add(glob.clone());
|
builder.add(glob.clone());
|
||||||
|
|||||||
57
objdiff-core/src/config/path.rs
Normal file
57
objdiff-core/src/config/path.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// For argp::FromArgs
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn platform_path(value: &str) -> Result<typed_path::Utf8PlatformPathBuf, String> {
|
||||||
|
Ok(typed_path::Utf8PlatformPathBuf::from(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8PlatformPath`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path(
|
||||||
|
path: &std::path::Path,
|
||||||
|
) -> Result<&typed_path::Utf8PlatformPath, core::str::Utf8Error> {
|
||||||
|
typed_path::Utf8PlatformPath::from_bytes_path(typed_path::PlatformPath::new(
|
||||||
|
path.as_os_str().as_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path_buf(
|
||||||
|
path: std::path::PathBuf,
|
||||||
|
) -> Result<typed_path::Utf8PlatformPathBuf, alloc::string::FromUtf8Error> {
|
||||||
|
typed_path::Utf8PlatformPathBuf::from_bytes_path_buf(typed_path::PlatformPathBuf::from(
|
||||||
|
path.into_os_string().into_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod unix_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8UnixPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8UnixPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8UnixPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "serde", feature = "std"))]
|
||||||
|
pub mod platform_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8PlatformPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8PlatformPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8PlatformPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,400 +1,504 @@
|
|||||||
use std::{cmp::max, collections::BTreeMap};
|
use alloc::{
|
||||||
|
collections::{BTreeMap, btree_map},
|
||||||
use anyhow::{anyhow, Result};
|
string::{String, ToString},
|
||||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
vec,
|
||||||
|
vec::Vec,
|
||||||
use crate::{
|
|
||||||
arch::ProcessCodeResult,
|
|
||||||
diff::{
|
|
||||||
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
|
||||||
ObjSymbolDiff,
|
|
||||||
},
|
|
||||||
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSection, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn process_code_symbol(
|
use anyhow::{Context, Result, anyhow, ensure};
|
||||||
obj: &ObjInfo,
|
|
||||||
symbol_ref: SymbolRef,
|
use super::{
|
||||||
config: &DiffObjConfig,
|
DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
|
||||||
) -> Result<ProcessCodeResult> {
|
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
|
||||||
let (section, symbol) = obj.section_symbol(symbol_ref);
|
display::display_ins_data_literals,
|
||||||
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
|
};
|
||||||
let code = §ion.data
|
use crate::obj::{
|
||||||
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
|
||||||
let mut res = obj.arch.process_code(
|
ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn no_diff_code(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_idx: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<SymbolDiff> {
|
||||||
|
let symbol = &obj.symbols[symbol_idx];
|
||||||
|
let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let section = &obj.sections[section_index];
|
||||||
|
let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
symbol.address,
|
symbol.address,
|
||||||
code,
|
symbol.address + symbol.size
|
||||||
section.orig_index,
|
)
|
||||||
§ion.relocations,
|
})?;
|
||||||
§ion.line_info,
|
let ops = obj.arch.scan_instructions(symbol.address, data, section_index, diff_config)?;
|
||||||
config,
|
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
|
||||||
)?;
|
for i in &ops {
|
||||||
|
instruction_rows
|
||||||
for inst in res.insts.iter_mut() {
|
.push(InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() });
|
||||||
if let Some(reloc) = &mut inst.reloc {
|
|
||||||
if reloc.target.size == 0 && reloc.target.name.is_empty() {
|
|
||||||
// Fake target symbol we added as a placeholder. We need to find the real one.
|
|
||||||
if let Some(real_target) =
|
|
||||||
find_symbol_matching_fake_symbol_in_sections(&reloc.target, &obj.sections)
|
|
||||||
{
|
|
||||||
reloc.addend = (reloc.target.address - real_target.address) as i64;
|
|
||||||
reloc.target = real_target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
resolve_branches(obj, section_index, &ops, &mut instruction_rows);
|
||||||
|
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
const PENALTY_IMM_DIFF: u64 = 1;
|
||||||
}
|
const PENALTY_REG_DIFF: u64 = 5;
|
||||||
|
const PENALTY_REPLACE: u64 = 60;
|
||||||
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> {
|
const PENALTY_INSERT_DELETE: u64 = 100;
|
||||||
let mut diff = Vec::<ObjInsDiff>::new();
|
|
||||||
for i in &out.insts {
|
|
||||||
diff.push(ObjInsDiff {
|
|
||||||
ins: Some(i.clone()),
|
|
||||||
kind: ObjInsDiffKind::None,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve_branches(&mut diff);
|
|
||||||
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_out: &ProcessCodeResult,
|
left_symbol_idx: usize,
|
||||||
right_out: &ProcessCodeResult,
|
right_symbol_idx: usize,
|
||||||
left_symbol_ref: SymbolRef,
|
diff_config: &DiffObjConfig,
|
||||||
right_symbol_ref: SymbolRef,
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
config: &DiffObjConfig,
|
let left_symbol = &left_obj.symbols[left_symbol_idx];
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
let right_symbol = &right_obj.symbols[right_symbol_idx];
|
||||||
let mut left_diff = Vec::<ObjInsDiff>::new();
|
let left_section = left_symbol
|
||||||
let mut right_diff = Vec::<ObjInsDiff>::new();
|
.section
|
||||||
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?;
|
.and_then(|i| left_obj.sections.get(i))
|
||||||
|
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let right_section = right_symbol
|
||||||
|
.section
|
||||||
|
.and_then(|i| right_obj.sections.get(i))
|
||||||
|
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let left_data = left_section
|
||||||
|
.data_range(left_symbol.address, left_symbol.size as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
|
left_symbol.address,
|
||||||
|
left_symbol.address + left_symbol.size
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let right_data = right_section
|
||||||
|
.data_range(right_symbol.address, right_symbol.size as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
|
right_symbol.address,
|
||||||
|
right_symbol.address + right_symbol.size
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
resolve_branches(&mut left_diff);
|
let left_section_idx = left_symbol.section.unwrap();
|
||||||
resolve_branches(&mut right_diff);
|
let right_section_idx = right_symbol.section.unwrap();
|
||||||
|
let left_ops = left_obj.arch.scan_instructions(
|
||||||
|
left_symbol.address,
|
||||||
|
left_data,
|
||||||
|
left_section_idx,
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
let right_ops = left_obj.arch.scan_instructions(
|
||||||
|
right_symbol.address,
|
||||||
|
right_data,
|
||||||
|
right_section_idx,
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
|
||||||
|
resolve_branches(left_obj, left_section_idx, &left_ops, &mut left_rows);
|
||||||
|
resolve_branches(right_obj, right_section_idx, &right_ops, &mut right_rows);
|
||||||
|
|
||||||
let mut diff_state = InsDiffState::default();
|
let mut diff_state = InstructionDiffState::default();
|
||||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
|
||||||
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
|
let result = diff_instruction(
|
||||||
left.kind = result.kind;
|
left_obj,
|
||||||
right.kind = result.kind;
|
right_obj,
|
||||||
left.arg_diff = result.left_args_diff;
|
left_symbol_idx,
|
||||||
right.arg_diff = result.right_args_diff;
|
right_symbol_idx,
|
||||||
|
left_row.ins_ref,
|
||||||
|
right_row.ins_ref,
|
||||||
|
left_row,
|
||||||
|
right_row,
|
||||||
|
diff_config,
|
||||||
|
&mut diff_state,
|
||||||
|
)?;
|
||||||
|
left_row.kind = result.kind;
|
||||||
|
right_row.kind = result.kind;
|
||||||
|
left_row.arg_diff = result.left_args_diff;
|
||||||
|
right_row.arg_diff = result.right_args_diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total = left_out.insts.len().max(right_out.insts.len());
|
let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
|
||||||
let percent = if diff_state.diff_count >= total {
|
let diff_score = diff_state.diff_score.min(max_score);
|
||||||
0.0
|
let match_percent = if max_score == 0 {
|
||||||
|
100.0
|
||||||
} else {
|
} else {
|
||||||
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
((1.0 - (diff_score as f64 / max_score as f64)) * 100.0) as f32
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
target_symbol: Some(right_symbol_idx),
|
||||||
target_symbol: Some(right_symbol_ref),
|
match_percent: Some(match_percent),
|
||||||
instructions: left_diff,
|
diff_score: Some((diff_score, max_score)),
|
||||||
match_percent: Some(percent),
|
instruction_rows: left_rows,
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
target_symbol: Some(left_symbol_idx),
|
||||||
target_symbol: Some(left_symbol_ref),
|
match_percent: Some(match_percent),
|
||||||
instructions: right_diff,
|
diff_score: Some((diff_score, max_score)),
|
||||||
match_percent: Some(percent),
|
instruction_rows: right_rows,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_instructions(
|
fn diff_instructions(
|
||||||
left_diff: &mut Vec<ObjInsDiff>,
|
left_insts: &[ScannedInstruction],
|
||||||
right_diff: &mut Vec<ObjInsDiff>,
|
right_insts: &[ScannedInstruction],
|
||||||
left_code: &ProcessCodeResult,
|
) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
|
||||||
right_code: &ProcessCodeResult,
|
let left_ops = left_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
|
||||||
) -> Result<()> {
|
let right_ops = right_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
|
||||||
let ops =
|
let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
|
||||||
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
|
|
||||||
if ops.is_empty() {
|
if ops.is_empty() {
|
||||||
left_diff.extend(
|
ensure!(left_insts.len() == right_insts.len());
|
||||||
left_code
|
let left_diff = left_insts
|
||||||
.insts
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
|
||||||
);
|
.collect();
|
||||||
right_diff.extend(
|
let right_diff = right_insts
|
||||||
right_code
|
|
||||||
.insts
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
|
||||||
);
|
.collect();
|
||||||
return Ok(());
|
return Ok((left_diff, right_diff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let row_count = ops
|
||||||
|
.iter()
|
||||||
|
.map(|op| match *op {
|
||||||
|
similar::DiffOp::Equal { len, .. } => len,
|
||||||
|
similar::DiffOp::Delete { old_len, .. } => old_len,
|
||||||
|
similar::DiffOp::Insert { new_len, .. } => new_len,
|
||||||
|
similar::DiffOp::Replace { old_len, new_len, .. } => old_len.max(new_len),
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let mut left_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
|
||||||
|
let mut right_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let len = max(left_range.len(), right_range.len());
|
let len = left_range.len().max(right_range.len());
|
||||||
left_diff.extend(
|
left_diff.extend(left_range.clone().map(|i| InstructionDiffRow {
|
||||||
left_code.insts[left_range.clone()]
|
ins_ref: Some(left_insts[i].ins_ref),
|
||||||
.iter()
|
..Default::default()
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
}));
|
||||||
);
|
right_diff.extend(right_range.clone().map(|i| InstructionDiffRow {
|
||||||
right_diff.extend(
|
ins_ref: Some(right_insts[i].ins_ref),
|
||||||
right_code.insts[right_range.clone()]
|
..Default::default()
|
||||||
.iter()
|
}));
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
|
||||||
);
|
|
||||||
if left_range.len() < len {
|
if left_range.len() < len {
|
||||||
left_diff.extend((left_range.len()..len).map(|_| ObjInsDiff::default()));
|
left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
|
||||||
}
|
}
|
||||||
if right_range.len() < len {
|
if right_range.len() < len {
|
||||||
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default()));
|
right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((left_diff, right_diff))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> String {
|
||||||
|
match arg {
|
||||||
|
InstructionArg::Value(arg) => arg.to_string(),
|
||||||
|
InstructionArg::Reloc => {
|
||||||
|
reloc.as_ref().map_or_else(|| "<unknown>".to_string(), |r| r.symbol.name.clone())
|
||||||
|
}
|
||||||
|
InstructionArg::BranchDest(arg) => arg.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
fn resolve_branches(
|
||||||
}
|
obj: &Object,
|
||||||
|
section_index: usize,
|
||||||
fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
ops: &[ScannedInstruction],
|
||||||
let mut branch_idx = 0usize;
|
rows: &mut [InstructionDiffRow],
|
||||||
|
) {
|
||||||
|
let section = &obj.sections[section_index];
|
||||||
|
let mut branch_idx = 0u32;
|
||||||
// Map addresses to indices
|
// Map addresses to indices
|
||||||
let mut addr_map = BTreeMap::<u64, usize>::new();
|
let mut addr_map = BTreeMap::<u64, u32>::new();
|
||||||
for (i, ins_diff) in vec.iter().enumerate() {
|
for (i, ins_diff) in rows.iter().enumerate() {
|
||||||
if let Some(ins) = &ins_diff.ins {
|
if let Some(ins) = ins_diff.ins_ref {
|
||||||
addr_map.insert(ins.address, i);
|
addr_map.insert(ins.address, i as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate branches
|
// Generate branches
|
||||||
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
|
let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
|
||||||
for (i, ins_diff) in vec.iter_mut().enumerate() {
|
for ((i, ins_diff), ins) in
|
||||||
if let Some(ins) = &ins_diff.ins {
|
rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
|
||||||
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) {
|
{
|
||||||
if let Some(branch) = branches.get_mut(ins_idx) {
|
let branch_dest = if let Some(resolved) = section.relocation_at(obj, ins.ins_ref) {
|
||||||
ins_diff.branch_to =
|
if resolved.symbol.section == Some(section_index) {
|
||||||
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
|
// If the relocation target is in the same section, use it as the branch destination
|
||||||
branch.ins_idx.push(i);
|
resolved.symbol.address.checked_add_signed(resolved.relocation.addend)
|
||||||
} else {
|
} else {
|
||||||
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
|
None
|
||||||
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
|
}
|
||||||
|
} else {
|
||||||
|
ins.branch_dest
|
||||||
|
};
|
||||||
|
if let Some(ins_idx) = branch_dest.and_then(|a| addr_map.get(&a).copied()) {
|
||||||
|
match branches.entry(ins_idx) {
|
||||||
|
btree_map::Entry::Vacant(e) => {
|
||||||
|
ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });
|
||||||
|
e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
|
||||||
branch_idx += 1;
|
branch_idx += 1;
|
||||||
}
|
}
|
||||||
|
btree_map::Entry::Occupied(e) => {
|
||||||
|
let branch = e.into_mut();
|
||||||
|
ins_diff.branch_to =
|
||||||
|
Some(InstructionBranchTo { ins_idx, branch_idx: branch.branch_idx });
|
||||||
|
branch.ins_idx.push(i as u32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store branch from
|
// Store branch from
|
||||||
for (i, branch) in branches {
|
for (i, branch) in branches {
|
||||||
vec[i].branch_from = Some(branch);
|
rows[i as usize].branch_from = Some(branch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool {
|
||||||
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
if right.symbol.size == 0 && left.symbol.size != 0 {
|
||||||
|
// The base relocation is against a pool but the target relocation isn't.
|
||||||
|
// This can happen in rare cases where the compiler will generate a pool+addend relocation
|
||||||
|
// in the base's data, but the one detected in the target is direct with no addend.
|
||||||
|
// Just check that the final address is the same so these count as a match.
|
||||||
|
left.symbol.address as i64 + left.relocation.addend
|
||||||
|
== right.symbol.address as i64 + right.relocation.addend
|
||||||
|
} else {
|
||||||
|
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
|
||||||
|
// target symbol address and relocation addend both match exactly.
|
||||||
|
left.symbol.address == right.symbol.address
|
||||||
|
&& left.relocation.addend == right.relocation.addend
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section_name_eq(
|
pub(crate) fn section_name_eq(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_orig_section_index: usize,
|
left_section_index: usize,
|
||||||
right_orig_section_index: usize,
|
right_section_index: usize,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(left_section) =
|
left_obj.sections.get(left_section_index).is_some_and(|left_section| {
|
||||||
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
|
right_obj
|
||||||
else {
|
.sections
|
||||||
return false;
|
.get(right_section_index)
|
||||||
};
|
.is_some_and(|right_section| left_section.name == right_section.name)
|
||||||
let Some(right_section) =
|
})
|
||||||
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
left_section.name == right_section.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reloc_eq(
|
fn reloc_eq(
|
||||||
config: &DiffObjConfig,
|
left_obj: &Object,
|
||||||
left_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
left_ins: ResolvedInstructionRef,
|
||||||
left_reloc: Option<&ObjReloc>,
|
right_ins: ResolvedInstructionRef,
|
||||||
right_reloc: Option<&ObjReloc>,
|
diff_config: &DiffObjConfig,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
|
||||||
return false;
|
let (left_reloc, right_reloc) = match (left_ins.relocation, right_ins.relocation) {
|
||||||
|
(Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
|
||||||
|
// If relocations are relaxed, match if left is missing a reloc
|
||||||
|
(None, Some(_)) => return relax_reloc_diffs,
|
||||||
|
(None, None) => return true,
|
||||||
|
_ => return false,
|
||||||
};
|
};
|
||||||
if left.flags != right.flags {
|
if left_reloc.relocation.flags != right_reloc.relocation.flags {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if config.relax_reloc_diffs {
|
if relax_reloc_diffs {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol_name_matches = left.target.name == right.target.name;
|
let symbol_name_addend_matches = left_reloc.symbol.name == right_reloc.symbol.name
|
||||||
match (&left.target.orig_section_index, &right.target.orig_section_index) {
|
&& left_reloc.relocation.addend == right_reloc.relocation.addend;
|
||||||
|
match (&left_reloc.symbol.section, &right_reloc.symbol.section) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
// Match if section and name or address match
|
// Match if section and name or address match
|
||||||
section_name_eq(left_obj, right_obj, *sl, *sr)
|
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||||
&& (symbol_name_matches || address_eq(left, right))
|
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|
||||||
|
|| symbol_name_addend_matches
|
||||||
|
|| address_eq(left_reloc, right_reloc))
|
||||||
|
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|
||||||
|
|| left_reloc.symbol.kind != SymbolKind::Object
|
||||||
|
|| display_ins_data_literals(left_obj, left_ins)
|
||||||
|
== display_ins_data_literals(right_obj, right_ins))
|
||||||
}
|
}
|
||||||
(Some(_), None) => false,
|
(Some(_), None) => false,
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
// Match if possibly stripped weak symbol
|
// Match if possibly stripped weak symbol
|
||||||
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
|
||||||
}
|
}
|
||||||
(None, None) => symbol_name_matches,
|
(None, None) => symbol_name_addend_matches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_eq(
|
fn arg_eq(
|
||||||
config: &DiffObjConfig,
|
left_obj: &Object,
|
||||||
left_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
left_row: &InstructionDiffRow,
|
||||||
left: &ObjInsArg,
|
right_row: &InstructionDiffRow,
|
||||||
right: &ObjInsArg,
|
left_arg: &InstructionArg,
|
||||||
left_diff: &ObjInsDiff,
|
right_arg: &InstructionArg,
|
||||||
right_diff: &ObjInsDiff,
|
left_ins: ResolvedInstructionRef,
|
||||||
|
right_ins: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match left {
|
match left_arg {
|
||||||
ObjInsArg::PlainText(l) => match right {
|
InstructionArg::Value(l) => match right_arg {
|
||||||
ObjInsArg::PlainText(r) => l == r,
|
InstructionArg::Value(r) => l.loose_eq(r),
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
ObjInsArg::Arg(l) => match right {
|
|
||||||
ObjInsArg::Arg(r) => l.loose_eq(r),
|
|
||||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||||
// Useful for instances where the target object is created without relocations
|
// Useful for instances where the target object is created without relocations
|
||||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
ObjInsArg::Reloc => {
|
InstructionArg::Reloc => {
|
||||||
matches!(right, ObjInsArg::Reloc)
|
matches!(right_arg, InstructionArg::Reloc)
|
||||||
&& reloc_eq(
|
&& reloc_eq(left_obj, right_obj, left_ins, right_ins, diff_config)
|
||||||
config,
|
}
|
||||||
|
InstructionArg::BranchDest(_) => match right_arg {
|
||||||
|
// Compare dest instruction idx after diffing
|
||||||
|
InstructionArg::BranchDest(_) => {
|
||||||
|
left_row.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
|
== right_row.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
|
}
|
||||||
|
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||||
|
// Useful for instances where the target object is created without relocations
|
||||||
|
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InstructionDiffState {
|
||||||
|
diff_score: u64,
|
||||||
|
left_arg_idx: u32,
|
||||||
|
right_arg_idx: u32,
|
||||||
|
left_args_idx: BTreeMap<String, u32>,
|
||||||
|
right_args_idx: BTreeMap<String, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InstructionDiffResult {
|
||||||
|
kind: InstructionDiffKind,
|
||||||
|
left_args_diff: Vec<InstructionArgDiffIndex>,
|
||||||
|
right_args_diff: Vec<InstructionArgDiffIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstructionDiffResult {
|
||||||
|
#[inline]
|
||||||
|
const fn new(kind: InstructionDiffKind) -> Self {
|
||||||
|
Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff_instruction(
|
||||||
|
left_obj: &Object,
|
||||||
|
right_obj: &Object,
|
||||||
|
left_symbol_idx: usize,
|
||||||
|
right_symbol_idx: usize,
|
||||||
|
l: Option<InstructionRef>,
|
||||||
|
r: Option<InstructionRef>,
|
||||||
|
left_row: &InstructionDiffRow,
|
||||||
|
right_row: &InstructionDiffRow,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
state: &mut InstructionDiffState,
|
||||||
|
) -> Result<InstructionDiffResult> {
|
||||||
|
let (l, r) = match (l, r) {
|
||||||
|
(Some(l), Some(r)) => (l, r),
|
||||||
|
(Some(_), None) => {
|
||||||
|
state.diff_score += PENALTY_INSERT_DELETE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Delete));
|
||||||
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
state.diff_score += PENALTY_INSERT_DELETE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Insert));
|
||||||
|
}
|
||||||
|
(None, None) => return Ok(InstructionDiffResult::new(InstructionDiffKind::None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If opcodes don't match, replace
|
||||||
|
if l.opcode != r.opcode {
|
||||||
|
state.diff_score += PENALTY_REPLACE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_resolved = left_obj
|
||||||
|
.resolve_instruction_ref(left_symbol_idx, l)
|
||||||
|
.context("Failed to resolve left instruction")?;
|
||||||
|
let right_resolved = right_obj
|
||||||
|
.resolve_instruction_ref(right_symbol_idx, r)
|
||||||
|
.context("Failed to resolve right instruction")?;
|
||||||
|
|
||||||
|
if left_resolved.code != right_resolved.code
|
||||||
|
|| !reloc_eq(left_obj, right_obj, left_resolved, right_resolved, diff_config)
|
||||||
|
{
|
||||||
|
// If either the raw code bytes or relocations don't match, process instructions and compare args
|
||||||
|
let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?;
|
||||||
|
let right_ins = left_obj.arch.process_instruction(right_resolved, diff_config)?;
|
||||||
|
if left_ins.args.len() != right_ins.args.len() {
|
||||||
|
state.diff_score += PENALTY_REPLACE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
|
||||||
|
}
|
||||||
|
let mut result = InstructionDiffResult::new(InstructionDiffKind::None);
|
||||||
|
if left_ins.mnemonic != right_ins.mnemonic {
|
||||||
|
state.diff_score += PENALTY_REG_DIFF;
|
||||||
|
result.kind = InstructionDiffKind::OpMismatch;
|
||||||
|
}
|
||||||
|
for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
|
||||||
|
if arg_eq(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
left_row,
|
||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
right_row,
|
||||||
)
|
a,
|
||||||
|
b,
|
||||||
|
left_resolved,
|
||||||
|
right_resolved,
|
||||||
|
diff_config,
|
||||||
|
) {
|
||||||
|
result.left_args_diff.push(InstructionArgDiffIndex::NONE);
|
||||||
|
result.right_args_diff.push(InstructionArgDiffIndex::NONE);
|
||||||
|
} else {
|
||||||
|
state.diff_score += if let InstructionArg::Value(
|
||||||
|
InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
|
||||||
|
) = a
|
||||||
|
{
|
||||||
|
PENALTY_IMM_DIFF
|
||||||
|
} else {
|
||||||
|
PENALTY_REG_DIFF
|
||||||
|
};
|
||||||
|
if result.kind == InstructionDiffKind::None {
|
||||||
|
result.kind = InstructionDiffKind::ArgMismatch;
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchDest(_) => match right {
|
let a_str = arg_to_string(a, left_resolved.relocation);
|
||||||
// Compare dest instruction idx after diffing
|
let a_diff = match state.left_args_idx.entry(a_str) {
|
||||||
ObjInsArg::BranchDest(_) => {
|
btree_map::Entry::Vacant(e) => {
|
||||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
let idx = state.left_arg_idx;
|
||||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
state.left_arg_idx = idx + 1;
|
||||||
|
e.insert(idx);
|
||||||
|
idx
|
||||||
}
|
}
|
||||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
btree_map::Entry::Occupied(e) => *e.get(),
|
||||||
// Useful for instances where the target object is created without relocations
|
};
|
||||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
let b_str = arg_to_string(b, right_resolved.relocation);
|
||||||
_ => false,
|
let b_diff = match state.right_args_idx.entry(b_str) {
|
||||||
},
|
btree_map::Entry::Vacant(e) => {
|
||||||
|
let idx = state.right_arg_idx;
|
||||||
|
state.right_arg_idx = idx + 1;
|
||||||
|
e.insert(idx);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
btree_map::Entry::Occupied(e) => *e.get(),
|
||||||
|
};
|
||||||
|
result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
|
||||||
|
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct InsDiffState {
|
|
||||||
diff_count: usize,
|
|
||||||
left_arg_idx: usize,
|
|
||||||
right_arg_idx: usize,
|
|
||||||
left_args_idx: BTreeMap<String, usize>,
|
|
||||||
right_args_idx: BTreeMap<String, usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct InsDiffResult {
|
|
||||||
kind: ObjInsDiffKind,
|
|
||||||
left_args_diff: Vec<Option<ObjInsArgDiff>>,
|
|
||||||
right_args_diff: Vec<Option<ObjInsArgDiff>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare_ins(
|
|
||||||
config: &DiffObjConfig,
|
|
||||||
left_obj: &ObjInfo,
|
|
||||||
right_obj: &ObjInfo,
|
|
||||||
left: &ObjInsDiff,
|
|
||||||
right: &ObjInsDiff,
|
|
||||||
state: &mut InsDiffState,
|
|
||||||
) -> Result<InsDiffResult> {
|
|
||||||
let mut result = InsDiffResult::default();
|
|
||||||
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
|
||||||
// Count only non-PlainText args
|
|
||||||
let left_args_count = left_ins.iter_args().count();
|
|
||||||
let right_args_count = right_ins.iter_args().count();
|
|
||||||
if left_args_count != right_args_count || left_ins.op != right_ins.op {
|
|
||||||
// Totally different op
|
|
||||||
result.kind = ObjInsDiffKind::Replace;
|
|
||||||
state.diff_count += 1;
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
if left_ins.mnemonic != right_ins.mnemonic {
|
|
||||||
// Same op but different mnemonic, still cmp args
|
|
||||||
result.kind = ObjInsDiffKind::OpMismatch;
|
|
||||||
state.diff_count += 1;
|
|
||||||
}
|
|
||||||
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
|
|
||||||
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
|
||||||
result.left_args_diff.push(None);
|
|
||||||
result.right_args_diff.push(None);
|
|
||||||
} else {
|
|
||||||
if result.kind == ObjInsDiffKind::None {
|
|
||||||
result.kind = ObjInsDiffKind::ArgMismatch;
|
|
||||||
state.diff_count += 1;
|
|
||||||
}
|
|
||||||
let a_str = match a {
|
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
|
||||||
ObjInsArg::Reloc => left_ins
|
|
||||||
.reloc
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
|
||||||
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
|
||||||
};
|
|
||||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
|
||||||
ObjInsArgDiff { idx: *idx }
|
|
||||||
} else {
|
|
||||||
let idx = state.left_arg_idx;
|
|
||||||
state.left_args_idx.insert(a_str, idx);
|
|
||||||
state.left_arg_idx += 1;
|
|
||||||
ObjInsArgDiff { idx }
|
|
||||||
};
|
|
||||||
let b_str = match b {
|
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
|
||||||
ObjInsArg::Reloc => right_ins
|
|
||||||
.reloc
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
|
||||||
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
|
||||||
};
|
|
||||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
|
||||||
ObjInsArgDiff { idx: *idx }
|
|
||||||
} else {
|
|
||||||
let idx = state.right_arg_idx;
|
|
||||||
state.right_args_idx.insert(b_str, idx);
|
|
||||||
state.right_arg_idx += 1;
|
|
||||||
ObjInsArgDiff { idx }
|
|
||||||
};
|
|
||||||
result.left_args_diff.push(Some(a_diff));
|
|
||||||
result.right_args_diff.push(Some(b_diff));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if left.ins.is_some() {
|
|
||||||
result.kind = ObjInsDiffKind::Delete;
|
|
||||||
state.diff_count += 1;
|
|
||||||
} else {
|
|
||||||
result.kind = ObjInsDiffKind::Insert;
|
|
||||||
state.diff_count += 1;
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_symbol_matching_fake_symbol_in_sections(
|
Ok(InstructionDiffResult::new(InstructionDiffKind::None))
|
||||||
fake_symbol: &ObjSymbol,
|
|
||||||
sections: &[ObjSection],
|
|
||||||
) -> Option<ObjSymbol> {
|
|
||||||
let orig_section_index = fake_symbol.orig_section_index?;
|
|
||||||
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
|
|
||||||
let real_symbol = section
|
|
||||||
.symbols
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&fake_symbol.address))?;
|
|
||||||
Some(real_symbol.clone())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +1,232 @@
|
|||||||
use std::cmp::{max, min, Ordering};
|
use alloc::{vec, vec::Vec};
|
||||||
|
use core::{cmp::Ordering, ops::Range};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
|
use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
|
||||||
|
|
||||||
use crate::{
|
use super::{
|
||||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjSectionDiff, ObjSymbolDiff},
|
DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
|
||||||
obj::{ObjInfo, ObjSection, SymbolRef},
|
code::{address_eq, section_name_eq},
|
||||||
};
|
};
|
||||||
|
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
|
||||||
|
|
||||||
pub fn diff_bss_symbol(
|
pub fn diff_bss_symbol(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_ref: usize,
|
||||||
right_symbol_ref: SymbolRef,
|
right_symbol_ref: usize,
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
let (_, left_symbol) = left_obj.section_symbol(left_symbol_ref);
|
let left_symbol = &left_obj.symbols[left_symbol_ref];
|
||||||
let (_, right_symbol) = right_obj.section_symbol(right_symbol_ref);
|
let right_symbol = &right_obj.symbols[right_symbol_ref];
|
||||||
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
|
||||||
target_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
|
||||||
target_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
fn reloc_eq(
|
||||||
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
|
left_obj: &Object,
|
||||||
|
right_obj: &Object,
|
||||||
|
left: ResolvedRelocation,
|
||||||
|
right: ResolvedRelocation,
|
||||||
|
) -> bool {
|
||||||
|
if left.relocation.flags != right.relocation.flags {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let symbol_name_addend_matches =
|
||||||
|
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
|
||||||
|
match (left.symbol.section, right.symbol.section) {
|
||||||
|
(Some(sl), Some(sr)) => {
|
||||||
|
// Match if section and name+addend or address match
|
||||||
|
section_name_eq(left_obj, right_obj, sl, sr)
|
||||||
|
&& (symbol_name_addend_matches || address_eq(left, right))
|
||||||
|
}
|
||||||
|
(Some(_), None) => false,
|
||||||
|
(None, Some(_)) => {
|
||||||
|
// Match if possibly stripped weak symbol
|
||||||
|
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
|
||||||
|
}
|
||||||
|
(None, None) => symbol_name_addend_matches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn resolve_relocation<'obj>(
|
||||||
|
symbols: &'obj [Symbol],
|
||||||
|
reloc: &'obj Relocation,
|
||||||
|
) -> ResolvedRelocation<'obj> {
|
||||||
|
let symbol = &symbols[reloc.target_symbol];
|
||||||
|
ResolvedRelocation { relocation: reloc, symbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares relocations contained with a certain data range.
|
||||||
|
fn diff_data_relocs_for_range<'left, 'right>(
|
||||||
|
left_obj: &'left Object,
|
||||||
|
right_obj: &'right Object,
|
||||||
|
left_section_idx: usize,
|
||||||
|
right_section_idx: usize,
|
||||||
|
left_range: Range<usize>,
|
||||||
|
right_range: Range<usize>,
|
||||||
|
) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
|
||||||
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
|
let mut diffs = Vec::new();
|
||||||
|
for left_reloc in left_section.relocations.iter() {
|
||||||
|
if !left_range.contains(&(left_reloc.address as usize)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let left_offset = left_reloc.address as usize - left_range.start;
|
||||||
|
let left_reloc = resolve_relocation(&left_obj.symbols, left_reloc);
|
||||||
|
let Some(right_reloc) = right_section.relocations.iter().find(|r| {
|
||||||
|
if !right_range.contains(&(r.address as usize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let right_offset = r.address as usize - right_range.start;
|
||||||
|
right_offset == left_offset
|
||||||
|
}) else {
|
||||||
|
diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
|
||||||
|
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
|
||||||
|
diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
|
||||||
|
} else {
|
||||||
|
diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for right_reloc in right_section.relocations.iter() {
|
||||||
|
if !right_range.contains(&(right_reloc.address as usize)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let right_offset = right_reloc.address as usize - right_range.start;
|
||||||
|
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
|
||||||
|
let Some(_) = left_section.relocations.iter().find(|r| {
|
||||||
|
if !left_range.contains(&(r.address as usize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let left_offset = r.address as usize - left_range.start;
|
||||||
|
left_offset == right_offset
|
||||||
|
}) else {
|
||||||
|
diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// No need to check the cases for relocations being deleted or matching again.
|
||||||
|
// They were already handled in the loop over the left relocs.
|
||||||
|
}
|
||||||
|
diffs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the data sections of two object files.
|
/// Compare the data sections of two object files.
|
||||||
pub fn diff_data_section(
|
pub fn diff_data_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
right: &ObjSection,
|
right_obj: &Object,
|
||||||
left_section_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
right_section_diff: &ObjSectionDiff,
|
right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let left_max =
|
right_section_idx: usize,
|
||||||
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size);
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
let right_max =
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
let left_data = &left.data[..left_max as usize];
|
let left_max = left_obj
|
||||||
let right_data = &right.data[..right_max as usize];
|
.symbols
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
.iter()
|
||||||
|
.filter_map(|s| {
|
||||||
|
if s.section != Some(left_section_idx) || s.kind == SymbolKind::Section {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
s.address.checked_sub(left_section.address).map(|a| a + s.size)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(left_section.size);
|
||||||
|
let right_max = right_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| {
|
||||||
|
if s.section != Some(right_section_idx) || s.kind == SymbolKind::Section {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
s.address.checked_sub(right_section.address).map(|a| a + s.size)
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(right_section.size);
|
||||||
|
let left_data = &left_section.data[..left_max as usize];
|
||||||
|
let right_data = &right_section.data[..right_max as usize];
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||||
|
|
||||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
let mut left_data_diff = Vec::<DataDiff>::new();
|
||||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
let mut right_data_diff = Vec::<DataDiff>::new();
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let left_len = left_range.len();
|
let left_len = left_range.len();
|
||||||
let right_len = right_range.len();
|
let right_len = right_range.len();
|
||||||
let mut len = max(left_len, right_len);
|
let mut len = left_len.max(right_len);
|
||||||
let kind = match tag {
|
let kind = match tag {
|
||||||
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
similar::DiffTag::Equal => DataDiffKind::None,
|
||||||
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
similar::DiffTag::Delete => DataDiffKind::Delete,
|
||||||
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
similar::DiffTag::Insert => DataDiffKind::Insert,
|
||||||
similar::DiffTag::Replace => {
|
similar::DiffTag::Replace => {
|
||||||
// Ensure replacements are equal length
|
// Ensure replacements are equal length
|
||||||
len = min(left_len, right_len);
|
len = left_len.min(right_len);
|
||||||
ObjDataDiffKind::Replace
|
DataDiffKind::Replace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let left_data = &left.data[left_range];
|
let left_data = &left_section.data[left_range];
|
||||||
let right_data = &right.data[right_range];
|
let right_data = &right_section.data[right_range];
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: left_data[..min(len, left_data.len())].to_vec(),
|
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: right_data[..min(len, right_data.len())].to_vec(),
|
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
if kind == ObjDataDiffKind::Replace {
|
if kind == DataDiffKind::Replace {
|
||||||
match left_len.cmp(&right_len) {
|
match left_len.cmp(&right_len) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
let len = right_len - left_len;
|
let len = right_len - left_len;
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: DataDiffKind::Insert,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: right_data[left_len..right_len].to_vec(),
|
data: right_data[left_len..right_len].to_vec(),
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: DataDiffKind::Insert,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
let len = left_len - right_len;
|
let len = left_len - right_len;
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: left_data[right_len..left_len].to_vec(),
|
data: left_data[right_len..left_len].to_vec(),
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: DataDiffKind::Delete,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: DataDiffKind::Delete,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
@@ -121,52 +236,168 @@ pub fn diff_data_section(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut left_section_diff, mut right_section_diff) =
|
let mut left_reloc_diffs = Vec::new();
|
||||||
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
|
let mut right_reloc_diffs = Vec::new();
|
||||||
left_section_diff.data_diff = left_diff;
|
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
|
||||||
right_section_diff.data_diff = right_diff;
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
0..left_max as usize,
|
||||||
|
0..right_max as usize,
|
||||||
|
) {
|
||||||
|
if let Some(left_reloc) = left_reloc {
|
||||||
|
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
|
||||||
|
let range = left_reloc.relocation.address as usize
|
||||||
|
..left_reloc.relocation.address as usize + len;
|
||||||
|
left_reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: left_reloc.relocation.clone(),
|
||||||
|
kind: diff_kind,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(right_reloc) = right_reloc {
|
||||||
|
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
|
||||||
|
let range = right_reloc.relocation.address as usize
|
||||||
|
..right_reloc.relocation.address as usize + len;
|
||||||
|
right_reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: right_reloc.relocation.clone(),
|
||||||
|
kind: diff_kind,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut left_section_diff, mut right_section_diff) = diff_generic_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_diff,
|
||||||
|
right_diff,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
|
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == DataDiffKind::None);
|
||||||
|
left_section_diff.data_diff = left_data_diff;
|
||||||
|
right_section_diff.data_diff = right_data_diff;
|
||||||
|
left_section_diff.reloc_diff = left_reloc_diffs;
|
||||||
|
right_section_diff.reloc_diff = right_reloc_diffs;
|
||||||
|
if all_left_relocs_match {
|
||||||
// Use the highest match percent between two options:
|
// Use the highest match percent between two options:
|
||||||
// - Left symbols matching right symbols by name
|
// - Left symbols matching right symbols by name
|
||||||
// - Diff of the data itself
|
// - Diff of the data itself
|
||||||
|
// We only do this when all relocations on the left side match.
|
||||||
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
||||||
left_section_diff.match_percent = Some(match_percent);
|
left_section_diff.match_percent = Some(match_percent);
|
||||||
right_section_diff.match_percent = Some(match_percent);
|
right_section_diff.match_percent = Some(match_percent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok((left_section_diff, right_section_diff))
|
Ok((left_section_diff, right_section_diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_data_symbol(
|
pub fn diff_data_symbol(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_idx: usize,
|
||||||
right_symbol_ref: SymbolRef,
|
right_symbol_idx: usize,
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
let (left_section, left_symbol) = left_obj.section_symbol(left_symbol_ref);
|
let left_symbol = &left_obj.symbols[left_symbol_idx];
|
||||||
let (right_section, right_symbol) = right_obj.section_symbol(right_symbol_ref);
|
let right_symbol = &right_obj.symbols[right_symbol_idx];
|
||||||
|
|
||||||
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
let left_section_idx =
|
||||||
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||||
|
let right_section_idx =
|
||||||
|
right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||||
|
|
||||||
let left_data = &left_section.data[left_symbol.section_address as usize
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
let right_data = &right_section.data[right_symbol.section_address as usize
|
|
||||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
|
||||||
|
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
let left_start = left_symbol
|
||||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
.address
|
||||||
|
.checked_sub(left_section.address)
|
||||||
|
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
|
||||||
|
let right_start = right_symbol
|
||||||
|
.address
|
||||||
|
.checked_sub(right_section.address)
|
||||||
|
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
|
||||||
|
let left_end = left_start + left_symbol.size;
|
||||||
|
if left_end > left_section.size {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Symbol {} size out of section bounds ({} > {})",
|
||||||
|
left_symbol.name,
|
||||||
|
left_end,
|
||||||
|
left_section.size
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let right_end = right_start + right_symbol.size;
|
||||||
|
if right_end > right_section.size {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Symbol {} size out of section bounds ({} > {})",
|
||||||
|
right_symbol.name,
|
||||||
|
right_end,
|
||||||
|
right_section.size
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let left_range = left_start as usize..left_end as usize;
|
||||||
|
let right_range = right_start as usize..right_end as usize;
|
||||||
|
let left_data = &left_section.data[left_range.clone()];
|
||||||
|
let right_data = &right_section.data[right_range.clone()];
|
||||||
|
|
||||||
|
let reloc_diffs = diff_data_relocs_for_range(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
left_range,
|
||||||
|
right_range,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
|
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||||
|
|
||||||
|
let mut match_ratio = bytes_match_ratio;
|
||||||
|
if !reloc_diffs.is_empty() {
|
||||||
|
let mut total_reloc_bytes = 0;
|
||||||
|
let mut matching_reloc_bytes = 0;
|
||||||
|
for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
|
||||||
|
let reloc_diff_len = match (left_reloc, right_reloc) {
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
(None, Some(right_reloc)) => {
|
||||||
|
right_obj.arch.data_reloc_size(right_reloc.relocation.flags)
|
||||||
|
}
|
||||||
|
(Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags),
|
||||||
|
};
|
||||||
|
total_reloc_bytes += reloc_diff_len;
|
||||||
|
if diff_kind == DataDiffKind::None {
|
||||||
|
matching_reloc_bytes += reloc_diff_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if total_reloc_bytes > 0 {
|
||||||
|
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
|
||||||
|
// Adjust the overall match ratio to include relocation differences.
|
||||||
|
// We calculate it so that bytes that contain a relocation are counted twice: once for the
|
||||||
|
// byte's raw value, and once for its relocation.
|
||||||
|
// e.g. An 8 byte symbol that has 8 matching raw bytes and a single 4 byte relocation that
|
||||||
|
// doesn't match would show as 66% (weighted average of 100% and 0%).
|
||||||
|
match_ratio = ((bytes_match_ratio * (left_data.len() as f32))
|
||||||
|
+ (relocs_match_ratio * total_reloc_bytes as f32))
|
||||||
|
/ (left_data.len() + total_reloc_bytes) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let match_percent = match_ratio * 100.0;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
target_symbol: Some(right_symbol_idx),
|
||||||
target_symbol: Some(right_symbol_ref),
|
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
target_symbol: Some(left_symbol_idx),
|
||||||
target_symbol: Some(left_symbol_ref),
|
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -174,49 +405,85 @@ pub fn diff_data_symbol(
|
|||||||
/// Compares a section of two object files.
|
/// Compares a section of two object files.
|
||||||
/// This essentially adds up the match percentage of each symbol in the section.
|
/// This essentially adds up the match percentage of each symbol in the section.
|
||||||
pub fn diff_generic_section(
|
pub fn diff_generic_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
_right: &ObjSection,
|
_right_obj: &Object,
|
||||||
left_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
_right_diff: &ObjSectionDiff,
|
_right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) {
|
_right_section_idx: usize,
|
||||||
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
|
let match_percent = if left_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
|
||||||
|
.map(|(i, _)| &left_diff.symbols[i])
|
||||||
|
.all(|d| d.match_percent == Some(100.0))
|
||||||
|
{
|
||||||
100.0 // Avoid fp precision issues
|
100.0 // Avoid fp precision issues
|
||||||
} else {
|
} else {
|
||||||
left.symbols
|
let (matched, total) = left_obj
|
||||||
|
.symbols
|
||||||
.iter()
|
.iter()
|
||||||
.zip(left_diff.symbols.iter())
|
.enumerate()
|
||||||
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
|
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
|
||||||
.sum::<f32>()
|
.map(|(i, s)| (s, &left_diff.symbols[i]))
|
||||||
/ left.size as f32
|
.fold((0.0, 0.0), |(matched, total), (s, d)| {
|
||||||
|
(matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
|
||||||
|
});
|
||||||
|
if total == 0.0 { 100.0 } else { matched / total }
|
||||||
};
|
};
|
||||||
Ok((
|
Ok((
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
||||||
pub fn diff_bss_section(
|
pub fn diff_bss_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
right: &ObjSection,
|
right_obj: &Object,
|
||||||
left_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
right_diff: &ObjSectionDiff,
|
right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
right_section_idx: usize,
|
||||||
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None);
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
|
let left_sizes = left_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
|
||||||
|
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| (a, s.size)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
|
let right_sizes = right_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, s)| s.section == Some(right_section_idx) && s.kind != SymbolKind::Section)
|
||||||
|
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| (a, s.size)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
|
||||||
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
||||||
|
|
||||||
// Use the highest match percent between two options:
|
// Use the highest match percent between two options:
|
||||||
// - Left symbols matching right symbols by name
|
// - Left symbols matching right symbols by name
|
||||||
// - Diff of the addresses and sizes of each symbol
|
// - Diff of the addresses and sizes of each symbol
|
||||||
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?;
|
let (generic_diff, _) = diff_generic_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_diff,
|
||||||
|
right_diff,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
|
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
|
||||||
match_percent = generic_diff.match_percent.unwrap();
|
match_percent = generic_diff.match_percent.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
use std::cmp::Ordering;
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::BTreeSet,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{ObjInsArgDiff, ObjInsDiff},
|
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
|
||||||
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
|
obj::{
|
||||||
|
InstructionArg, InstructionArgValue, Object, ParsedInstruction, ResolvedInstructionRef,
|
||||||
|
ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag, SymbolKind,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DiffText<'a> {
|
pub enum DiffText<'a> {
|
||||||
/// Basic text
|
/// Basic text
|
||||||
Basic(&'a str),
|
Basic(&'a str),
|
||||||
/// Colored text
|
|
||||||
BasicColor(&'a str, usize),
|
|
||||||
/// Line number
|
/// Line number
|
||||||
Line(u32),
|
Line(u32),
|
||||||
/// Instruction address
|
/// Instruction address
|
||||||
@@ -18,103 +30,267 @@ pub enum DiffText<'a> {
|
|||||||
/// Instruction mnemonic
|
/// Instruction mnemonic
|
||||||
Opcode(&'a str, u16),
|
Opcode(&'a str, u16),
|
||||||
/// Instruction argument
|
/// Instruction argument
|
||||||
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
|
Argument(InstructionArgValue<'a>),
|
||||||
/// Branch destination
|
/// Branch destination
|
||||||
BranchDest(u64, Option<&'a ObjInsArgDiff>),
|
BranchDest(u64),
|
||||||
/// Symbol name
|
/// Symbol name
|
||||||
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>),
|
Symbol(&'a Symbol),
|
||||||
|
/// Relocation addend
|
||||||
|
Addend(i64),
|
||||||
/// Number of spaces
|
/// Number of spaces
|
||||||
Spacing(usize),
|
Spacing(u8),
|
||||||
/// End of line
|
/// End of line
|
||||||
Eol,
|
Eol,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub enum DiffTextColor {
|
||||||
|
#[default]
|
||||||
|
Normal, // Grey
|
||||||
|
Dim, // Dark grey
|
||||||
|
Bright, // White
|
||||||
|
Replace, // Blue
|
||||||
|
Delete, // Red
|
||||||
|
Insert, // Green
|
||||||
|
Rotating(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiffTextSegment<'a> {
|
||||||
|
pub text: DiffText<'a>,
|
||||||
|
pub color: DiffTextColor,
|
||||||
|
pub pad_to: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DiffTextSegment<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn basic(text: &'a str, color: DiffTextColor) -> Self {
|
||||||
|
Self { text: DiffText::Basic(text), color, pad_to: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn spacing(spaces: u8) -> Self {
|
||||||
|
Self { text: DiffText::Spacing(spaces), color: DiffTextColor::Normal, pad_to: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EOL_SEGMENT: DiffTextSegment<'static> =
|
||||||
|
DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum HighlightKind {
|
pub enum HighlightKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Opcode(u16),
|
Opcode(u16),
|
||||||
Arg(ObjInsArgValue),
|
Argument(InstructionArgValue<'static>),
|
||||||
Symbol(String),
|
Symbol(String),
|
||||||
Address(u64),
|
Address(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_diff<E>(
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
ins_diff: &ObjInsDiff,
|
pub enum InstructionPart<'a> {
|
||||||
base_addr: u64,
|
Basic(Cow<'a, str>),
|
||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
Opcode(Cow<'a, str>, u16),
|
||||||
) -> Result<(), E> {
|
Arg(InstructionArg<'a>),
|
||||||
let Some(ins) = &ins_diff.ins else {
|
Separator,
|
||||||
cb(DiffText::Eol)?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
if let Some(line) = ins.line {
|
|
||||||
cb(DiffText::Line(line))?;
|
|
||||||
}
|
|
||||||
cb(DiffText::Address(ins.address - base_addr))?;
|
|
||||||
if let Some(branch) = &ins_diff.branch_from {
|
|
||||||
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?;
|
|
||||||
} else {
|
|
||||||
cb(DiffText::Spacing(4))?;
|
|
||||||
}
|
|
||||||
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
|
|
||||||
let mut arg_diff_idx = 0; // non-PlainText index
|
|
||||||
for (i, arg) in ins.args.iter().enumerate() {
|
|
||||||
if i == 0 {
|
|
||||||
cb(DiffText::Spacing(1))?;
|
|
||||||
}
|
|
||||||
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
|
|
||||||
match arg {
|
|
||||||
ObjInsArg::PlainText(s) => {
|
|
||||||
cb(DiffText::Basic(s))?;
|
|
||||||
}
|
|
||||||
ObjInsArg::Arg(v) => {
|
|
||||||
cb(DiffText::Argument(v, diff))?;
|
|
||||||
arg_diff_idx += 1;
|
|
||||||
}
|
|
||||||
ObjInsArg::Reloc => {
|
|
||||||
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
|
|
||||||
arg_diff_idx += 1;
|
|
||||||
}
|
|
||||||
ObjInsArg::BranchDest(dest) => {
|
|
||||||
if let Some(dest) = dest.checked_sub(base_addr) {
|
|
||||||
cb(DiffText::BranchDest(dest, diff))?;
|
|
||||||
} else {
|
|
||||||
cb(DiffText::Basic("<unknown>"))?;
|
|
||||||
}
|
|
||||||
arg_diff_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(branch) = &ins_diff.branch_to {
|
|
||||||
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
|
|
||||||
}
|
|
||||||
cb(DiffText::Eol)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc_name<E>(
|
impl<'a> InstructionPart<'a> {
|
||||||
reloc: &ObjReloc,
|
#[inline(always)]
|
||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
pub fn basic<T>(s: T) -> Self
|
||||||
diff: Option<&ObjInsArgDiff>,
|
where T: Into<Cow<'a, str>> {
|
||||||
) -> Result<(), E> {
|
InstructionPart::Basic(s.into())
|
||||||
cb(DiffText::Symbol(&reloc.target, diff))?;
|
|
||||||
match reloc.addend.cmp(&0i64) {
|
|
||||||
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
|
||||||
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn opcode<T>(s: T, o: u16) -> Self
|
||||||
|
where T: Into<Cow<'a, str>> {
|
||||||
|
InstructionPart::Opcode(s.into(), o)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn opaque<T>(s: T) -> Self
|
||||||
|
where T: Into<Cow<'a, str>> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(s.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn signed<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<i64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Signed(v.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn unsigned<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<u64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Unsigned(v.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn branch_dest<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<u64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::BranchDest(v.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reloc() -> InstructionPart<'static> { InstructionPart::Arg(InstructionArg::Reloc) }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn separator() -> InstructionPart<'static> { InstructionPart::Separator }
|
||||||
|
|
||||||
|
pub fn into_static(self) -> InstructionPart<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionPart::Basic(s) => InstructionPart::Basic(Cow::Owned(s.into_owned())),
|
||||||
|
InstructionPart::Opcode(s, o) => InstructionPart::Opcode(Cow::Owned(s.into_owned()), o),
|
||||||
|
InstructionPart::Arg(a) => InstructionPart::Arg(a.into_static()),
|
||||||
|
InstructionPart::Separator => InstructionPart::Separator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_row(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_index: usize,
|
||||||
|
ins_row: &InstructionDiffRow,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
mut cb: impl FnMut(DiffTextSegment) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(ins_ref) = ins_row.ins_ref else {
|
||||||
|
cb(EOL_SEGMENT)?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else {
|
||||||
|
cb(DiffTextSegment::basic("<invalid>", DiffTextColor::Delete))?;
|
||||||
|
cb(EOL_SEGMENT)?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let base_color = match ins_row.kind {
|
||||||
|
InstructionDiffKind::Replace => DiffTextColor::Replace,
|
||||||
|
InstructionDiffKind::Delete => DiffTextColor::Delete,
|
||||||
|
InstructionDiffKind::Insert => DiffTextColor::Insert,
|
||||||
|
_ => DiffTextColor::Normal,
|
||||||
|
};
|
||||||
|
if let Some(line) = resolved.section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b)
|
||||||
|
{
|
||||||
|
cb(DiffTextSegment { text: DiffText::Line(line), color: DiffTextColor::Dim, pad_to: 5 })?;
|
||||||
|
}
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
|
||||||
|
color: base_color,
|
||||||
|
pad_to: 5,
|
||||||
|
})?;
|
||||||
|
if let Some(branch) = &ins_row.branch_from {
|
||||||
|
cb(DiffTextSegment::basic(" ~> ", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
||||||
|
} else {
|
||||||
|
cb(DiffTextSegment::spacing(4))?;
|
||||||
|
}
|
||||||
|
let mut arg_idx = 0;
|
||||||
|
let mut displayed_relocation = false;
|
||||||
|
obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
|
||||||
|
InstructionPart::Basic(text) => {
|
||||||
|
if text.chars().all(|c| c == ' ') {
|
||||||
|
cb(DiffTextSegment::spacing(text.len() as u8))
|
||||||
|
} else {
|
||||||
|
cb(DiffTextSegment::basic(&text, base_color))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment {
|
||||||
|
text: DiffText::Opcode(mnemonic.as_ref(), opcode),
|
||||||
|
color: match ins_row.kind {
|
||||||
|
InstructionDiffKind::OpMismatch => DiffTextColor::Replace,
|
||||||
|
_ => base_color,
|
||||||
|
},
|
||||||
|
pad_to: 10,
|
||||||
|
}),
|
||||||
|
InstructionPart::Arg(arg) => {
|
||||||
|
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||||
|
arg_idx += 1;
|
||||||
|
match arg {
|
||||||
|
InstructionArg::Value(value) => cb(DiffTextSegment {
|
||||||
|
text: DiffText::Argument(value),
|
||||||
|
color: diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||||
|
pad_to: 0,
|
||||||
|
}),
|
||||||
|
InstructionArg::Reloc => {
|
||||||
|
displayed_relocation = true;
|
||||||
|
let resolved = resolved.relocation.unwrap();
|
||||||
|
let color = diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Symbol(resolved.symbol),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
if resolved.relocation.addend != 0 {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Addend(resolved.relocation.addend),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
InstructionArg::BranchDest(dest) => {
|
||||||
|
if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::BranchDest(addr),
|
||||||
|
color: diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||||
|
pad_to: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(
|
||||||
|
"<invalid>",
|
||||||
|
))),
|
||||||
|
color: diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||||
|
pad_to: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InstructionPart::Separator => {
|
||||||
|
cb(DiffTextSegment::basic(diff_config.separator(), base_color))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
// Fallback for relocation that wasn't displayed
|
||||||
|
if resolved.relocation.is_some() && !displayed_relocation {
|
||||||
|
cb(DiffTextSegment::basic(" <", base_color))?;
|
||||||
|
let resolved = resolved.relocation.unwrap();
|
||||||
|
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||||
|
let color =
|
||||||
|
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||||
|
cb(DiffTextSegment { text: DiffText::Symbol(resolved.symbol), color, pad_to: 0 })?;
|
||||||
|
if resolved.relocation.addend != 0 {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Addend(resolved.relocation.addend),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
cb(DiffTextSegment::basic(">", base_color))?;
|
||||||
|
}
|
||||||
|
if let Some(branch) = &ins_row.branch_to {
|
||||||
|
cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
||||||
|
}
|
||||||
|
cb(EOL_SEGMENT)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<DiffText<'_>> for HighlightKind {
|
impl PartialEq<DiffText<'_>> for HighlightKind {
|
||||||
fn eq(&self, other: &DiffText) -> bool {
|
fn eq(&self, other: &DiffText) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
||||||
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
(HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
|
||||||
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name,
|
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
|
||||||
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
|
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
|
||||||
a == b
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,14 +300,460 @@ impl PartialEq<HighlightKind> for DiffText<'_> {
|
|||||||
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
|
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DiffText<'_>> for HighlightKind {
|
impl From<&DiffText<'_>> for HighlightKind {
|
||||||
fn from(value: DiffText<'_>) -> Self {
|
fn from(value: &DiffText<'_>) -> Self {
|
||||||
match value {
|
match value {
|
||||||
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
DiffText::Opcode(_, op) => HighlightKind::Opcode(*op),
|
||||||
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
DiffText::Argument(arg) => HighlightKind::Argument(arg.to_static()),
|
||||||
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()),
|
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
|
||||||
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
|
DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(*addr),
|
||||||
_ => HighlightKind::None,
|
_ => HighlightKind::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ContextItem {
|
||||||
|
Copy { value: String, label: Option<String> },
|
||||||
|
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
pub enum SymbolNavigationKind {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
Extab,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HoverItemColor {
|
||||||
|
Normal, // Gray
|
||||||
|
Emphasized, // White
|
||||||
|
Special, // Blue
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HoverItem {
|
||||||
|
Text { label: String, value: String, color: HoverItemColor },
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||||
|
let symbol = &obj.symbols[symbol_index];
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
|
||||||
|
if let Some(name) = &symbol.demangled_name {
|
||||||
|
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
||||||
|
}
|
||||||
|
if symbol.section.is_some() {
|
||||||
|
if let Some(address) = symbol.virtual_address {
|
||||||
|
out.push(ContextItem::Copy {
|
||||||
|
value: format!("{:x}", address),
|
||||||
|
label: Some("virtual address".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_hover(obj: &Object, symbol_index: usize, addend: i64) -> Vec<HoverItem> {
|
||||||
|
let symbol = &obj.symbols[symbol_index];
|
||||||
|
let addend_str = match addend.cmp(&0i64) {
|
||||||
|
Ordering::Greater => format!("+{:x}", addend),
|
||||||
|
Ordering::Less => format!("-{:x}", -addend),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Name".into(),
|
||||||
|
value: format!("{}{}", symbol.name, addend_str),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
if let Some(demangled_name) = &symbol.demangled_name {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Demangled".into(),
|
||||||
|
value: demangled_name.into(),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(section) = symbol.section {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Section".into(),
|
||||||
|
value: obj.sections[section].name.clone(),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Address".into(),
|
||||||
|
value: format!("{:x}{}", symbol.address, addend_str),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
if symbol.flags.contains(SymbolFlag::SizeInferred) {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Size".into(),
|
||||||
|
value: format!("{:x} (inferred)", symbol.size),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Size".into(),
|
||||||
|
value: format!("{:x}", symbol.size),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(align) = symbol.align {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Alignment".into(),
|
||||||
|
value: align.get().to_string(),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(address) = symbol.virtual_address {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Virtual address".into(),
|
||||||
|
value: format!("{:x}", address),
|
||||||
|
color: HoverItemColor::Special,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: "Extern".into(),
|
||||||
|
color: HoverItemColor::Emphasized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.append(&mut obj.arch.symbol_hover(obj, symbol_index));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_context(
|
||||||
|
obj: &Object,
|
||||||
|
reloc: ResolvedRelocation,
|
||||||
|
ins: Option<ResolvedInstructionRef>,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
|
||||||
|
if let Some(ins) = ins {
|
||||||
|
let literals = display_ins_data_literals(obj, ins);
|
||||||
|
if !literals.is_empty() {
|
||||||
|
out.push(ContextItem::Separator);
|
||||||
|
for literal in literals {
|
||||||
|
out.push(ContextItem::Copy { value: literal, label: None });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_hover(obj: &Object, reloc: ResolvedRelocation) -> Vec<HoverItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Relocation".into(),
|
||||||
|
value: name.to_string(),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Relocation".into(),
|
||||||
|
value: format!("<{:?}>", reloc.relocation.flags),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.append(&mut symbol_hover(obj, reloc.relocation.target_symbol, reloc.relocation.addend));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instruction_context(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
ins: &ParsedInstruction,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut hex_string = String::new();
|
||||||
|
for byte in resolved.code {
|
||||||
|
hex_string.push_str(&format!("{:02x}", byte));
|
||||||
|
}
|
||||||
|
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
|
||||||
|
out.append(&mut obj.arch.instruction_context(obj, resolved));
|
||||||
|
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||||
|
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
|
out.push(ContextItem::Copy {
|
||||||
|
value: format!("{:x}", virtual_address + offset),
|
||||||
|
label: Some("virtual address".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for arg in &ins.args {
|
||||||
|
if let InstructionArg::Value(arg) = arg {
|
||||||
|
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
|
||||||
|
match arg {
|
||||||
|
InstructionArgValue::Signed(v) => {
|
||||||
|
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||||
|
}
|
||||||
|
InstructionArgValue::Unsigned(v) => {
|
||||||
|
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
out.push(ContextItem::Separator);
|
||||||
|
out.append(&mut relocation_context(obj, reloc, Some(resolved)));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instruction_hover(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
ins: &ParsedInstruction,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{:02x?}", resolved.code),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
out.append(&mut obj.arch.instruction_hover(obj, resolved));
|
||||||
|
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||||
|
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Virtual address".into(),
|
||||||
|
value: format!("{:x}", virtual_address + offset),
|
||||||
|
color: HoverItemColor::Special,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for arg in &ins.args {
|
||||||
|
if let InstructionArg::Value(arg) = arg {
|
||||||
|
match arg {
|
||||||
|
InstructionArgValue::Signed(v) => {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{arg} == {v}"),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InstructionArgValue::Unsigned(v) => {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{arg} == {v}"),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
out.push(HoverItem::Separator);
|
||||||
|
out.append(&mut relocation_hover(obj, reloc));
|
||||||
|
if let Some(ty) = obj.arch.guess_data_type(resolved) {
|
||||||
|
let literals = display_ins_data_literals(obj, resolved);
|
||||||
|
if !literals.is_empty() {
|
||||||
|
out.push(HoverItem::Separator);
|
||||||
|
for literal in literals {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: format!("{}", ty),
|
||||||
|
value: literal,
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum SymbolFilter<'a> {
|
||||||
|
None,
|
||||||
|
Search(&'a Regex),
|
||||||
|
Mapping(usize, Option<&'a Regex>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_matches_filter(
|
||||||
|
symbol: &Symbol,
|
||||||
|
diff: &SymbolDiff,
|
||||||
|
filter: SymbolFilter<'_>,
|
||||||
|
show_hidden_symbols: bool,
|
||||||
|
) -> bool {
|
||||||
|
// Ignore absolute symbols
|
||||||
|
if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !show_hidden_symbols && (symbol.size == 0 || symbol.flags.contains(SymbolFlag::Hidden)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match filter {
|
||||||
|
SymbolFilter::None => true,
|
||||||
|
SymbolFilter::Search(regex) => {
|
||||||
|
regex.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
|
||||||
|
}
|
||||||
|
SymbolFilter::Mapping(symbol_ref, regex) => {
|
||||||
|
diff.target_symbol == Some(symbol_ref)
|
||||||
|
&& regex.is_none_or(|r| {
|
||||||
|
r.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct SectionDisplaySymbol {
|
||||||
|
pub symbol: usize,
|
||||||
|
pub is_mapping_symbol: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SectionDisplay {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub size: u64,
|
||||||
|
pub match_percent: Option<f32>,
|
||||||
|
pub symbols: Vec<SectionDisplaySymbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_sections(
|
||||||
|
obj: &Object,
|
||||||
|
diff: &ObjectDiff,
|
||||||
|
filter: SymbolFilter<'_>,
|
||||||
|
show_hidden_symbols: bool,
|
||||||
|
show_mapped_symbols: bool,
|
||||||
|
reverse_fn_order: bool,
|
||||||
|
) -> Vec<SectionDisplay> {
|
||||||
|
let mut mapping = BTreeSet::new();
|
||||||
|
let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
|
||||||
|
for mapping_diff in &diff.mapping_symbols {
|
||||||
|
let symbol = &obj.symbols[mapping_diff.symbol_index];
|
||||||
|
if !symbol_matches_filter(
|
||||||
|
symbol,
|
||||||
|
&mapping_diff.symbol_diff,
|
||||||
|
filter,
|
||||||
|
show_hidden_symbols,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !show_mapped_symbols {
|
||||||
|
let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
|
||||||
|
if symbol_diff.target_symbol.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.insert((symbol.section, mapping_diff.symbol_index));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
|
||||||
|
{
|
||||||
|
if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mapping.insert((symbol.section, symbol_idx));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
|
||||||
|
let mut sections = Vec::with_capacity(num_sections);
|
||||||
|
for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
|
||||||
|
let mut symbols = group
|
||||||
|
.map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if let Some(section_idx) = section_idx {
|
||||||
|
let section = &obj.sections[section_idx];
|
||||||
|
if section.kind == SectionKind::Unknown || section.flags.contains(SectionFlag::Hidden) {
|
||||||
|
// Skip unknown and hidden sections
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let section_diff = &diff.sections[section_idx];
|
||||||
|
if section.kind == SectionKind::Code && reverse_fn_order {
|
||||||
|
symbols.sort_by(|a, b| {
|
||||||
|
let a_symbol = &obj.symbols[a.symbol];
|
||||||
|
let b_symbol = &obj.symbols[b.symbol];
|
||||||
|
symbol_sort_reverse(a_symbol, b_symbol)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
symbols.sort_by(|a, b| {
|
||||||
|
let a_symbol = &obj.symbols[a.symbol];
|
||||||
|
let b_symbol = &obj.symbols[b.symbol];
|
||||||
|
symbol_sort(a_symbol, b_symbol)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sections.push(SectionDisplay {
|
||||||
|
id: section.id.clone(),
|
||||||
|
name: if section.flags.contains(SectionFlag::Combined) {
|
||||||
|
format!("{} [combined]", section.name)
|
||||||
|
} else {
|
||||||
|
section.name.clone()
|
||||||
|
},
|
||||||
|
size: section.size,
|
||||||
|
match_percent: section_diff.match_percent,
|
||||||
|
symbols,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Don't sort, preserve order of absolute symbols
|
||||||
|
sections.push(SectionDisplay {
|
||||||
|
id: ".comm".to_string(),
|
||||||
|
name: ".comm".to_string(),
|
||||||
|
size: 0,
|
||||||
|
match_percent: None,
|
||||||
|
symbols,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sections.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
sections
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
|
||||||
|
if a.kind == SymbolKind::Section {
|
||||||
|
if b.kind != SymbolKind::Section {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
} else if b.kind == SymbolKind::Section {
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
|
||||||
|
section_symbol_sort(a, b).then(a.address.cmp(&b.address)).then(a.size.cmp(&b.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_sort_reverse(a: &Symbol, b: &Symbol) -> Ordering {
|
||||||
|
section_symbol_sort(a, b).then(b.address.cmp(&a.address)).then(b.size.cmp(&a.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
|
||||||
|
let Some(reloc) = resolved.relocation else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let bytes = &data[reloc.relocation.addend as usize..];
|
||||||
|
obj.arch
|
||||||
|
.guess_data_type(resolved)
|
||||||
|
.map(|ty| ty.display_labels(obj.endianness, bytes))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_ins_data_literals(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
|
||||||
|
let Some(reloc) = resolved.relocation else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let bytes = &data[reloc.relocation.addend as usize..];
|
||||||
|
obj.arch
|
||||||
|
.guess_data_type(resolved)
|
||||||
|
.map(|ty| ty.display_literals(obj.endianness, bytes))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
use std::collections::HashSet;
|
use alloc::{
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
string::String,
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::{num::NonZeroU32, ops::Range};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::SymbolMappings,
|
|
||||||
diff::{
|
diff::{
|
||||||
code::{diff_code, no_diff_code, process_code_symbol},
|
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, no_diff_symbol,
|
diff_generic_section,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON},
|
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod code;
|
pub mod code;
|
||||||
@@ -21,55 +26,48 @@ pub mod display;
|
|||||||
include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
|
include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
|
||||||
|
|
||||||
impl DiffObjConfig {
|
impl DiffObjConfig {
|
||||||
pub fn separator(&self) -> &'static str {
|
pub fn separator(&self) -> &'static str { if self.space_between_args { ", " } else { "," } }
|
||||||
if self.space_between_args {
|
|
||||||
", "
|
|
||||||
} else {
|
|
||||||
","
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjSectionDiff {
|
pub struct SectionDiff {
|
||||||
pub symbols: Vec<ObjSymbolDiff>,
|
// pub target_section: Option<usize>,
|
||||||
pub data_diff: Vec<ObjDataDiff>,
|
|
||||||
pub match_percent: Option<f32>,
|
pub match_percent: Option<f32>,
|
||||||
}
|
pub data_diff: Vec<DataDiff>,
|
||||||
|
pub reloc_diff: Vec<DataRelocationDiff>,
|
||||||
impl ObjSectionDiff {
|
|
||||||
fn merge(&mut self, other: ObjSectionDiff) {
|
|
||||||
// symbols ignored
|
|
||||||
self.data_diff = other.data_diff;
|
|
||||||
self.match_percent = other.match_percent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ObjSymbolDiff {
|
pub struct SymbolDiff {
|
||||||
/// The symbol ref this object
|
/// The symbol index in the _other_ object that this symbol was diffed against
|
||||||
pub symbol_ref: SymbolRef,
|
pub target_symbol: Option<usize>,
|
||||||
/// The symbol ref in the _other_ object that this symbol was diffed against
|
|
||||||
pub target_symbol: Option<SymbolRef>,
|
|
||||||
pub instructions: Vec<ObjInsDiff>,
|
|
||||||
pub match_percent: Option<f32>,
|
pub match_percent: Option<f32>,
|
||||||
|
pub diff_score: Option<(u64, u64)>,
|
||||||
|
pub instruction_rows: Vec<InstructionDiffRow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ObjInsDiff {
|
pub struct MappingSymbolDiff {
|
||||||
pub ins: Option<ObjIns>,
|
pub symbol_index: usize,
|
||||||
|
pub symbol_diff: SymbolDiff,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct InstructionDiffRow {
|
||||||
|
/// Instruction reference
|
||||||
|
pub ins_ref: Option<InstructionRef>,
|
||||||
/// Diff kind
|
/// Diff kind
|
||||||
pub kind: ObjInsDiffKind,
|
pub kind: InstructionDiffKind,
|
||||||
/// Branches from instruction
|
/// Branches from instruction(s)
|
||||||
pub branch_from: Option<ObjInsBranchFrom>,
|
pub branch_from: Option<InstructionBranchFrom>,
|
||||||
/// Branches to instruction
|
/// Branches to instruction
|
||||||
pub branch_to: Option<ObjInsBranchTo>,
|
pub branch_to: Option<InstructionBranchTo>,
|
||||||
/// Arg diffs (only contains non-PlainText args)
|
/// Arg diffs
|
||||||
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
|
pub arg_diff: Vec<InstructionArgDiffIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||||
pub enum ObjInsDiffKind {
|
pub enum InstructionDiffKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
OpMismatch,
|
OpMismatch,
|
||||||
@@ -80,15 +78,22 @@ pub enum ObjInsDiffKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ObjDataDiff {
|
pub struct DataDiff {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
pub kind: ObjDataDiffKind,
|
pub kind: DataDiffKind,
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DataRelocationDiff {
|
||||||
|
pub reloc: Relocation,
|
||||||
|
pub kind: DataDiffKind,
|
||||||
|
pub range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||||
pub enum ObjDataDiffKind {
|
pub enum DataDiffKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Replace,
|
Replace,
|
||||||
@@ -96,126 +101,102 @@ pub enum ObjDataDiffKind {
|
|||||||
Insert,
|
Insert,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
/// Index of the argument diff for coloring.
|
||||||
pub struct ObjInsArgDiff {
|
#[repr(transparent)]
|
||||||
/// Incrementing index for coloring
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub idx: usize,
|
pub struct InstructionArgDiffIndex(pub Option<NonZeroU32>);
|
||||||
|
|
||||||
|
impl InstructionArgDiffIndex {
|
||||||
|
pub const NONE: Self = Self(None);
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(idx: u32) -> Self {
|
||||||
|
Self(Some(unsafe { NonZeroU32::new_unchecked(idx.saturating_add(1)) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self) -> Option<u32> { self.0.map(|idx| idx.get() - 1) }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_some(&self) -> bool { self.0.is_some() }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_none(&self) -> bool { self.0.is_none() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjInsBranchFrom {
|
pub struct InstructionBranchFrom {
|
||||||
/// Source instruction indices
|
/// Source instruction indices
|
||||||
pub ins_idx: Vec<usize>,
|
pub ins_idx: Vec<u32>,
|
||||||
/// Incrementing index for coloring
|
/// Incrementing index for coloring
|
||||||
pub branch_idx: usize,
|
pub branch_idx: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjInsBranchTo {
|
pub struct InstructionBranchTo {
|
||||||
/// Target instruction index
|
/// Target instruction index
|
||||||
pub ins_idx: usize,
|
pub ins_idx: u32,
|
||||||
/// Incrementing index for coloring
|
/// Incrementing index for coloring
|
||||||
pub branch_idx: usize,
|
pub branch_idx: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ObjDiff {
|
pub struct ObjectDiff {
|
||||||
|
/// A list of all symbol diffs in the object.
|
||||||
|
pub symbols: Vec<SymbolDiff>,
|
||||||
/// A list of all section diffs in the object.
|
/// A list of all section diffs in the object.
|
||||||
pub sections: Vec<ObjSectionDiff>,
|
pub sections: Vec<SectionDiff>,
|
||||||
/// Common BSS symbols don't live in a section, so they're stored separately.
|
|
||||||
pub common: Vec<ObjSymbolDiff>,
|
|
||||||
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols
|
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols
|
||||||
/// that are being mapped to the other object.
|
/// that are being mapped to the other object.
|
||||||
pub mapping_symbols: Vec<ObjSymbolDiff>,
|
pub mapping_symbols: Vec<MappingSymbolDiff>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjDiff {
|
impl ObjectDiff {
|
||||||
pub fn new_from_obj(obj: &ObjInfo) -> Self {
|
pub fn new_from_obj(obj: &Object) -> Self {
|
||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
|
symbols: Vec::with_capacity(obj.symbols.len()),
|
||||||
sections: Vec::with_capacity(obj.sections.len()),
|
sections: Vec::with_capacity(obj.sections.len()),
|
||||||
common: Vec::with_capacity(obj.common.len()),
|
|
||||||
mapping_symbols: vec![],
|
mapping_symbols: vec![],
|
||||||
};
|
};
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
for _ in obj.symbols.iter() {
|
||||||
let mut symbols = Vec::with_capacity(section.symbols.len());
|
result.symbols.push(SymbolDiff {
|
||||||
for (symbol_idx, _) in section.symbols.iter().enumerate() {
|
|
||||||
symbols.push(ObjSymbolDiff {
|
|
||||||
symbol_ref: SymbolRef { section_idx, symbol_idx },
|
|
||||||
target_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
result.sections.push(ObjSectionDiff {
|
for _ in obj.sections.iter() {
|
||||||
symbols,
|
result.sections.push(SectionDiff {
|
||||||
data_diff: vec![ObjDataDiff {
|
// target_section: None,
|
||||||
data: section.data.clone(),
|
|
||||||
kind: ObjDataDiffKind::None,
|
|
||||||
len: section.data.len(),
|
|
||||||
symbol: section.name.clone(),
|
|
||||||
}],
|
|
||||||
match_percent: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
|
||||||
result.common.push(ObjSymbolDiff {
|
|
||||||
symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx },
|
|
||||||
target_symbol: None,
|
|
||||||
instructions: vec![],
|
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
|
data_diff: vec![],
|
||||||
|
reloc_diff: vec![],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn section_diff(&self, section_idx: usize) -> &ObjSectionDiff {
|
|
||||||
&self.sections[section_idx]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[derive(Debug, Default)]
|
||||||
pub fn section_diff_mut(&mut self, section_idx: usize) -> &mut ObjSectionDiff {
|
|
||||||
&mut self.sections[section_idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
|
|
||||||
if symbol_ref.section_idx == SECTION_COMMON {
|
|
||||||
&self.common[symbol_ref.symbol_idx]
|
|
||||||
} else {
|
|
||||||
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
|
|
||||||
if symbol_ref.section_idx == SECTION_COMMON {
|
|
||||||
&mut self.common[symbol_ref.symbol_idx]
|
|
||||||
} else {
|
|
||||||
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DiffObjsResult {
|
pub struct DiffObjsResult {
|
||||||
pub left: Option<ObjDiff>,
|
pub left: Option<ObjectDiff>,
|
||||||
pub right: Option<ObjDiff>,
|
pub right: Option<ObjectDiff>,
|
||||||
pub prev: Option<ObjDiff>,
|
pub prev: Option<ObjectDiff>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_objs(
|
pub fn diff_objs(
|
||||||
|
left: Option<&Object>,
|
||||||
|
right: Option<&Object>,
|
||||||
|
prev: Option<&Object>,
|
||||||
diff_config: &DiffObjConfig,
|
diff_config: &DiffObjConfig,
|
||||||
mapping_config: &MappingConfig,
|
mapping_config: &MappingConfig,
|
||||||
left: Option<&ObjInfo>,
|
|
||||||
right: Option<&ObjInfo>,
|
|
||||||
prev: Option<&ObjInfo>,
|
|
||||||
) -> Result<DiffObjsResult> {
|
) -> Result<DiffObjsResult> {
|
||||||
let symbol_matches = matching_symbols(left, right, prev, mapping_config)?;
|
let symbol_matches = matching_symbols(left, right, prev, mapping_config)?;
|
||||||
let section_matches = matching_sections(left, right)?;
|
let section_matches = matching_sections(left, right)?;
|
||||||
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut left = left.map(|p| (p, ObjectDiff::new_from_obj(p)));
|
||||||
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut right = right.map(|p| (p, ObjectDiff::new_from_obj(p)));
|
||||||
let mut prev = prev.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut prev = prev.map(|p| (p, ObjectDiff::new_from_obj(p)));
|
||||||
|
|
||||||
for symbol_match in symbol_matches {
|
for symbol_match in symbol_matches {
|
||||||
match symbol_match {
|
match symbol_match {
|
||||||
@@ -228,87 +209,76 @@ pub fn diff_objs(
|
|||||||
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 {
|
||||||
ObjSectionKind::Code => {
|
SectionKind::Code => {
|
||||||
let left_code =
|
|
||||||
process_code_symbol(left_obj, left_symbol_ref, diff_config)?;
|
|
||||||
let right_code =
|
|
||||||
process_code_symbol(right_obj, right_symbol_ref, diff_config)?;
|
|
||||||
let (left_diff, right_diff) = diff_code(
|
let (left_diff, right_diff) = diff_code(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
&left_code,
|
|
||||||
&right_code,
|
|
||||||
left_symbol_ref,
|
left_symbol_ref,
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
diff_config,
|
diff_config,
|
||||||
)?;
|
)?;
|
||||||
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff;
|
left_out.symbols[left_symbol_ref] = left_diff;
|
||||||
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff;
|
right_out.symbols[right_symbol_ref] = right_diff;
|
||||||
|
|
||||||
if let Some(prev_symbol_ref) = prev_symbol_ref {
|
if let Some(prev_symbol_ref) = prev_symbol_ref {
|
||||||
let (prev_obj, prev_out) = prev.as_mut().unwrap();
|
let (_prev_obj, prev_out) = prev.as_mut().unwrap();
|
||||||
let prev_code =
|
|
||||||
process_code_symbol(prev_obj, prev_symbol_ref, diff_config)?;
|
|
||||||
let (_, prev_diff) = diff_code(
|
let (_, prev_diff) = diff_code(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
&right_code,
|
|
||||||
&prev_code,
|
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
prev_symbol_ref,
|
prev_symbol_ref,
|
||||||
diff_config,
|
diff_config,
|
||||||
)?;
|
)?;
|
||||||
*prev_out.symbol_diff_mut(prev_symbol_ref) = prev_diff;
|
prev_out.symbols[prev_symbol_ref] = prev_diff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data => {
|
SectionKind::Data => {
|
||||||
let (left_diff, right_diff) = diff_data_symbol(
|
let (left_diff, right_diff) = diff_data_symbol(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
left_symbol_ref,
|
left_symbol_ref,
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
)?;
|
)?;
|
||||||
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff;
|
left_out.symbols[left_symbol_ref] = left_diff;
|
||||||
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff;
|
right_out.symbols[right_symbol_ref] = right_diff;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Bss => {
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
let (left_diff, right_diff) = diff_bss_symbol(
|
let (left_diff, right_diff) = diff_bss_symbol(
|
||||||
left_obj,
|
left_obj,
|
||||||
right_obj,
|
right_obj,
|
||||||
left_symbol_ref,
|
left_symbol_ref,
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
)?;
|
)?;
|
||||||
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff;
|
left_out.symbols[left_symbol_ref] = left_diff;
|
||||||
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff;
|
right_out.symbols[right_symbol_ref] = right_diff;
|
||||||
}
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => {
|
SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => {
|
||||||
let (left_obj, left_out) = left.as_mut().unwrap();
|
let (left_obj, left_out) = left.as_mut().unwrap();
|
||||||
match section_kind {
|
match section_kind {
|
||||||
ObjSectionKind::Code => {
|
SectionKind::Code => {
|
||||||
let code = process_code_symbol(left_obj, left_symbol_ref, diff_config)?;
|
left_out.symbols[left_symbol_ref] =
|
||||||
*left_out.symbol_diff_mut(left_symbol_ref) =
|
no_diff_code(left_obj, left_symbol_ref, diff_config)?;
|
||||||
no_diff_code(&code, left_symbol_ref)?;
|
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
|
||||||
*left_out.symbol_diff_mut(left_symbol_ref) =
|
// Nothing needs to be done
|
||||||
no_diff_symbol(left_obj, left_symbol_ref);
|
|
||||||
}
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => {
|
SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => {
|
||||||
let (right_obj, right_out) = right.as_mut().unwrap();
|
let (right_obj, right_out) = right.as_mut().unwrap();
|
||||||
match section_kind {
|
match section_kind {
|
||||||
ObjSectionKind::Code => {
|
SectionKind::Code => {
|
||||||
let code = process_code_symbol(right_obj, right_symbol_ref, diff_config)?;
|
right_out.symbols[right_symbol_ref] =
|
||||||
*right_out.symbol_diff_mut(right_symbol_ref) =
|
no_diff_code(right_obj, right_symbol_ref, diff_config)?;
|
||||||
no_diff_code(&code, right_symbol_ref)?;
|
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
|
||||||
*right_out.symbol_diff_mut(right_symbol_ref) =
|
// Nothing needs to be done
|
||||||
no_diff_symbol(right_obj, right_symbol_ref);
|
|
||||||
}
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SymbolMatch { left: None, right: None, .. } => {
|
SymbolMatch { left: None, right: None, .. } => {
|
||||||
@@ -326,45 +296,44 @@ pub fn diff_objs(
|
|||||||
{
|
{
|
||||||
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();
|
||||||
let left_section = &left_obj.sections[left_section_idx];
|
|
||||||
let right_section = &right_obj.sections[right_section_idx];
|
|
||||||
match section_kind {
|
match section_kind {
|
||||||
ObjSectionKind::Code => {
|
SectionKind::Code => {
|
||||||
let left_section_diff = left_out.section_diff(left_section_idx);
|
|
||||||
let right_section_diff = right_out.section_diff(right_section_idx);
|
|
||||||
let (left_diff, right_diff) = diff_generic_section(
|
let (left_diff, right_diff) = diff_generic_section(
|
||||||
left_section,
|
left_obj,
|
||||||
right_section,
|
right_obj,
|
||||||
left_section_diff,
|
left_out,
|
||||||
right_section_diff,
|
right_out,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
)?;
|
)?;
|
||||||
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data => {
|
SectionKind::Data => {
|
||||||
let left_section_diff = left_out.section_diff(left_section_idx);
|
|
||||||
let right_section_diff = right_out.section_diff(right_section_idx);
|
|
||||||
let (left_diff, right_diff) = diff_data_section(
|
let (left_diff, right_diff) = diff_data_section(
|
||||||
left_section,
|
left_obj,
|
||||||
right_section,
|
right_obj,
|
||||||
left_section_diff,
|
left_out,
|
||||||
right_section_diff,
|
right_out,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
)?;
|
)?;
|
||||||
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Bss => {
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
let left_section_diff = left_out.section_diff(left_section_idx);
|
|
||||||
let right_section_diff = right_out.section_diff(right_section_idx);
|
|
||||||
let (left_diff, right_diff) = diff_bss_section(
|
let (left_diff, right_diff) = diff_bss_section(
|
||||||
left_section,
|
left_obj,
|
||||||
right_section,
|
right_obj,
|
||||||
left_section_diff,
|
left_out,
|
||||||
right_section_diff,
|
right_out,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
)?;
|
)?;
|
||||||
left_out.section_diff_mut(left_section_idx).merge(left_diff);
|
left_out.sections[left_section_idx] = left_diff;
|
||||||
right_out.section_diff_mut(right_section_idx).merge(right_diff);
|
right_out.sections[right_section_idx] = right_diff;
|
||||||
}
|
}
|
||||||
|
SectionKind::Unknown => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,54 +360,46 @@ pub fn diff_objs(
|
|||||||
/// symbols in the other object that match the selected symbol's section and kind. This allows
|
/// symbols in the other object that match the selected symbol's section and kind. This allows
|
||||||
/// us to display match percentages for all symbols in the other object that could be selected.
|
/// us to display match percentages for all symbols in the other object that could be selected.
|
||||||
fn generate_mapping_symbols(
|
fn generate_mapping_symbols(
|
||||||
base_obj: &ObjInfo,
|
base_obj: &Object,
|
||||||
base_name: &str,
|
base_name: &str,
|
||||||
target_obj: &ObjInfo,
|
target_obj: &Object,
|
||||||
target_out: &mut ObjDiff,
|
target_out: &mut ObjectDiff,
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let (base_section, _base_symbol) = base_obj.section_symbol(base_symbol_ref);
|
let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]);
|
||||||
let Some(base_section) = base_section else {
|
for (target_symbol_index, target_symbol) in target_obj.symbols.iter().enumerate() {
|
||||||
return Ok(());
|
if symbol_section_kind(target_obj, target_symbol) != base_section_kind {
|
||||||
};
|
continue;
|
||||||
let base_code = match base_section.kind {
|
|
||||||
ObjSectionKind::Code => Some(process_code_symbol(base_obj, base_symbol_ref, config)?),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
for (target_section_index, target_section) in
|
|
||||||
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
|
|
||||||
{
|
|
||||||
for (target_symbol_index, _target_symbol) in target_section.symbols.iter().enumerate() {
|
|
||||||
let target_symbol_ref =
|
|
||||||
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
|
|
||||||
match base_section.kind {
|
|
||||||
ObjSectionKind::Code => {
|
|
||||||
let target_code = process_code_symbol(target_obj, target_symbol_ref, config)?;
|
|
||||||
let (left_diff, _right_diff) = diff_code(
|
|
||||||
target_obj,
|
|
||||||
base_obj,
|
|
||||||
&target_code,
|
|
||||||
base_code.as_ref().unwrap(),
|
|
||||||
target_symbol_ref,
|
|
||||||
base_symbol_ref,
|
|
||||||
config,
|
|
||||||
)?;
|
|
||||||
target_out.mapping_symbols.push(left_diff);
|
|
||||||
}
|
}
|
||||||
ObjSectionKind::Data => {
|
match base_section_kind {
|
||||||
|
SectionKind::Code => {
|
||||||
let (left_diff, _right_diff) =
|
let (left_diff, _right_diff) =
|
||||||
diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
diff_code(target_obj, base_obj, target_symbol_index, base_symbol_ref, config)?;
|
||||||
target_out.mapping_symbols.push(left_diff);
|
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||||
|
symbol_index: target_symbol_index,
|
||||||
|
symbol_diff: left_diff,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ObjSectionKind::Bss => {
|
SectionKind::Data => {
|
||||||
let (left_diff, _right_diff) =
|
let (left_diff, _right_diff) =
|
||||||
diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
diff_data_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
|
||||||
target_out.mapping_symbols.push(left_diff);
|
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||||
|
symbol_index: target_symbol_index,
|
||||||
|
symbol_diff: left_diff,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
SectionKind::Bss | SectionKind::Common => {
|
||||||
|
let (left_diff, _right_diff) =
|
||||||
|
diff_bss_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
|
||||||
|
target_out.mapping_symbols.push(MappingSymbolDiff {
|
||||||
|
symbol_index: target_symbol_index,
|
||||||
|
symbol_diff: left_diff,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
SectionKind::Unknown => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -446,48 +407,40 @@ fn generate_mapping_symbols(
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
struct SymbolMatch {
|
struct SymbolMatch {
|
||||||
left: Option<SymbolRef>,
|
left: Option<usize>,
|
||||||
right: Option<SymbolRef>,
|
right: Option<usize>,
|
||||||
prev: Option<SymbolRef>,
|
prev: Option<usize>,
|
||||||
section_kind: ObjSectionKind,
|
section_kind: SectionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
struct SectionMatch {
|
struct SectionMatch {
|
||||||
left: Option<usize>,
|
left: Option<usize>,
|
||||||
right: Option<usize>,
|
right: Option<usize>,
|
||||||
section_kind: ObjSectionKind,
|
section_kind: SectionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
#[serde(default)]
|
|
||||||
pub struct MappingConfig {
|
pub struct MappingConfig {
|
||||||
/// Manual symbol mappings
|
/// Manual symbol mappings
|
||||||
pub mappings: SymbolMappings,
|
pub mappings: BTreeMap<String, String>,
|
||||||
/// The right object symbol name that we're selecting a left symbol for
|
/// The right object symbol name that we're selecting a left symbol for
|
||||||
pub selecting_left: Option<String>,
|
pub selecting_left: Option<String>,
|
||||||
/// The left object symbol name that we're selecting a right symbol for
|
/// The left object symbol name that we're selecting a right symbol for
|
||||||
pub selecting_right: Option<String>,
|
pub selecting_right: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
fn symbol_ref_by_name(obj: &Object, name: &str) -> Option<usize> {
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
obj.symbols.iter().position(|s| s.name == name)
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
|
||||||
if symbol.name == name {
|
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_symbol_mappings(
|
fn apply_symbol_mappings(
|
||||||
left: &ObjInfo,
|
left: &Object,
|
||||||
right: &ObjInfo,
|
right: &Object,
|
||||||
mapping_config: &MappingConfig,
|
mapping_config: &MappingConfig,
|
||||||
left_used: &mut HashSet<SymbolRef>,
|
left_used: &mut BTreeSet<usize>,
|
||||||
right_used: &mut HashSet<SymbolRef>,
|
right_used: &mut BTreeSet<usize>,
|
||||||
matches: &mut Vec<SymbolMatch>,
|
matches: &mut Vec<SymbolMatch>,
|
||||||
) -> 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
|
||||||
@@ -505,52 +458,62 @@ fn apply_symbol_mappings(
|
|||||||
|
|
||||||
// Apply manual symbol mappings
|
// Apply manual symbol mappings
|
||||||
for (left_name, right_name) in &mapping_config.mappings {
|
for (left_name, right_name) in &mapping_config.mappings {
|
||||||
let Some(left_symbol) = symbol_ref_by_name(left, left_name) else {
|
let Some(left_symbol_index) = symbol_ref_by_name(left, left_name) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if left_used.contains(&left_symbol) {
|
if left_used.contains(&left_symbol_index) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(right_symbol) = symbol_ref_by_name(right, right_name) else {
|
let Some(right_symbol_index) = symbol_ref_by_name(right, right_name) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if right_used.contains(&right_symbol) {
|
if right_used.contains(&right_symbol_index) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let left_section = &left.sections[left_symbol.section_idx];
|
let left_section_kind = left
|
||||||
let right_section = &right.sections[right_symbol.section_idx];
|
.symbols
|
||||||
if left_section.kind != right_section.kind {
|
.get(left_symbol_index)
|
||||||
|
.and_then(|s| s.section)
|
||||||
|
.and_then(|section_index| left.sections.get(section_index))
|
||||||
|
.map_or(SectionKind::Unknown, |s| s.kind);
|
||||||
|
let right_section_kind = right
|
||||||
|
.symbols
|
||||||
|
.get(right_symbol_index)
|
||||||
|
.and_then(|s| s.section)
|
||||||
|
.and_then(|section_index| right.sections.get(section_index))
|
||||||
|
.map_or(SectionKind::Unknown, |s| s.kind);
|
||||||
|
if left_section_kind != right_section_kind {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
|
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
|
||||||
left_name,
|
left_name,
|
||||||
left_section.kind,
|
left_section_kind,
|
||||||
right_name,
|
right_name,
|
||||||
right_section.kind
|
right_section_kind
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
matches.push(SymbolMatch {
|
matches.push(SymbolMatch {
|
||||||
left: Some(left_symbol),
|
left: Some(left_symbol_index),
|
||||||
right: Some(right_symbol),
|
right: Some(right_symbol_index),
|
||||||
prev: None, // TODO
|
prev: None, // TODO
|
||||||
section_kind: left_section.kind,
|
section_kind: left_section_kind,
|
||||||
});
|
});
|
||||||
left_used.insert(left_symbol);
|
left_used.insert(left_symbol_index);
|
||||||
right_used.insert(right_symbol);
|
right_used.insert(right_symbol_index);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find matching symbols between each object.
|
/// Find matching symbols between each object.
|
||||||
fn matching_symbols(
|
fn matching_symbols(
|
||||||
left: Option<&ObjInfo>,
|
left: Option<&Object>,
|
||||||
right: Option<&ObjInfo>,
|
right: Option<&Object>,
|
||||||
prev: Option<&ObjInfo>,
|
prev: Option<&Object>,
|
||||||
mappings: &MappingConfig,
|
mappings: &MappingConfig,
|
||||||
) -> Result<Vec<SymbolMatch>> {
|
) -> Result<Vec<SymbolMatch>> {
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
let mut left_used = HashSet::new();
|
let mut left_used = BTreeSet::new();
|
||||||
let mut right_used = HashSet::new();
|
let mut right_used = BTreeSet::new();
|
||||||
if let Some(left) = left {
|
if let Some(left) = left {
|
||||||
if let Some(right) = right {
|
if let Some(right) = right {
|
||||||
apply_symbol_mappings(
|
apply_symbol_mappings(
|
||||||
@@ -562,34 +525,19 @@ fn matching_symbols(
|
|||||||
&mut matches,
|
&mut matches,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for (section_idx, section) in left.sections.iter().enumerate() {
|
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
let section_kind = symbol_section_kind(left, symbol);
|
||||||
let symbol_ref = SymbolRef { section_idx, symbol_idx };
|
if section_kind == SectionKind::Unknown {
|
||||||
if left_used.contains(&symbol_ref) {
|
continue;
|
||||||
|
}
|
||||||
|
if left_used.contains(&symbol_idx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let symbol_match = SymbolMatch {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(symbol_ref),
|
left: Some(symbol_idx),
|
||||||
right: find_symbol(right, symbol, section, Some(&right_used)),
|
right: find_symbol(right, left, symbol, Some(&right_used)),
|
||||||
prev: find_symbol(prev, symbol, section, None),
|
prev: find_symbol(prev, left, symbol, None),
|
||||||
section_kind: section.kind,
|
section_kind,
|
||||||
};
|
|
||||||
matches.push(symbol_match);
|
|
||||||
if let Some(right) = symbol_match.right {
|
|
||||||
right_used.insert(right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
|
||||||
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
|
|
||||||
if left_used.contains(&symbol_ref) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let symbol_match = SymbolMatch {
|
|
||||||
left: Some(symbol_ref),
|
|
||||||
right: find_common_symbol(right, symbol),
|
|
||||||
prev: find_common_symbol(prev, symbol),
|
|
||||||
section_kind: ObjSectionKind::Bss,
|
|
||||||
};
|
};
|
||||||
matches.push(symbol_match);
|
matches.push(symbol_match);
|
||||||
if let Some(right) = symbol_match.right {
|
if let Some(right) = symbol_match.right {
|
||||||
@@ -598,83 +546,84 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(right) = right {
|
if let Some(right) = right {
|
||||||
for (section_idx, section) in right.sections.iter().enumerate() {
|
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
let section_kind = symbol_section_kind(right, symbol);
|
||||||
let symbol_ref = SymbolRef { section_idx, symbol_idx };
|
if section_kind == SectionKind::Unknown {
|
||||||
if right_used.contains(&symbol_ref) {
|
continue;
|
||||||
|
}
|
||||||
|
if right_used.contains(&symbol_idx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
matches.push(SymbolMatch {
|
matches.push(SymbolMatch {
|
||||||
left: None,
|
left: None,
|
||||||
right: Some(symbol_ref),
|
right: Some(symbol_idx),
|
||||||
prev: find_symbol(prev, symbol, section, None),
|
prev: find_symbol(prev, right, symbol, None),
|
||||||
section_kind: section.kind,
|
section_kind,
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (symbol_idx, symbol) in right.common.iter().enumerate() {
|
|
||||||
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
|
|
||||||
if right_used.contains(&symbol_ref) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
matches.push(SymbolMatch {
|
|
||||||
left: None,
|
|
||||||
right: Some(symbol_ref),
|
|
||||||
prev: find_common_symbol(prev, symbol),
|
|
||||||
section_kind: ObjSectionKind::Bss,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(matches)
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmatched_symbols<'section, 'used>(
|
fn unmatched_symbols<'obj, 'used>(
|
||||||
section: &'section ObjSection,
|
obj: &'obj Object,
|
||||||
section_idx: usize,
|
used: Option<&'used BTreeSet<usize>>,
|
||||||
used: Option<&'used HashSet<SymbolRef>>,
|
) -> impl Iterator<Item = (usize, &'obj Symbol)> + 'used
|
||||||
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
|
|
||||||
where
|
where
|
||||||
'section: 'used,
|
'obj: 'used,
|
||||||
{
|
{
|
||||||
section.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| {
|
obj.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| {
|
||||||
// Skip symbols that have already been matched
|
// Skip symbols that have already been matched
|
||||||
!used.map(|u| u.contains(&SymbolRef { section_idx, symbol_idx })).unwrap_or(false)
|
!used.is_some_and(|u| u.contains(&symbol_idx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
|
||||||
|
if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
|
||||||
|
Some((section.name.as_str(), section.kind))
|
||||||
|
} else if symbol.flags.contains(SymbolFlag::Common) {
|
||||||
|
Some((".comm", SectionKind::Common))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
|
||||||
|
match symbol.section {
|
||||||
|
Some(section_index) => obj.sections[section_index].kind,
|
||||||
|
None if symbol.flags.contains(SymbolFlag::Common) => SectionKind::Common,
|
||||||
|
None => SectionKind::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_symbol(
|
fn find_symbol(
|
||||||
obj: Option<&ObjInfo>,
|
obj: Option<&Object>,
|
||||||
in_symbol: &ObjSymbol,
|
in_obj: &Object,
|
||||||
in_section: &ObjSection,
|
in_symbol: &Symbol,
|
||||||
used: Option<&HashSet<SymbolRef>>,
|
used: Option<&BTreeSet<usize>>,
|
||||||
) -> Option<SymbolRef> {
|
) -> Option<usize> {
|
||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
|
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
|
||||||
// Try to find an exact name match
|
// Try to find an exact name match
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||||
if section.kind != in_section.kind {
|
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
|
||||||
continue;
|
}) {
|
||||||
}
|
return Some(symbol_idx);
|
||||||
if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used)
|
|
||||||
.find(|(_, symbol)| symbol.name == in_symbol.name)
|
|
||||||
{
|
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
||||||
// If they are at the same address in the same section
|
// If they are at the same address in the same section
|
||||||
if in_symbol.name.starts_with('@')
|
if in_symbol.name.starts_with('@')
|
||||||
&& matches!(in_section.kind, ObjSectionKind::Data | ObjSectionKind::Bss)
|
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
|
||||||
{
|
{
|
||||||
if let Some((section_idx, section)) =
|
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||||
obj.sections.iter().enumerate().find(|(_, s)| s.name == in_section.name)
|
let Some(section_index) = symbol.section else {
|
||||||
{
|
return false;
|
||||||
if let Some((symbol_idx, _)) =
|
};
|
||||||
unmatched_symbols(section, section_idx, used).find(|(_, symbol)| {
|
symbol.name.starts_with('@')
|
||||||
symbol.address == in_symbol.address && symbol.name.starts_with('@')
|
&& symbol.address == in_symbol.address
|
||||||
})
|
&& obj.sections[section_index].name == section_name
|
||||||
{
|
}) {
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
return Some(symbol_idx);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Match Metrowerks symbol$1234 against symbol$2345
|
// Match Metrowerks symbol$1234 against symbol$2345
|
||||||
@@ -682,41 +631,29 @@ fn find_symbol(
|
|||||||
if !suffix.chars().all(char::is_numeric) {
|
if !suffix.chars().all(char::is_numeric) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||||
if section.kind != in_section.kind {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some((symbol_idx, _)) =
|
|
||||||
unmatched_symbols(section, section_idx, used).find(|&(_, symbol)| {
|
|
||||||
if let Some((p, s)) = symbol.name.split_once('$') {
|
if let Some((p, s)) = symbol.name.split_once('$') {
|
||||||
prefix == p && s.chars().all(char::is_numeric)
|
prefix == p
|
||||||
|
&& s.chars().all(char::is_numeric)
|
||||||
|
&& symbol_section_kind(obj, symbol) == section_kind
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
})
|
}) {
|
||||||
{
|
return Some(symbol_idx);
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option<SymbolRef> {
|
|
||||||
let obj = obj?;
|
|
||||||
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
|
|
||||||
if symbol.name == in_symbol.name {
|
|
||||||
return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find matching sections between each object.
|
/// Find matching sections between each object.
|
||||||
fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<Vec<SectionMatch>> {
|
fn matching_sections(left: Option<&Object>, right: Option<&Object>) -> Result<Vec<SectionMatch>> {
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
if let Some(left) = left {
|
if let Some(left) = left {
|
||||||
for (section_idx, section) in left.sections.iter().enumerate() {
|
for (section_idx, section) in left.sections.iter().enumerate() {
|
||||||
|
if section.kind == SectionKind::Unknown {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
matches.push(SectionMatch {
|
matches.push(SectionMatch {
|
||||||
left: Some(section_idx),
|
left: Some(section_idx),
|
||||||
right: find_section(right, §ion.name, section.kind),
|
right: find_section(right, §ion.name, section.kind),
|
||||||
@@ -726,6 +663,9 @@ fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<
|
|||||||
}
|
}
|
||||||
if let Some(right) = right {
|
if let Some(right) = right {
|
||||||
for (section_idx, section) in right.sections.iter().enumerate() {
|
for (section_idx, section) in right.sections.iter().enumerate() {
|
||||||
|
if section.kind == SectionKind::Unknown {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if matches.iter().any(|m| m.right == Some(section_idx)) {
|
if matches.iter().any(|m| m.right == Some(section_idx)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -739,14 +679,6 @@ fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<
|
|||||||
Ok(matches)
|
Ok(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_section(obj: Option<&ObjInfo>, name: &str, section_kind: ObjSectionKind) -> Option<usize> {
|
fn find_section(obj: Option<&Object>, name: &str, section_kind: SectionKind) -> Option<usize> {
|
||||||
for (section_idx, section) in obj?.sections.iter().enumerate() {
|
obj?.sections.iter().position(|s| s.kind == section_kind && s.name == name)
|
||||||
if section.kind != section_kind {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if section.name == name {
|
|
||||||
return Some(section_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use self_update::{
|
|||||||
update::{Release, ReleaseUpdate},
|
update::{Release, ReleaseUpdate},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::jobs::{start_job, update_status, Job, JobContext, JobResult, JobState};
|
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
|
||||||
|
|
||||||
pub struct CheckUpdateConfig {
|
pub struct CheckUpdateConfig {
|
||||||
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
use std::{fs, path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
use std::{fs, sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
build::{run_make, BuildConfig, BuildStatus},
|
build::{BuildConfig, BuildStatus, run_make},
|
||||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CreateScratchConfig {
|
pub struct CreateScratchConfig {
|
||||||
pub build_config: BuildConfig,
|
pub build_config: BuildConfig,
|
||||||
pub context_path: Option<PathBuf>,
|
pub context_path: Option<Utf8UnixPathBuf>,
|
||||||
pub build_context: bool,
|
pub build_context: bool,
|
||||||
|
|
||||||
// Scratch fields
|
// Scratch fields
|
||||||
@@ -18,7 +19,7 @@ pub struct CreateScratchConfig {
|
|||||||
pub platform: String,
|
pub platform: String,
|
||||||
pub compiler_flags: String,
|
pub compiler_flags: String,
|
||||||
pub function_name: String,
|
pub function_name: String,
|
||||||
pub target_obj: PathBuf,
|
pub target_obj: Utf8PlatformPathBuf,
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,26 +48,25 @@ fn run_create_scratch(
|
|||||||
if let Some(context_path) = &config.context_path {
|
if let Some(context_path) = &config.context_path {
|
||||||
if config.build_context {
|
if config.build_context {
|
||||||
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
||||||
match run_make(&config.build_config, context_path) {
|
match run_make(&config.build_config, context_path.as_ref()) {
|
||||||
BuildStatus { success: true, .. } => {}
|
BuildStatus { success: true, .. } => {}
|
||||||
BuildStatus { success: false, stdout, stderr, .. } => {
|
BuildStatus { success: false, stdout, stderr, .. } => {
|
||||||
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let context_path = project_dir.join(context_path);
|
let context_path = project_dir.join(context_path.with_platform_encoding());
|
||||||
context = Some(
|
context = Some(
|
||||||
fs::read_to_string(&context_path)
|
fs::read_to_string(&context_path)
|
||||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?,
|
.map_err(|e| anyhow!("Failed to read {}: {}", context_path, e))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
||||||
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
||||||
let diff_flags = serde_json::to_string(&diff_flags)?;
|
let diff_flags = serde_json::to_string(&diff_flags)?;
|
||||||
let obj_path = project_dir.join(&config.target_obj);
|
let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
|
||||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
.with_context(|| format!("Failed to open {}", config.target_obj))?;
|
||||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
|
||||||
let mut form = reqwest::blocking::multipart::Form::new()
|
let mut form = reqwest::blocking::multipart::Form::new()
|
||||||
.text("compiler", config.compiler.clone())
|
.text("compiler", config.compiler.clone())
|
||||||
.text("platform", config.platform.clone())
|
.text("platform", config.platform.clone())
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
|
Arc, RwLock,
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
mpsc::{Receiver, Sender, TryRecvError},
|
mpsc::{Receiver, Sender, TryRecvError},
|
||||||
Arc, RwLock,
|
|
||||||
},
|
},
|
||||||
task::Waker,
|
task::Waker,
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
use std::{path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
use std::{sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{Error, Result, bail};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
build::{run_make, BuildConfig, BuildStatus},
|
build::{BuildConfig, BuildStatus, run_make},
|
||||||
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff},
|
diff::{DiffObjConfig, MappingConfig, ObjectDiff, diff_objs},
|
||||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
|
||||||
obj::{read, ObjInfo},
|
obj::{Object, read},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ObjDiffConfig {
|
pub struct ObjDiffConfig {
|
||||||
pub build_config: BuildConfig,
|
pub build_config: BuildConfig,
|
||||||
pub build_base: bool,
|
pub build_base: bool,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
pub mapping_config: MappingConfig,
|
pub mapping_config: MappingConfig,
|
||||||
}
|
}
|
||||||
@@ -23,8 +24,8 @@ pub struct ObjDiffConfig {
|
|||||||
pub struct ObjDiffResult {
|
pub struct ObjDiffResult {
|
||||||
pub first_status: BuildStatus,
|
pub first_status: BuildStatus,
|
||||||
pub second_status: BuildStatus,
|
pub second_status: BuildStatus,
|
||||||
pub first_obj: Option<(ObjInfo, ObjDiff)>,
|
pub first_obj: Option<(Object, ObjectDiff)>,
|
||||||
pub second_obj: Option<(ObjInfo, ObjDiff)>,
|
pub second_obj: Option<(Object, ObjectDiff)>,
|
||||||
pub time: OffsetDateTime,
|
pub time: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,22 +43,20 @@ fn run_build(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||||
if let Some(target_path) = &config.target_path {
|
if let Some(target_path) = &config.target_path {
|
||||||
target_path_rel = Some(target_path.strip_prefix(project_dir).map_err(|_| {
|
target_path_rel = match target_path.strip_prefix(project_dir) {
|
||||||
anyhow!(
|
Ok(p) => Some(p.with_unix_encoding()),
|
||||||
"Target path '{}' doesn't begin with '{}'",
|
Err(_) => {
|
||||||
target_path.display(),
|
bail!("Target path '{}' doesn't begin with '{}'", target_path, project_dir);
|
||||||
project_dir.display()
|
}
|
||||||
)
|
};
|
||||||
})?);
|
|
||||||
}
|
}
|
||||||
if let Some(base_path) = &config.base_path {
|
if let Some(base_path) = &config.base_path {
|
||||||
base_path_rel = Some(base_path.strip_prefix(project_dir).map_err(|_| {
|
base_path_rel = match base_path.strip_prefix(project_dir) {
|
||||||
anyhow!(
|
Ok(p) => Some(p.with_unix_encoding()),
|
||||||
"Base path '{}' doesn't begin with '{}'",
|
Err(_) => {
|
||||||
base_path.display(),
|
bail!("Base path '{}' doesn't begin with '{}'", base_path, project_dir);
|
||||||
project_dir.display()
|
}
|
||||||
)
|
};
|
||||||
})?);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,13 +79,13 @@ fn run_build(
|
|||||||
Some(target_path_rel) if config.build_target => {
|
Some(target_path_rel) if config.build_target => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Building target {}", target_path_rel.display()),
|
format!("Building target {}", target_path_rel),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
run_make(&config.build_config, target_path_rel)
|
run_make(&config.build_config, target_path_rel.as_ref())
|
||||||
}
|
}
|
||||||
_ => BuildStatus::default(),
|
_ => BuildStatus::default(),
|
||||||
};
|
};
|
||||||
@@ -95,13 +94,13 @@ fn run_build(
|
|||||||
Some(base_path_rel) if config.build_base => {
|
Some(base_path_rel) if config.build_base => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Building base {}", base_path_rel.display()),
|
format!("Building base {}", base_path_rel),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
run_make(&config.build_config, base_path_rel)
|
run_make(&config.build_config, base_path_rel.as_ref())
|
||||||
}
|
}
|
||||||
_ => BuildStatus::default(),
|
_ => BuildStatus::default(),
|
||||||
};
|
};
|
||||||
@@ -112,18 +111,18 @@ fn run_build(
|
|||||||
Some(target_path) if first_status.success => {
|
Some(target_path) if first_status.success => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Loading target {}", target_path.display()),
|
format!("Loading target {}", target_path),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
match read::read(target_path, &config.diff_obj_config) {
|
match read::read(target_path.as_ref(), &config.diff_obj_config) {
|
||||||
Ok(obj) => Some(obj),
|
Ok(obj) => Some(obj),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
first_status = BuildStatus {
|
first_status = BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
stdout: format!("Loading object '{}'", target_path.display()),
|
stdout: format!("Loading object '{}'", target_path),
|
||||||
stderr: format!("{:#}", e),
|
stderr: format!("{:#}", e),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -142,18 +141,18 @@ fn run_build(
|
|||||||
Some(base_path) if second_status.success => {
|
Some(base_path) if second_status.success => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Loading base {}", base_path.display()),
|
format!("Loading base {}", base_path),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
match read::read(base_path, &config.diff_obj_config) {
|
match read::read(base_path.as_ref(), &config.diff_obj_config) {
|
||||||
Ok(obj) => Some(obj),
|
Ok(obj) => Some(obj),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
second_status = BuildStatus {
|
second_status = BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
stdout: format!("Loading object '{}'", base_path.display()),
|
stdout: format!("Loading object '{}'", base_path),
|
||||||
stderr: format!("{:#}", e),
|
stderr: format!("{:#}", e),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -171,11 +170,11 @@ fn run_build(
|
|||||||
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
|
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
let result = diff_objs(
|
let result = diff_objs(
|
||||||
&config.diff_obj_config,
|
|
||||||
&config.mapping_config,
|
|
||||||
first_obj.as_ref(),
|
first_obj.as_ref(),
|
||||||
second_obj.as_ref(),
|
second_obj.as_ref(),
|
||||||
None,
|
None,
|
||||||
|
&config.diff_obj_config,
|
||||||
|
&config.mapping_config,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
|
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use anyhow::{Context, Result};
|
|||||||
pub use self_update; // Re-export self_update crate
|
pub use self_update; // Re-export self_update crate
|
||||||
use self_update::update::ReleaseUpdate;
|
use self_update::update::ReleaseUpdate;
|
||||||
|
|
||||||
use crate::jobs::{start_job, update_status, Job, JobContext, JobResult, JobState};
|
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
|
||||||
|
|
||||||
pub struct UpdateConfig {
|
pub struct UpdateConfig {
|
||||||
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod arch;
|
pub mod arch;
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod split_meta;
|
pub mod split_meta;
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
collections::BTreeMap,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::{fmt, num::NonZeroU32};
|
||||||
|
|
||||||
use filetime::FileTime;
|
use flagset::{FlagSet, flags};
|
||||||
use flagset::{flags, FlagSet};
|
|
||||||
use object::RelocationFlags;
|
|
||||||
use split_meta::SplitMeta;
|
|
||||||
|
|
||||||
use crate::{arch::ObjArch, util::ReallySigned};
|
use crate::{
|
||||||
|
arch::{Arch, ArchDummy},
|
||||||
|
obj::split_meta::SplitMeta,
|
||||||
|
util::ReallySigned,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
|
||||||
pub enum ObjSectionKind {
|
pub enum SectionKind {
|
||||||
|
#[default]
|
||||||
|
Unknown = -1,
|
||||||
Code,
|
Code,
|
||||||
Data,
|
Data,
|
||||||
Bss,
|
Bss,
|
||||||
|
Common,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags! {
|
flags! {
|
||||||
pub enum ObjSymbolFlags: u8 {
|
#[derive(Hash)]
|
||||||
|
pub enum SymbolFlag: u8 {
|
||||||
Global,
|
Global,
|
||||||
Local,
|
Local,
|
||||||
Weak,
|
Weak,
|
||||||
@@ -26,105 +40,192 @@ flags! {
|
|||||||
/// Has extra data associated with the symbol
|
/// Has extra data associated with the symbol
|
||||||
/// (e.g. exception table entry)
|
/// (e.g. exception table entry)
|
||||||
HasExtra,
|
HasExtra,
|
||||||
|
/// Symbol size was missing and was inferred
|
||||||
|
SizeInferred,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
|
||||||
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub type SymbolFlagSet = FlagSet<SymbolFlag>;
|
||||||
pub struct ObjSection {
|
|
||||||
|
flags! {
|
||||||
|
#[derive(Hash)]
|
||||||
|
pub enum SectionFlag: u8 {
|
||||||
|
/// Section combined from multiple input sections
|
||||||
|
Combined,
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SectionFlagSet = FlagSet<SectionFlag>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Section {
|
||||||
|
/// Unique section ID
|
||||||
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub kind: ObjSectionKind,
|
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub data: Vec<u8>,
|
pub kind: SectionKind,
|
||||||
pub orig_index: usize,
|
pub data: SectionData,
|
||||||
pub symbols: Vec<ObjSymbol>,
|
pub flags: SectionFlagSet,
|
||||||
pub relocations: Vec<ObjReloc>,
|
pub relocations: Vec<Relocation>,
|
||||||
pub virtual_address: Option<u64>,
|
|
||||||
/// Line number info (.line or .debug_line section)
|
/// Line number info (.line or .debug_line section)
|
||||||
pub line_info: BTreeMap<u64, u32>,
|
pub line_info: BTreeMap<u64, u32>,
|
||||||
|
/// Original virtual address (from .note.split section)
|
||||||
|
pub virtual_address: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct SectionData(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl core::ops::Deref for SectionData {
|
||||||
|
type Target = Vec<u8>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SectionData {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("SectionData").field(&self.0.len()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section {
|
||||||
|
pub fn data_range(&self, address: u64, size: usize) -> Option<&[u8]> {
|
||||||
|
let start = self.address;
|
||||||
|
let end = start + self.size;
|
||||||
|
if address >= start && address + size as u64 <= end {
|
||||||
|
let offset = (address - start) as usize;
|
||||||
|
Some(&self.data[offset..offset + size])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_at<'obj>(
|
||||||
|
&'obj self,
|
||||||
|
obj: &'obj Object,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
) -> Option<ResolvedRelocation<'obj>> {
|
||||||
|
match self.relocations.binary_search_by_key(&ins_ref.address, |r| r.address) {
|
||||||
|
Ok(i) => self.relocations.get(i),
|
||||||
|
Err(i) => self
|
||||||
|
.relocations
|
||||||
|
.get(i)
|
||||||
|
.take_if(|r| r.address < ins_ref.address + ins_ref.size as u64),
|
||||||
|
}
|
||||||
|
.and_then(|relocation| {
|
||||||
|
let symbol = obj.symbols.get(relocation.target_symbol)?;
|
||||||
|
Some(ResolvedRelocation { relocation, symbol })
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum ObjInsArgValue {
|
pub enum InstructionArgValue<'a> {
|
||||||
Signed(i64),
|
Signed(i64),
|
||||||
Unsigned(u64),
|
Unsigned(u64),
|
||||||
Opaque(Cow<'static, str>),
|
Opaque(Cow<'a, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInsArgValue {
|
impl InstructionArgValue<'_> {
|
||||||
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool {
|
pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b,
|
(InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
|
||||||
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b,
|
(InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
|
||||||
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b))
|
(InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
|
||||||
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b,
|
| (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
|
||||||
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b,
|
(InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_static(&self) -> InstructionArgValue<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(*v),
|
||||||
|
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(*v),
|
||||||
|
InstructionArgValue::Opaque(v) => InstructionArgValue::Opaque(v.to_string().into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ObjInsArgValue {
|
pub fn into_static(self) -> InstructionArgValue<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(v),
|
||||||
|
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(v),
|
||||||
|
InstructionArgValue::Opaque(v) => {
|
||||||
|
InstructionArgValue::Opaque(Cow::Owned(v.into_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InstructionArgValue<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
|
InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
|
||||||
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v),
|
InstructionArgValue::Unsigned(v) => write!(f, "{:#x}", v),
|
||||||
ObjInsArgValue::Opaque(v) => write!(f, "{}", v),
|
InstructionArgValue::Opaque(v) => write!(f, "{}", v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum ObjInsArg {
|
pub enum InstructionArg<'a> {
|
||||||
PlainText(Cow<'static, str>),
|
Value(InstructionArgValue<'a>),
|
||||||
Arg(ObjInsArgValue),
|
|
||||||
Reloc,
|
Reloc,
|
||||||
BranchDest(u64),
|
BranchDest(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInsArg {
|
impl InstructionArg<'_> {
|
||||||
#[inline]
|
pub fn loose_eq(&self, other: &InstructionArg) -> bool {
|
||||||
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
|
|
||||||
|
|
||||||
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
|
(InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
|
||||||
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
|
(InstructionArg::Reloc, InstructionArg::Reloc) => true,
|
||||||
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b,
|
(InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_static(&self) -> InstructionArg<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArg::Value(v) => InstructionArg::Value(v.to_static()),
|
||||||
|
InstructionArg::Reloc => InstructionArg::Reloc,
|
||||||
|
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(*v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_static(self) -> InstructionArg<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArg::Value(v) => InstructionArg::Value(v.into_static()),
|
||||||
|
InstructionArg::Reloc => InstructionArg::Reloc,
|
||||||
|
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct InstructionRef {
|
||||||
|
pub address: u64,
|
||||||
|
pub size: u8,
|
||||||
|
pub opcode: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct ScannedInstruction {
|
||||||
|
pub ins_ref: InstructionRef,
|
||||||
|
pub branch_dest: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjIns {
|
pub struct ParsedInstruction {
|
||||||
pub address: u64,
|
pub ins_ref: InstructionRef,
|
||||||
pub size: u8,
|
|
||||||
pub op: u16,
|
|
||||||
pub mnemonic: Cow<'static, str>,
|
pub mnemonic: Cow<'static, str>,
|
||||||
pub args: Vec<ObjInsArg>,
|
pub args: Vec<InstructionArg<'static>>,
|
||||||
pub reloc: Option<ObjReloc>,
|
|
||||||
pub branch_dest: Option<u64>,
|
|
||||||
/// Line number
|
|
||||||
pub line: Option<u32>,
|
|
||||||
/// Formatted instruction
|
|
||||||
pub formatted: String,
|
|
||||||
/// Original (unsimplified) instruction
|
|
||||||
pub orig: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjIns {
|
|
||||||
/// Iterate over non-PlainText arguments.
|
|
||||||
#[inline]
|
|
||||||
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> {
|
|
||||||
self.args.iter().filter(|a| !a.is_plain_text())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
pub enum ObjSymbolKind {
|
pub enum SymbolKind {
|
||||||
#[default]
|
#[default]
|
||||||
Unknown,
|
Unknown,
|
||||||
Function,
|
Function,
|
||||||
@@ -132,59 +233,149 @@ pub enum ObjSymbolKind {
|
|||||||
Section,
|
Section,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
pub struct ObjSymbol {
|
pub struct Symbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub demangled_name: Option<String>,
|
pub demangled_name: Option<String>,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub section_address: u64,
|
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub size_known: bool,
|
pub kind: SymbolKind,
|
||||||
pub kind: ObjSymbolKind,
|
pub section: Option<usize>,
|
||||||
pub flags: ObjSymbolFlagSet,
|
pub flags: SymbolFlagSet,
|
||||||
pub orig_section_index: Option<usize>,
|
/// Alignment (from Metrowerks .comment section)
|
||||||
|
pub align: Option<NonZeroU32>,
|
||||||
/// Original virtual address (from .note.split section)
|
/// Original virtual address (from .note.split section)
|
||||||
pub virtual_address: Option<u64>,
|
pub virtual_address: Option<u64>,
|
||||||
/// Original index in object symbol table
|
|
||||||
pub original_index: Option<usize>,
|
|
||||||
pub bytes: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ObjInfo {
|
#[derive(Debug)]
|
||||||
pub arch: Box<dyn ObjArch>,
|
pub struct Object {
|
||||||
pub path: Option<PathBuf>,
|
pub arch: Box<dyn Arch>,
|
||||||
pub timestamp: Option<FileTime>,
|
pub endianness: object::Endianness,
|
||||||
pub sections: Vec<ObjSection>,
|
pub symbols: Vec<Symbol>,
|
||||||
/// Common BSS symbols
|
pub sections: Vec<Section>,
|
||||||
pub common: Vec<ObjSymbol>,
|
|
||||||
/// Split object metadata (.note.split section)
|
/// Split object metadata (.note.split section)
|
||||||
pub split_meta: Option<SplitMeta>,
|
pub split_meta: Option<SplitMeta>,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub path: Option<std::path::PathBuf>,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl Default for Object {
|
||||||
pub struct ObjReloc {
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
arch: ArchDummy::new(),
|
||||||
|
endianness: object::Endianness::Little,
|
||||||
|
symbols: vec![],
|
||||||
|
sections: vec![],
|
||||||
|
split_meta: None,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
path: None,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
timestamp: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn resolve_instruction_ref(
|
||||||
|
&self,
|
||||||
|
symbol_index: usize,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
) -> Option<ResolvedInstructionRef> {
|
||||||
|
let symbol = self.symbols.get(symbol_index)?;
|
||||||
|
let section_index = symbol.section?;
|
||||||
|
let section = self.sections.get(section_index)?;
|
||||||
|
let offset = ins_ref.address.checked_sub(section.address)?;
|
||||||
|
let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
|
||||||
|
let relocation = section.relocation_at(self, ins_ref);
|
||||||
|
Some(ResolvedInstructionRef {
|
||||||
|
ins_ref,
|
||||||
|
symbol_index,
|
||||||
|
symbol,
|
||||||
|
section,
|
||||||
|
section_index,
|
||||||
|
code,
|
||||||
|
relocation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_data(&self, symbol_index: usize) -> Option<&[u8]> {
|
||||||
|
let symbol = self.symbols.get(symbol_index)?;
|
||||||
|
let section_index = symbol.section?;
|
||||||
|
let section = self.sections.get(section_index)?;
|
||||||
|
let offset = symbol.address.checked_sub(section.address)?;
|
||||||
|
section.data.get(offset as usize..offset as usize + symbol.size as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Relocation {
|
||||||
pub flags: RelocationFlags,
|
pub flags: RelocationFlags,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub target: ObjSymbol,
|
pub target_symbol: usize,
|
||||||
pub addend: i64,
|
pub addend: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct SymbolRef {
|
pub enum RelocationFlags {
|
||||||
pub section_idx: usize,
|
Elf(u32),
|
||||||
pub symbol_idx: usize,
|
Coff(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SECTION_COMMON: usize = usize::MAX - 1;
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ResolvedRelocation<'a> {
|
||||||
|
pub relocation: &'a Relocation,
|
||||||
|
pub symbol: &'a Symbol,
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjInfo {
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
pub struct ResolvedInstructionRef<'obj> {
|
||||||
if symbol_ref.section_idx == SECTION_COMMON {
|
pub ins_ref: InstructionRef,
|
||||||
let symbol = &self.common[symbol_ref.symbol_idx];
|
pub symbol_index: usize,
|
||||||
return (None, symbol);
|
pub symbol: &'obj Symbol,
|
||||||
|
pub section_index: usize,
|
||||||
|
pub section: &'obj Section,
|
||||||
|
pub code: &'obj [u8],
|
||||||
|
pub relocation: Option<ResolvedRelocation<'obj>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static DUMMY_SYMBOL: Symbol = Symbol {
|
||||||
|
name: String::new(),
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: SymbolKind::Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: SymbolFlagSet::empty(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DUMMY_SECTION: Section = Section {
|
||||||
|
id: String::new(),
|
||||||
|
name: String::new(),
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: SectionKind::Unknown,
|
||||||
|
data: SectionData(Vec::new()),
|
||||||
|
flags: SectionFlagSet::empty(),
|
||||||
|
relocations: Vec::new(),
|
||||||
|
line_info: BTreeMap::new(),
|
||||||
|
virtual_address: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Default for ResolvedInstructionRef<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ins_ref: InstructionRef::default(),
|
||||||
|
symbol_index: 0,
|
||||||
|
symbol: &DUMMY_SYMBOL,
|
||||||
|
section_index: 0,
|
||||||
|
section: &DUMMY_SECTION,
|
||||||
|
code: &[],
|
||||||
|
relocation: None,
|
||||||
}
|
}
|
||||||
let section = &self.sections[symbol_ref.section_idx];
|
|
||||||
let symbol = §ion.symbols[symbol_ref.symbol_idx];
|
|
||||||
(Some(section), symbol)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/src/obj/read.rs
|
||||||
|
expression: "(sections, symbols)"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
[
|
||||||
|
Section {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 4,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 2,
|
||||||
|
target_symbol: 1,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-combined",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Combined),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 2,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 2,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {
|
||||||
|
0: 1,
|
||||||
|
8: 2,
|
||||||
|
},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-1",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-2",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Symbol {
|
||||||
|
name: ".data",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "symbol",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 4,
|
||||||
|
size: 4,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "function",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: ".data",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{io, io::Write};
|
use alloc::{string::String, vec, vec::Vec};
|
||||||
|
|
||||||
use object::{elf::SHT_NOTE, Endian, ObjectSection};
|
use anyhow::{Result, anyhow};
|
||||||
|
use object::{Endian, ObjectSection, elf::SHT_NOTE};
|
||||||
|
|
||||||
pub const SPLITMETA_SECTION: &str = ".note.split";
|
pub const SPLITMETA_SECTION: &str = ".note.split";
|
||||||
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
|
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
|
||||||
@@ -27,10 +28,10 @@ const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
|
|||||||
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
||||||
|
|
||||||
impl SplitMeta {
|
impl SplitMeta {
|
||||||
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self>
|
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> Result<Self>
|
||||||
where E: Endian {
|
where E: Endian {
|
||||||
let mut result = SplitMeta::default();
|
let mut result = SplitMeta::default();
|
||||||
let data = section.uncompressed_data().map_err(object_io_error)?;
|
let data = section.uncompressed_data().map_err(object_error)?;
|
||||||
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
||||||
while let Some(note) = iter.next(e)? {
|
while let Some(note) = iter.next(e)? {
|
||||||
if note.name != ELF_NOTE_SPLIT {
|
if note.name != ELF_NOTE_SPLIT {
|
||||||
@@ -38,20 +39,19 @@ impl SplitMeta {
|
|||||||
}
|
}
|
||||||
match note.n_type {
|
match note.n_type {
|
||||||
NT_SPLIT_GENERATOR => {
|
NT_SPLIT_GENERATOR => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string =
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
|
||||||
result.generator = Some(string);
|
result.generator = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_NAME => {
|
NT_SPLIT_MODULE_NAME => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string =
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
|
||||||
result.module_name = Some(string);
|
result.module_name = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_ID => {
|
NT_SPLIT_MODULE_ID => {
|
||||||
result.module_id =
|
result.module_id = Some(e.read_u32_bytes(
|
||||||
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
|
note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
|
));
|
||||||
})?));
|
|
||||||
}
|
}
|
||||||
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
||||||
let vec = if is_64 {
|
let vec = if is_64 {
|
||||||
@@ -79,10 +79,11 @@ impl SplitMeta {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
if let Some(generator) = &self.generator {
|
if let Some(generator) = &self.generator {
|
||||||
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
||||||
@@ -137,10 +138,9 @@ impl SplitMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an object::read::Error to an io::Error.
|
/// Convert an object::read::Error to a String.
|
||||||
fn object_io_error(err: object::read::Error) -> io::Error {
|
#[inline]
|
||||||
io::Error::new(io::ErrorKind::InvalidData, err)
|
fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
|
||||||
}
|
|
||||||
|
|
||||||
/// An ELF note entry.
|
/// An ELF note entry.
|
||||||
struct Note<'data> {
|
struct Note<'data> {
|
||||||
@@ -161,27 +161,27 @@ where E: Endian
|
|||||||
impl<'data, E> NoteIterator<'data, E>
|
impl<'data, E> NoteIterator<'data, E>
|
||||||
where E: Endian
|
where E: Endian
|
||||||
{
|
{
|
||||||
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> {
|
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result<Self> {
|
||||||
Ok(if is_64 {
|
Ok(if is_64 {
|
||||||
NoteIterator::B64(
|
NoteIterator::B64(
|
||||||
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?,
|
object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
NoteIterator::B32(
|
NoteIterator::B32(
|
||||||
object::read::elf::NoteIterator::new(e, align as u32, data)
|
object::read::elf::NoteIterator::new(e, align as u32, data)
|
||||||
.map_err(object_io_error)?,
|
.map_err(object_error)?,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> {
|
fn next(&mut self, e: E) -> Result<Option<Note<'data>>> {
|
||||||
match self {
|
match self {
|
||||||
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
})),
|
})),
|
||||||
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
@@ -192,7 +192,8 @@ where E: Endian
|
|||||||
|
|
||||||
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
||||||
|
|
||||||
fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<()> {
|
#[cfg(feature = "std")]
|
||||||
|
fn align_data_to_4<W: std::io::Write + ?Sized>(writer: &mut W, len: usize) -> std::io::Result<()> {
|
||||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||||
if len % 4 != 0 {
|
if len % 4 != 0 {
|
||||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||||
@@ -208,10 +209,11 @@ fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<
|
|||||||
// Desc | variable size, padded to a 4 byte boundary
|
// Desc | variable size, padded to a 4 byte boundary
|
||||||
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
||||||
|
|
||||||
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
||||||
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use std::{
|
use alloc::format;
|
||||||
fmt::{LowerHex, UpperHex},
|
use core::fmt;
|
||||||
io::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, ensure};
|
||||||
use byteorder::{NativeEndian, ReadBytesExt};
|
|
||||||
use num_traits::PrimInt;
|
use num_traits::PrimInt;
|
||||||
use object::{Endian, Object};
|
use object::{Endian, Object};
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
||||||
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
pub struct ReallySigned<N: PrimInt>(pub N);
|
||||||
|
|
||||||
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:x}", num.abs());
|
let bare_hex = format!("{:x}", num.abs());
|
||||||
@@ -20,8 +17,8 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:X}", num.abs());
|
let bare_hex = format!("{:X}", num.abs());
|
||||||
@@ -29,10 +26,16 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> {
|
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
|
||||||
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?))
|
ensure!(reader.len() >= 4, "Not enough bytes to read u32");
|
||||||
|
let value = u32::from_ne_bytes(reader[..4].try_into()?);
|
||||||
|
*reader = &reader[4..];
|
||||||
|
Ok(obj_file.endianness().read_u32(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u16<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u16> {
|
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
|
||||||
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?))
|
ensure!(reader.len() >= 2, "Not enough bytes to read u16");
|
||||||
|
let value = u16::from_ne_bytes(reader[..2].try_into()?);
|
||||||
|
*reader = &reader[2..];
|
||||||
|
Ok(obj_file.endianness().read_u16(value))
|
||||||
}
|
}
|
||||||
|
|||||||
17
objdiff-core/tests/arch_arm.rs
Normal file
17
objdiff-core/tests/arch_arm.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use objdiff_core::{diff, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "arm")]
|
||||||
|
fn read_arm() {
|
||||||
|
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/arm/LinkStateItem.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "_ZN13LinkStateItem12OnStateLeaveEi").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
16
objdiff-core/tests/arch_mips.rs
Normal file
16
objdiff-core/tests/arch_mips.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use objdiff_core::{diff, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "mips")]
|
||||||
|
fn read_mips() {
|
||||||
|
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "ControlEntry").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
87
objdiff-core/tests/arch_ppc.rs
Normal file
87
objdiff-core/tests/arch_ppc.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use objdiff_core::{
|
||||||
|
diff::{self, display},
|
||||||
|
obj,
|
||||||
|
obj::SectionKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_ppc() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_dwarf1_line_info() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/m_Do_hostIO.o"), &diff_config).unwrap();
|
||||||
|
let line_infos = obj
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| s.kind == SectionKind::Code)
|
||||||
|
.map(|s| s.line_info.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
insta::assert_debug_snapshot!(line_infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_extab() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/NMWException.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn diff_ppc() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let mapping_config = diff::MappingConfig::default();
|
||||||
|
let target_obj =
|
||||||
|
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_target.o"), &diff_config)
|
||||||
|
.unwrap();
|
||||||
|
let base_obj =
|
||||||
|
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_base.o"), &diff_config)
|
||||||
|
.unwrap();
|
||||||
|
let diff =
|
||||||
|
diff::diff_objs(Some(&target_obj), Some(&base_obj), None, &diff_config, &mapping_config)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let target_diff = diff.left.as_ref().unwrap();
|
||||||
|
let base_diff = diff.right.as_ref().unwrap();
|
||||||
|
let sections_display = display::display_sections(
|
||||||
|
&target_obj,
|
||||||
|
&target_diff,
|
||||||
|
display::SymbolFilter::None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
insta::assert_debug_snapshot!(sections_display);
|
||||||
|
|
||||||
|
let target_symbol_idx = target_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
|
||||||
|
.unwrap();
|
||||||
|
let target_symbol_diff = &target_diff.symbols[target_symbol_idx];
|
||||||
|
let base_symbol_idx = base_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
|
||||||
|
.unwrap();
|
||||||
|
let base_symbol_diff = &base_diff.symbols[base_symbol_idx];
|
||||||
|
assert_eq!(target_symbol_diff.target_symbol, Some(base_symbol_idx));
|
||||||
|
assert_eq!(base_symbol_diff.target_symbol, Some(target_symbol_idx));
|
||||||
|
insta::assert_debug_snapshot!((target_symbol_diff, base_symbol_diff));
|
||||||
|
}
|
||||||
28
objdiff-core/tests/arch_x86.rs
Normal file
28
objdiff-core/tests/arch_x86.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use objdiff_core::{diff, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/staticdebug.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?PrintThing@@YAXXZ").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86_combine_sections() {
|
||||||
|
let diff_config = diff::DiffObjConfig {
|
||||||
|
combine_data_sections: true,
|
||||||
|
combine_text_sections: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj.sections);
|
||||||
|
}
|
||||||
52
objdiff-core/tests/common.rs
Normal file
52
objdiff-core/tests/common.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use objdiff_core::{
|
||||||
|
diff::{DiffObjConfig, SymbolDiff, display::DiffTextSegment},
|
||||||
|
obj::Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn display_diff(
|
||||||
|
obj: &Object,
|
||||||
|
diff: &SymbolDiff,
|
||||||
|
symbol_idx: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
for row in &diff.instruction_rows {
|
||||||
|
output.push('[');
|
||||||
|
let mut separator = false;
|
||||||
|
objdiff_core::diff::display::display_row(&obj, symbol_idx, row, &diff_config, |segment| {
|
||||||
|
if separator {
|
||||||
|
output.push_str(", ");
|
||||||
|
} else {
|
||||||
|
separator = true;
|
||||||
|
}
|
||||||
|
let DiffTextSegment { text, color, pad_to } = segment;
|
||||||
|
output.push_str(&format!("({:?}, {:?}, {:?})", text, color, pad_to));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
output.push_str("]\n");
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AlignedAs<Align, Bytes: ?Sized> {
|
||||||
|
pub _align: [Align; 0],
|
||||||
|
pub bytes: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_bytes_align_as {
|
||||||
|
($align_ty:ty, $path:literal) => {{
|
||||||
|
static ALIGNED: &common::AlignedAs<$align_ty, [u8]> =
|
||||||
|
&common::AlignedAs { _align: [], bytes: *include_bytes!($path) };
|
||||||
|
&ALIGNED.bytes
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_object {
|
||||||
|
($path:literal) => {
|
||||||
|
include_bytes_align_as!(u32, $path)
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
objdiff-core/tests/data/arm/LinkStateItem.o
Normal file
BIN
objdiff-core/tests/data/arm/LinkStateItem.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/mips/main.c.o
Normal file
BIN
objdiff-core/tests/data/mips/main.c.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_base.o
Normal file
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_base.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_target.o
Normal file
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_target.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/IObj.o
Normal file
BIN
objdiff-core/tests/data/ppc/IObj.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/NMWException.o
Normal file
BIN
objdiff-core/tests/data/ppc/NMWException.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/m_Do_hostIO.o
Normal file
BIN
objdiff-core/tests/data/ppc/m_Do_hostIO.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/rtest.obj
Normal file
BIN
objdiff-core/tests/data/x86/rtest.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/staticdebug.obj
Normal file
BIN
objdiff-core/tests/data/x86/staticdebug.obj
Normal file
Binary file not shown.
1704
objdiff-core/tests/snapshots/arch_arm__read_arm-2.snap
Normal file
1704
objdiff-core/tests/snapshots/arch_arm__read_arm-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
112
objdiff-core/tests/snapshots/arch_arm__read_arm-3.snap
Normal file
112
objdiff-core/tests/snapshots/arch_arm__read_arm-3.snap
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_arm.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stmdb", 32895), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lr")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12OnStateLeaveEi", demangled_name: Some("LinkStateBase::OnStateLeave(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addls", 32770), Normal, 10), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lsl")), Normal, 0), (Basic(" #"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(192), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(124), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(140), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(76), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(152), Normal, 0), (Basic(" ~>"), Rotating(5), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(76), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(336)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf01cEv", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf01c()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(224)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(108), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem15GetEquipBombchuEv", demangled_name: Some("LinkStateItem::GetEquipBombchu()"), address: 472, size: 16, kind: Function, section: Some(0), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN12EquipBombchu19func_ov014_0213ec64Ev", demangled_name: Some("EquipBombchu::func_ov014_0213ec64()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(108), Normal, 5), (Basic(" ~> "), Rotating(7), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(308)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32777), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov014_0211fd04Pi", demangled_name: Some("func_ov014_0211fd04(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(124), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(128), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingBombEi", demangled_name: Some("LinkStateItem::StopUsingBomb(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(140), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingRopeEv", demangled_name: Some("LinkStateItem::StopUsingRope()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(152), Normal, 5), (Basic(" ~> "), Rotating(5), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem15StopUsingHammerEv", demangled_name: Some("LinkStateItem::StopUsingHammer()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(164), Normal, 5), (Basic(" ~> "), Rotating(6), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(248)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(42)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf9dcEii", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf9dc(int, int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(192), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(196), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem14StopUsingScoopEv", demangled_name: Some("LinkStateItem::StopUsingScoop()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(200), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(212), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(236), Normal, 0), (Basic(" ~>"), Rotating(9), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(216), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12GetEquipItemEi", demangled_name: Some("LinkStateBase::GetEquipItem(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(28)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32778), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(236), Normal, 5), (Basic(" ~> "), Rotating(9), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(9)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(244), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(288), Normal, 0), (Basic(" ~>"), Rotating(10), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(248), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bge", 32773), Normal, 10), (BranchDest(296), Normal, 0), (Basic(" ~>"), Rotating(11), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(252), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(256), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(260), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(264), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(268), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(272), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(276), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(280), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(284), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(288), Normal, 5), (Basic(" ~> "), Rotating(10), 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(292), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(296), Normal, 5), (Basic(" ~> "), Rotating(11), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(300), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(304), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(308), Normal, 5), (Basic(" ~> "), Rotating(12), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(312), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(316), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(320), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(324), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(328), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem16GetLinkStateMoveEv", demangled_name: Some("LinkStateItem::GetLinkStateMove()"), address: 488, size: 16, kind: Function, section: Some(0), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(332), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(336), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(340), Normal, 5), (Basic(" ~> "), Rotating(13), 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(344), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(80)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(348), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(88)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(352), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 32898), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(356), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(360), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(384), Normal, 0), (Basic(" ~>"), Rotating(14), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(364), Normal, 5), (Basic(" ~> "), Rotating(15), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(368), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov000_020b7e6cPi", demangled_name: Some("func_ov000_020b7e6c(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(372), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(376), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(380), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(364), Normal, 0), (Basic(" ~>"), Rotating(15), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(384), Normal, 5), (Basic(" ~> "), Rotating(14), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(36)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(388), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(392), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(396), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(400), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(408), Normal, 0), (Basic(" ~>"), Rotating(16), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(404), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13PlayerControl13StopFollowingEv", demangled_name: Some("PlayerControl::StopFollowing()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(408), Normal, 5), (Basic(" ~> "), Rotating(16), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(412), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(38)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(416), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldmia", 32793), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(420), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "data_027e103c", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(424), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "data_027e1098", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(428), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "gPlayerControl", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
1936
objdiff-core/tests/snapshots/arch_arm__read_arm.snap
Normal file
1936
objdiff-core/tests/snapshots/arch_arm__read_arm.snap
Normal file
File diff suppressed because it is too large
Load Diff
640
objdiff-core/tests/snapshots/arch_mips__read_mips-2.snap
Normal file
640
objdiff-core/tests/snapshots/arch_mips__read_mips-2.snap
Normal file
@@ -0,0 +1,640 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_mips.rs
|
||||||
|
expression: diff.instruction_rows
|
||||||
|
---
|
||||||
|
[
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 4,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 8,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 12,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 16,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 20,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 24,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 28,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 32,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 36,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 40,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 44,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 48,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 52,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 56,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 60,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 64,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 68,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 72,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 76,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 80,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: Some(
|
||||||
|
InstructionBranchFrom {
|
||||||
|
ins_idx: [
|
||||||
|
22,
|
||||||
|
],
|
||||||
|
branch_idx: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 84,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 88,
|
||||||
|
size: 4,
|
||||||
|
opcode: 56,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 20,
|
||||||
|
branch_idx: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 92,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 96,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 100,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 104,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 108,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 112,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 116,
|
||||||
|
size: 4,
|
||||||
|
opcode: 16,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 120,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 124,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 128,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: Some(
|
||||||
|
InstructionBranchFrom {
|
||||||
|
ins_idx: [
|
||||||
|
36,
|
||||||
|
38,
|
||||||
|
44,
|
||||||
|
],
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 132,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 136,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 140,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 144,
|
||||||
|
size: 4,
|
||||||
|
opcode: 55,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 148,
|
||||||
|
size: 4,
|
||||||
|
opcode: 90,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 152,
|
||||||
|
size: 4,
|
||||||
|
opcode: 3,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 156,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 160,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 164,
|
||||||
|
size: 4,
|
||||||
|
opcode: 60,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 168,
|
||||||
|
size: 4,
|
||||||
|
opcode: 77,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 172,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 176,
|
||||||
|
size: 4,
|
||||||
|
opcode: 54,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 180,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
50
objdiff-core/tests/snapshots/arch_mips__read_mips-3.snap
Normal file
50
objdiff-core/tests/snapshots/arch_mips__read_mips-3.snap
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_mips.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-32)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(16)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$ra")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSleep", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadEffect", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadSwd", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdAddWaveData", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(80), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdSpuDmaCompleted", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bnez", 56), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(80), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglRenderDispOn", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(255)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(108), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "LogoFirst", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ori", 16), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(65535)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(128), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beqz", 55), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("and", 90), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 3), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("srl", 60), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jalr", 77), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 54), Normal, 10), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
1456
objdiff-core/tests/snapshots/arch_mips__read_mips.snap
Normal file
1456
objdiff-core/tests/snapshots/arch_mips__read_mips.snap
Normal file
File diff suppressed because it is too large
Load Diff
4870
objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap
Normal file
4870
objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
87
objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap
Normal file
87
objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
assertion_line: 70
|
||||||
|
expression: sections_display
|
||||||
|
---
|
||||||
|
[
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".comm",
|
||||||
|
name: ".comm",
|
||||||
|
size: 0,
|
||||||
|
match_percent: None,
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 11,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 12,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 13,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 14,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".ctors-0",
|
||||||
|
name: ".ctors",
|
||||||
|
size: 4,
|
||||||
|
match_percent: Some(
|
||||||
|
100.0,
|
||||||
|
),
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 2,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
size: 3060,
|
||||||
|
match_percent: Some(
|
||||||
|
58.662746,
|
||||||
|
),
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 3,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 10,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 9,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 8,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 7,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 6,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 5,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 4,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
expression: line_infos
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
0: 13,
|
||||||
|
4: 16,
|
||||||
|
32: 17,
|
||||||
|
44: 18,
|
||||||
|
60: 20,
|
||||||
|
76: 21,
|
||||||
|
84: 23,
|
||||||
|
92: 25,
|
||||||
|
108: 26,
|
||||||
|
124: 27,
|
||||||
|
136: 28,
|
||||||
|
144: 29,
|
||||||
|
152: 31,
|
||||||
|
164: 34,
|
||||||
|
184: 35,
|
||||||
|
212: 39,
|
||||||
|
228: 40,
|
||||||
|
236: 41,
|
||||||
|
260: 43,
|
||||||
|
288: 44,
|
||||||
|
292: 45,
|
||||||
|
300: 48,
|
||||||
|
436: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0: 48,
|
||||||
|
132: 35,
|
||||||
|
244: 26,
|
||||||
|
304: 22,
|
||||||
|
312: 23,
|
||||||
|
316: 24,
|
||||||
|
320: 0,
|
||||||
|
},
|
||||||
|
]
|
||||||
521
objdiff-core/tests/snapshots/arch_ppc__read_extab.snap
Normal file
521
objdiff-core/tests/snapshots/arch_ppc__read_extab.snap
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
expression: obj
|
||||||
|
---
|
||||||
|
Object {
|
||||||
|
arch: ArchPpc {
|
||||||
|
extab: Some(
|
||||||
|
{
|
||||||
|
10: ExceptionInfo {
|
||||||
|
eti_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 5,
|
||||||
|
name: "@31",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
etb_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 4,
|
||||||
|
name: "@30",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
data: ExceptionTableData {
|
||||||
|
flag_val: 8200,
|
||||||
|
has_elf_vector: false,
|
||||||
|
large_frame: true,
|
||||||
|
has_frame_pointer: false,
|
||||||
|
saved_cr: false,
|
||||||
|
fpr_save_range: 0,
|
||||||
|
gpr_save_range: 4,
|
||||||
|
et_field: 0,
|
||||||
|
pc_actions: [],
|
||||||
|
exception_actions: [],
|
||||||
|
relocations: [],
|
||||||
|
},
|
||||||
|
dtors: [],
|
||||||
|
},
|
||||||
|
11: ExceptionInfo {
|
||||||
|
eti_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 7,
|
||||||
|
name: "@52",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
etb_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 6,
|
||||||
|
name: "@51",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
data: ExceptionTableData {
|
||||||
|
flag_val: 8200,
|
||||||
|
has_elf_vector: false,
|
||||||
|
large_frame: true,
|
||||||
|
has_frame_pointer: false,
|
||||||
|
saved_cr: false,
|
||||||
|
fpr_save_range: 0,
|
||||||
|
gpr_save_range: 4,
|
||||||
|
et_field: 0,
|
||||||
|
pc_actions: [
|
||||||
|
PCAction {
|
||||||
|
start_pc: 96,
|
||||||
|
end_pc: 96,
|
||||||
|
action_offset: 16,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exception_actions: [
|
||||||
|
ExceptionAction {
|
||||||
|
action_offset: 16,
|
||||||
|
action_type: DestroyLocal,
|
||||||
|
action_param: 0,
|
||||||
|
has_end_bit: true,
|
||||||
|
bytes: [
|
||||||
|
0,
|
||||||
|
8,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
offset: 20,
|
||||||
|
address: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
dtors: [
|
||||||
|
ExtabSymbolRef {
|
||||||
|
original_index: 12,
|
||||||
|
name: "__dt__26__partial_array_destructorFv",
|
||||||
|
demangled_name: Some(
|
||||||
|
"__partial_array_destructor::~__partial_array_destructor()",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
12: ExceptionInfo {
|
||||||
|
eti_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 9,
|
||||||
|
name: "@60",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
etb_symbol: ExtabSymbolRef {
|
||||||
|
original_index: 8,
|
||||||
|
name: "@59",
|
||||||
|
demangled_name: None,
|
||||||
|
},
|
||||||
|
data: ExceptionTableData {
|
||||||
|
flag_val: 6152,
|
||||||
|
has_elf_vector: false,
|
||||||
|
large_frame: true,
|
||||||
|
has_frame_pointer: false,
|
||||||
|
saved_cr: false,
|
||||||
|
fpr_save_range: 0,
|
||||||
|
gpr_save_range: 3,
|
||||||
|
et_field: 0,
|
||||||
|
pc_actions: [],
|
||||||
|
exception_actions: [],
|
||||||
|
relocations: [],
|
||||||
|
},
|
||||||
|
dtors: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
endianness: Big,
|
||||||
|
symbols: [
|
||||||
|
Symbol {
|
||||||
|
name: "NMWException.cpp",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.text]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[extab]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[extabindex]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@30",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@31",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@51",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 8,
|
||||||
|
size: 24,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@52",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 12,
|
||||||
|
size: 12,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@59",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 32,
|
||||||
|
size: 8,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@60",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 24,
|
||||||
|
size: 12,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__destroy_arr",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 120,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | HasExtra),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__construct_array",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 120,
|
||||||
|
size: 248,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | HasExtra),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__dt__26__partial_array_destructorFv",
|
||||||
|
demangled_name: Some(
|
||||||
|
"__partial_array_destructor::~__partial_array_destructor()",
|
||||||
|
),
|
||||||
|
address: 368,
|
||||||
|
size: 184,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Weak | HasExtra),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__dl__FPv",
|
||||||
|
demangled_name: Some(
|
||||||
|
"operator delete(void*)",
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
Section {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 552,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
552,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
address: 516,
|
||||||
|
target_symbol: 13,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: "extab-0",
|
||||||
|
name: "extab",
|
||||||
|
address: 0,
|
||||||
|
size: 40,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
40,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 28,
|
||||||
|
target_symbol: 12,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: "extabindex-0",
|
||||||
|
name: "extabindex",
|
||||||
|
address: 0,
|
||||||
|
size: 36,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
36,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 10,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 8,
|
||||||
|
target_symbol: 4,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 12,
|
||||||
|
target_symbol: 11,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 20,
|
||||||
|
target_symbol: 6,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 24,
|
||||||
|
target_symbol: 12,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 32,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rela.text-0",
|
||||||
|
name: ".rela.text",
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".relaextab-0",
|
||||||
|
name: ".relaextab",
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".relaextabindex-0",
|
||||||
|
name: ".relaextabindex",
|
||||||
|
address: 0,
|
||||||
|
size: 72,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".symtab-0",
|
||||||
|
name: ".symtab",
|
||||||
|
address: 0,
|
||||||
|
size: 240,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".strtab-0",
|
||||||
|
name: ".strtab",
|
||||||
|
address: 0,
|
||||||
|
size: 121,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".shstrtab-0",
|
||||||
|
name: ".shstrtab",
|
||||||
|
address: 0,
|
||||||
|
size: 97,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".comment-0",
|
||||||
|
name: ".comment",
|
||||||
|
address: 0,
|
||||||
|
size: 164,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
split_meta: None,
|
||||||
|
path: None,
|
||||||
|
timestamp: None,
|
||||||
|
}
|
||||||
1008
objdiff-core/tests/snapshots/arch_ppc__read_ppc-2.snap
Normal file
1008
objdiff-core/tests/snapshots/arch_ppc__read_ppc-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
71
objdiff-core/tests/snapshots/arch_ppc__read_ppc-3.snap
Normal file
71
objdiff-core/tests/snapshots/arch_ppc__read_ppc-3.snap
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
assertion_line: 20
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("srwi", 60), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(20), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(32), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(20), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(32), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("extrwi", 60), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(56), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(68), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(56), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(68), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("extrwi", 60), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("16")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(96), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(108), Normal, 0), (Basic(" ~>"), Rotating(5), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(96), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(108), Normal, 5), (Basic(" ~> "), Rotating(5), 0), (Opcode("clrlwi", 60), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(136), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(128), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(148), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(136), Normal, 5), (Basic(" ~> "), Rotating(6), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(148), Normal, 5), (Basic(" ~> "), Rotating(7), 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(3)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(45)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbz", 162), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(196), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(192), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(196), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(200), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(216), Normal, 0), (Basic(" ~>"), Rotating(9), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(212), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(216), Normal, 5), (Basic(" ~> "), Rotating(9), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(236), Normal, 0), (Basic(" ~>"), Rotating(10), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(236), Normal, 5), (Basic(" ~> "), Rotating(10), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(244), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(248), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(256), Normal, 0), (Basic(" ~>"), Rotating(11), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(252), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(256), Normal, 5), (Basic(" ~> "), Rotating(11), 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(260), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blr", 47), Normal, 10), (Eol, Normal, 0)]
|
||||||
555
objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap
Normal file
555
objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
assertion_line: 14
|
||||||
|
expression: obj
|
||||||
|
---
|
||||||
|
Object {
|
||||||
|
arch: ArchPpc {
|
||||||
|
extab: None,
|
||||||
|
},
|
||||||
|
endianness: Big,
|
||||||
|
symbols: [
|
||||||
|
Symbol {
|
||||||
|
name: "IObj.cpp",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.text]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2150895620,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.ctors]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2151461704,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.sbss]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2153420048,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__sinit_IObj_cpp",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 264,
|
||||||
|
size: 20,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2150895884,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "text$52",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 8,
|
||||||
|
size: 5,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2153420056,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "Type2Text__10SObjectTagFUi",
|
||||||
|
demangled_name: Some(
|
||||||
|
"SObjectTag::Type2Text(unsigned int)",
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
size: 264,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2150895620,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "gkInvalidObjectTag",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
2153420048,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__upper_map",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__ctype_map",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
Section {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 284,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
284,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 22,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
address: 26,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 28,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 36,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 58,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
address: 62,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 64,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 72,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 98,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
address: 102,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 104,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 112,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 138,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
address: 142,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 144,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 148,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 162,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
address: 166,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 176,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 180,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 200,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 220,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 240,
|
||||||
|
target_symbol: 9,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 256,
|
||||||
|
target_symbol: 5,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 268,
|
||||||
|
target_symbol: 7,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
109,
|
||||||
|
),
|
||||||
|
address: 272,
|
||||||
|
target_symbol: 7,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: Some(
|
||||||
|
2150895620,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".ctors-0",
|
||||||
|
name: ".ctors",
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 4,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: Some(
|
||||||
|
2151461704,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".sbss-0",
|
||||||
|
name: ".sbss",
|
||||||
|
address: 0,
|
||||||
|
size: 16,
|
||||||
|
kind: Bss,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: Some(
|
||||||
|
2153420048,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rela.text-0",
|
||||||
|
name: ".rela.text",
|
||||||
|
address: 0,
|
||||||
|
size: 216,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rela.ctors-0",
|
||||||
|
name: ".rela.ctors",
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".symtab-0",
|
||||||
|
name: ".symtab",
|
||||||
|
address: 0,
|
||||||
|
size: 176,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".strtab-0",
|
||||||
|
name: ".strtab",
|
||||||
|
address: 0,
|
||||||
|
size: 105,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".shstrtab-0",
|
||||||
|
name: ".shstrtab",
|
||||||
|
address: 0,
|
||||||
|
size: 77,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".comment-0",
|
||||||
|
name: ".comment",
|
||||||
|
address: 0,
|
||||||
|
size: 132,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".note.split-0",
|
||||||
|
name: ".note.split",
|
||||||
|
address: 0,
|
||||||
|
size: 152,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
split_meta: Some(
|
||||||
|
SplitMeta {
|
||||||
|
generator: Some(
|
||||||
|
"decomp-toolkit 1.4.0",
|
||||||
|
),
|
||||||
|
module_name: Some(
|
||||||
|
"main",
|
||||||
|
),
|
||||||
|
module_id: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
virtual_addresses: Some(
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2150895620,
|
||||||
|
2151461704,
|
||||||
|
2153420048,
|
||||||
|
2150895884,
|
||||||
|
2153420056,
|
||||||
|
2150895620,
|
||||||
|
2153420048,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
path: None,
|
||||||
|
timestamp: None,
|
||||||
|
}
|
||||||
97
objdiff-core/tests/snapshots/arch_x86__read_x86-2.snap
Normal file
97
objdiff-core/tests/snapshots/arch_x86__read_x86-2.snap
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_x86.rs
|
||||||
|
expression: diff.instruction_rows
|
||||||
|
---
|
||||||
|
[
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 0,
|
||||||
|
size: 1,
|
||||||
|
opcode: 640,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 1,
|
||||||
|
size: 2,
|
||||||
|
opcode: 414,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 3,
|
||||||
|
size: 5,
|
||||||
|
opcode: 640,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 8,
|
||||||
|
size: 5,
|
||||||
|
opcode: 59,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 13,
|
||||||
|
size: 3,
|
||||||
|
opcode: 7,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 16,
|
||||||
|
size: 1,
|
||||||
|
opcode: 590,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 17,
|
||||||
|
size: 1,
|
||||||
|
opcode: 662,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
11
objdiff-core/tests/snapshots/arch_x86__read_x86-3.snap
Normal file
11
objdiff-core/tests/snapshots/arch_x86__read_x86-3.snap
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_x86.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 640), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(1), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("esp")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(3), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 640), Normal, 10), (Symbol(Symbol { name: "$SG526", demangled_name: None, address: 4, size: 6, kind: Object, section: Some(1), flags: FlagSet(Local | SizeInferred), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("call", 59), Normal, 10), (Symbol(Symbol { name: "_printf", demangled_name: None, address: 0, size: 0, kind: Function, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(13), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("esp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 590), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(17), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ret", 662), Normal, 10), (Eol, Normal, 0)]
|
||||||
201
objdiff-core/tests/snapshots/arch_x86__read_x86.snap
Normal file
201
objdiff-core/tests/snapshots/arch_x86__read_x86.snap
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_x86.rs
|
||||||
|
expression: obj
|
||||||
|
---
|
||||||
|
Object {
|
||||||
|
arch: ArchX86 {
|
||||||
|
bits: 32,
|
||||||
|
endianness: Little,
|
||||||
|
},
|
||||||
|
endianness: Little,
|
||||||
|
symbols: [
|
||||||
|
Symbol {
|
||||||
|
name: "objdiffstaticdebug.cpp",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "@comp.id",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Object,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.drectve]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 38,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.data]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "?a@@3PAXA",
|
||||||
|
demangled_name: Some(
|
||||||
|
"void *a",
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | SizeInferred),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.text]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "?PrintThing@@YAXXZ",
|
||||||
|
demangled_name: Some(
|
||||||
|
"void __cdecl PrintThing(void)",
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
size: 18,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local | SizeInferred),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "_printf",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Function,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "$SG526",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 4,
|
||||||
|
size: 6,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local | SizeInferred),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sections: [
|
||||||
|
Section {
|
||||||
|
id: ".drectve-0",
|
||||||
|
name: ".drectve",
|
||||||
|
address: 0,
|
||||||
|
size: 38,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-0",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 10,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
10,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 6,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 18,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
18,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 9,
|
||||||
|
target_symbol: 7,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
split_meta: None,
|
||||||
|
path: None,
|
||||||
|
timestamp: None,
|
||||||
|
}
|
||||||
@@ -0,0 +1,940 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_x86.rs
|
||||||
|
expression: obj.sections
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Section {
|
||||||
|
id: ".drectve-0",
|
||||||
|
name: ".drectve",
|
||||||
|
address: 0,
|
||||||
|
size: 47,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".debug$S-0",
|
||||||
|
name: ".debug$S",
|
||||||
|
address: 0,
|
||||||
|
size: 100,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata-0",
|
||||||
|
name: ".rdata",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata-1",
|
||||||
|
name: ".rdata",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-0",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-combined",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 56,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
56,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Combined),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 44,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 16,
|
||||||
|
target_symbol: 44,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 32,
|
||||||
|
target_symbol: 44,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 48,
|
||||||
|
target_symbol: 6,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 52,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata-combined",
|
||||||
|
name: ".rdata",
|
||||||
|
address: 0,
|
||||||
|
size: 295,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
295,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Combined),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 12,
|
||||||
|
target_symbol: 17,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 16,
|
||||||
|
target_symbol: 19,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 21,
|
||||||
|
target_symbol: 13,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 45,
|
||||||
|
target_symbol: 15,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 61,
|
||||||
|
target_symbol: 25,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 65,
|
||||||
|
target_symbol: 27,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 70,
|
||||||
|
target_symbol: 21,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 94,
|
||||||
|
target_symbol: 23,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 110,
|
||||||
|
target_symbol: 31,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 114,
|
||||||
|
target_symbol: 33,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 130,
|
||||||
|
target_symbol: 35,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 134,
|
||||||
|
target_symbol: 37,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 138,
|
||||||
|
target_symbol: 19,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 142,
|
||||||
|
target_symbol: 39,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 147,
|
||||||
|
target_symbol: 31,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 171,
|
||||||
|
target_symbol: 33,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 175,
|
||||||
|
target_symbol: 21,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 199,
|
||||||
|
target_symbol: 23,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 215,
|
||||||
|
target_symbol: 31,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 219,
|
||||||
|
target_symbol: 33,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 235,
|
||||||
|
target_symbol: 13,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 239,
|
||||||
|
target_symbol: 15,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 255,
|
||||||
|
target_symbol: 21,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 259,
|
||||||
|
target_symbol: 23,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 263,
|
||||||
|
target_symbol: 29,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 267,
|
||||||
|
target_symbol: 11,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 271,
|
||||||
|
target_symbol: 43,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 275,
|
||||||
|
target_symbol: 41,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 279,
|
||||||
|
target_symbol: 70,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 283,
|
||||||
|
target_symbol: 56,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 287,
|
||||||
|
target_symbol: 72,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 291,
|
||||||
|
target_symbol: 59,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-1",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-2",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data$rs-1",
|
||||||
|
name: ".data$rs",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-3",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-4",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-5",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-6",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data$rs-2",
|
||||||
|
name: ".data$rs",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-7",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-8",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-9",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-10",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-1",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-11",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-2",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-3",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-4",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-5",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$mn-6",
|
||||||
|
name: ".text$mn",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text-combined",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 268,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
268,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Combined),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 62,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 9,
|
||||||
|
target_symbol: 53,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 29,
|
||||||
|
target_symbol: 60,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 48,
|
||||||
|
target_symbol: 52,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 68,
|
||||||
|
target_symbol: 11,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 84,
|
||||||
|
target_symbol: 64,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 104,
|
||||||
|
target_symbol: 66,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 124,
|
||||||
|
target_symbol: 6,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 134,
|
||||||
|
target_symbol: 8,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 145,
|
||||||
|
target_symbol: 57,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 153,
|
||||||
|
target_symbol: 54,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 172,
|
||||||
|
target_symbol: 54,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 191,
|
||||||
|
target_symbol: 52,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 218,
|
||||||
|
target_symbol: 57,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 237,
|
||||||
|
target_symbol: 52,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 257,
|
||||||
|
target_symbol: 68,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
address: 262,
|
||||||
|
target_symbol: 60,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".text$yd-0",
|
||||||
|
name: ".text$yd",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata-2",
|
||||||
|
name: ".rdata",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata-3",
|
||||||
|
name: ".rdata",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-0",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-12",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".rdata$r-13",
|
||||||
|
name: ".rdata$r",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Hidden),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".CRT$XCU-0",
|
||||||
|
name: ".CRT$XCU",
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Coff(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 61,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".chks64-0",
|
||||||
|
name: ".chks64",
|
||||||
|
address: 0,
|
||||||
|
size: 280,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -25,14 +25,12 @@ wsl = []
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
bytes = "1.9"
|
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
cwextab = "1.0"
|
dirs = "6.0"
|
||||||
dirs = "5.0"
|
egui = "0.31"
|
||||||
egui = "0.30"
|
egui_extras = "0.31"
|
||||||
egui_extras = "0.30"
|
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
float-ord = "0.3"
|
float-ord = "0.3"
|
||||||
font-kit = "0.14"
|
font-kit = "0.14"
|
||||||
@@ -44,17 +42,17 @@ png = "0.17"
|
|||||||
pollster = "0.4"
|
pollster = "0.4"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
|
||||||
rlwinmdec = "1.0"
|
rlwinmdec = { version = "1.0", git = "https://github.com/CelestialAmber/rlwinmdec.git" }
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
|
||||||
shell-escape = "0.1"
|
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
|
typed-path = "0.10"
|
||||||
|
winit = { version = "0.30", features = ["wayland-csd-adwaita"] }
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
# Keep version in sync with egui
|
# Keep version in sync with egui
|
||||||
[dependencies.eframe]
|
[dependencies.eframe]
|
||||||
version = "0.30"
|
version = "0.31"
|
||||||
features = [
|
features = [
|
||||||
"default_fonts",
|
"default_fonts",
|
||||||
"persistence",
|
"persistence",
|
||||||
@@ -65,7 +63,7 @@ default-features = false
|
|||||||
|
|
||||||
# Keep version in sync with eframe
|
# Keep version in sync with eframe
|
||||||
[dependencies.wgpu]
|
[dependencies.wgpu]
|
||||||
version = "23.0"
|
version = "24.0"
|
||||||
features = [
|
features = [
|
||||||
"dx12",
|
"dx12",
|
||||||
"metal",
|
"metal",
|
||||||
@@ -80,17 +78,8 @@ winapi = "0.3"
|
|||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
exec = "0.3"
|
exec = "0.3"
|
||||||
|
|
||||||
# native:
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
||||||
|
|
||||||
# web:
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
||||||
console_error_panic_hook = "0.1"
|
|
||||||
tracing-wasm = "0.2"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.build-dependencies]
|
[target.'cfg(windows)'.build-dependencies]
|
||||||
tauri-winres = "0.1"
|
tauri-winres = "0.3"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
default::Default,
|
default::Default,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc, Mutex, RwLock,
|
Arc, Mutex, RwLock,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
},
|
},
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
@@ -13,36 +14,36 @@ use std::{
|
|||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
build::watcher::{create_watcher, Watcher},
|
build::watcher::{Watcher, create_watcher},
|
||||||
config::{
|
config::{
|
||||||
build_globset, default_watch_patterns, save_project_config, ProjectConfig,
|
DEFAULT_WATCH_PATTERNS, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
|
||||||
ProjectConfigInfo, ProjectObject, ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
build_globset, default_watch_patterns, path::platform_path_serde_option,
|
||||||
|
save_project_config,
|
||||||
},
|
},
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
jobs::{Job, JobQueue, JobResult},
|
jobs::{Job, JobQueue, JobResult},
|
||||||
};
|
};
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_config::{deserialize_config, AppConfigVersion},
|
app_config::{AppConfigVersion, deserialize_config},
|
||||||
config::{load_project_config, ProjectObjectNode},
|
config::{ProjectObjectNode, load_project_config},
|
||||||
jobs::{create_objdiff_config, egui_waker, start_build},
|
jobs::{create_objdiff_config, egui_waker, start_build},
|
||||||
views::{
|
views::{
|
||||||
appearance::{appearance_window, Appearance},
|
appearance::{Appearance, appearance_window},
|
||||||
config::{
|
config::{
|
||||||
arch_config_window, config_ui, general_config_ui, project_window, ConfigViewState,
|
CONFIG_DISABLED_TEXT, ConfigViewState, arch_config_window, config_ui,
|
||||||
CONFIG_DISABLED_TEXT,
|
general_config_ui, project_window,
|
||||||
},
|
},
|
||||||
data_diff::data_diff_ui,
|
|
||||||
debug::debug_window,
|
debug::debug_window,
|
||||||
demangle::{demangle_window, DemangleViewState},
|
demangle::{DemangleViewState, demangle_window},
|
||||||
extab_diff::extab_diff_ui,
|
diff::diff_view_ui,
|
||||||
frame_history::FrameHistory,
|
frame_history::FrameHistory,
|
||||||
function_diff::function_diff_ui,
|
graphics::{GraphicsConfig, GraphicsViewState, graphics_window},
|
||||||
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
|
||||||
jobs::{jobs_menu_ui, jobs_window},
|
jobs::{jobs_menu_ui, jobs_window},
|
||||||
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
rlwinm::{RlwinmDecodeViewState, rlwinm_decode_window},
|
||||||
symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View},
|
symbol_diff::{DiffViewAction, DiffViewState, ResolvedNavigation, View},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,26 +93,53 @@ impl Default for ViewState {
|
|||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfig {
|
pub struct ObjectConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: Option<PathBuf>,
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub base_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfig>,
|
|
||||||
pub source_path: Option<String>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub symbol_mappings: SymbolMappings,
|
pub hidden: bool,
|
||||||
|
pub scratch: Option<ScratchConfig>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub source_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ProjectObject> for ObjectConfig {
|
impl ObjectConfig {
|
||||||
fn from(object: &ProjectObject) -> Self {
|
pub fn new(
|
||||||
|
object: &ProjectObject,
|
||||||
|
project_dir: &Utf8PlatformPath,
|
||||||
|
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
) -> Self {
|
||||||
|
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
object.target_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||||
|
};
|
||||||
|
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
object.base_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||||
|
};
|
||||||
|
let source_path =
|
||||||
|
object.source_path().map(|s| project_dir.join(s.with_platform_encoding()));
|
||||||
Self {
|
Self {
|
||||||
name: object.name().to_string(),
|
name: object.name().to_string(),
|
||||||
target_path: object.target_path.clone(),
|
target_path,
|
||||||
base_path: object.base_path.clone(),
|
base_path,
|
||||||
reverse_fn_order: object.reverse_fn_order(),
|
reverse_fn_order: object.reverse_fn_order(),
|
||||||
complete: object.complete(),
|
complete: object.complete(),
|
||||||
|
hidden: object.hidden(),
|
||||||
scratch: object.scratch.clone(),
|
scratch: object.scratch.clone(),
|
||||||
source_path: object.source_path().cloned(),
|
source_path,
|
||||||
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +150,7 @@ fn bool_true() -> bool { true }
|
|||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
pub objects: Vec<ProjectObject>,
|
pub objects: Vec<ObjectConfig>,
|
||||||
pub object_nodes: Vec<ProjectObjectNode>,
|
pub object_nodes: Vec<ProjectObjectNode>,
|
||||||
pub watcher_change: bool,
|
pub watcher_change: bool,
|
||||||
pub config_change: bool,
|
pub config_change: bool,
|
||||||
@@ -172,12 +200,12 @@ pub struct AppConfig {
|
|||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_obj: Option<ObjectConfig>,
|
pub selected_obj: Option<ObjectConfig>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
@@ -191,7 +219,7 @@ 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 recent_projects: Vec<PathBuf>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
}
|
}
|
||||||
@@ -219,12 +247,12 @@ impl Default for AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
pub fn set_project_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.recent_projects.retain(|p| p != &path);
|
self.config.recent_projects.retain(|p| p != &path);
|
||||||
if self.config.recent_projects.len() > 9 {
|
if self.config.recent_projects.len() > 9 {
|
||||||
self.config.recent_projects.truncate(9);
|
self.config.recent_projects.truncate(9);
|
||||||
}
|
}
|
||||||
self.config.recent_projects.insert(0, path.clone());
|
self.config.recent_projects.insert(0, path.to_string());
|
||||||
self.config.project_dir = Some(path);
|
self.config.project_dir = Some(path);
|
||||||
self.config.target_obj_dir = None;
|
self.config.target_obj_dir = None;
|
||||||
self.config.base_obj_dir = None;
|
self.config.base_obj_dir = None;
|
||||||
@@ -242,7 +270,7 @@ impl AppState {
|
|||||||
self.selecting_right = None;
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_target_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.target_obj_dir = Some(path);
|
self.config.target_obj_dir = Some(path);
|
||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
@@ -251,7 +279,7 @@ impl AppState {
|
|||||||
self.selecting_right = None;
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_base_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.base_obj_dir = Some(path);
|
self.config.base_obj_dir = Some(path);
|
||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
@@ -362,14 +390,8 @@ impl AppState {
|
|||||||
Some(object.symbol_mappings.clone())
|
Some(object.symbol_mappings.clone())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(existing) =
|
if let Some(existing) = self.objects.iter_mut().find(|u| u.name == object.name) {
|
||||||
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
existing.symbol_mappings = object.symbol_mappings.clone();
|
||||||
{
|
|
||||||
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(object.symbol_mappings.clone())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Save the updated project config
|
// Save the updated project config
|
||||||
@@ -532,7 +554,12 @@ impl App {
|
|||||||
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(|globset| {
|
||||||
create_watcher(self.modified.clone(), project_dir, globset, egui_waker(ctx))
|
create_watcher(
|
||||||
|
self.modified.clone(),
|
||||||
|
project_dir.as_ref(),
|
||||||
|
globset,
|
||||||
|
egui_waker(ctx),
|
||||||
|
)
|
||||||
.map_err(anyhow::Error::new)
|
.map_err(anyhow::Error::new)
|
||||||
}) {
|
}) {
|
||||||
Ok(watcher) => self.watcher = Some(watcher),
|
Ok(watcher) => self.watcher = Some(watcher),
|
||||||
@@ -674,8 +701,11 @@ impl eframe::App for App {
|
|||||||
};
|
};
|
||||||
ui.separator();
|
ui.separator();
|
||||||
for path in recent_projects {
|
for path in recent_projects {
|
||||||
if ui.button(format!("{}", path.display())).clicked() {
|
if ui.button(&path).clicked() {
|
||||||
state.write().unwrap().set_project_dir(path);
|
state
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -732,7 +762,7 @@ impl eframe::App for App {
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
if ui.button("Clear custom symbol mappings").clicked() {
|
if ui.button("Clear custom symbol mappings").clicked() {
|
||||||
state.clear_mappings();
|
state.clear_mappings();
|
||||||
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
diff_state.post_build_nav = Some(ResolvedNavigation::default());
|
||||||
state.queue_reload = true;
|
state.queue_reload = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -753,16 +783,8 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
let mut action = None;
|
let mut action = None;
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
|
let state = state.read().unwrap();
|
||||||
action = if diff_state.current_view == View::FunctionDiff && build_success {
|
action = diff_view_ui(ui, diff_state, appearance, &state.config.diff_obj_config);
|
||||||
function_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else if diff_state.current_view == View::DataDiff && build_success {
|
|
||||||
data_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
|
||||||
extab_diff_ui(ui, diff_state, appearance)
|
|
||||||
} else {
|
|
||||||
symbol_diff_ui(ui, diff_state, appearance)
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
project_window(ctx, state, show_project_config, config_state, appearance);
|
project_window(ctx, state, show_project_config, config_state, appearance);
|
||||||
@@ -787,8 +809,8 @@ impl eframe::App for App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
fn file_modified<P: AsRef<Path>>(path: P, last_ts: FileTime) -> bool {
|
||||||
if let Ok(metadata) = fs::metadata(path) {
|
if let Ok(metadata) = fs::metadata(path.as_ref()) {
|
||||||
FileTime::from_last_modification_time(&metadata) != last_ts
|
FileTime::from_last_modification_time(&metadata) != last_ts
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
use std::path::PathBuf;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use eframe::Storage;
|
use eframe::Storage;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::ScratchConfig,
|
config::ScratchConfig,
|
||||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, MipsAbi, MipsInstrCategory, X86Formatter},
|
diff::{
|
||||||
|
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
||||||
|
X86Formatter,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||||
|
|
||||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
use crate::app::{AppConfig, CONFIG_KEY, ObjectConfig};
|
||||||
|
|
||||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct AppConfigVersion {
|
pub struct AppConfigVersion {
|
||||||
@@ -15,7 +19,7 @@ pub struct AppConfigVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfigVersion {
|
impl Default for AppConfigVersion {
|
||||||
fn default() -> Self { Self { version: 2 } }
|
fn default() -> Self { Self { version: 3 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
|
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
|
||||||
@@ -23,7 +27,8 @@ pub fn deserialize_config(storage: &dyn Storage) -> Option<AppConfig> {
|
|||||||
let str = storage.get_string(CONFIG_KEY)?;
|
let str = storage.get_string(CONFIG_KEY)?;
|
||||||
match ron::from_str::<AppConfigVersion>(&str) {
|
match ron::from_str::<AppConfigVersion>(&str) {
|
||||||
Ok(version) => match version.version {
|
Ok(version) => match version.version {
|
||||||
2 => from_str::<AppConfig>(&str),
|
3 => from_str::<AppConfig>(&str),
|
||||||
|
2 => from_str::<AppConfigV2>(&str).map(|c| c.into_config()),
|
||||||
1 => from_str::<AppConfigV1>(&str).map(|c| c.into_config()),
|
1 => from_str::<AppConfigV1>(&str).map(|c| c.into_config()),
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("Unknown config version: {}", version.version);
|
log::warn!("Unknown config version: {}", version.version);
|
||||||
@@ -49,6 +54,120 @@ where T: serde::de::DeserializeOwned {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ScratchConfigV2 {
|
||||||
|
#[serde(default)]
|
||||||
|
pub platform: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub compiler: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub c_flags: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub ctx_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub build_ctx: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub preset_id: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScratchConfigV2 {
|
||||||
|
fn into_config(self) -> ScratchConfig {
|
||||||
|
ScratchConfig {
|
||||||
|
platform: self.platform,
|
||||||
|
compiler: self.compiler,
|
||||||
|
c_flags: self.c_flags,
|
||||||
|
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||||
|
build_ctx: self.build_ctx,
|
||||||
|
preset_id: self.preset_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ObjectConfigV2 {
|
||||||
|
pub name: String,
|
||||||
|
pub target_path: Option<String>,
|
||||||
|
pub base_path: Option<String>,
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
pub complete: Option<bool>,
|
||||||
|
pub scratch: Option<ScratchConfigV2>,
|
||||||
|
pub source_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: BTreeMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectConfigV2 {
|
||||||
|
fn into_config(self) -> ObjectConfig {
|
||||||
|
ObjectConfig {
|
||||||
|
name: self.name,
|
||||||
|
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||||
|
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||||
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
|
complete: self.complete,
|
||||||
|
hidden: false,
|
||||||
|
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||||
|
source_path: None,
|
||||||
|
symbol_mappings: self.symbol_mappings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct AppConfigV2 {
|
||||||
|
pub version: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom_make: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub custom_args: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_wsl_distro: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub project_dir: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_obj_dir: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub base_obj_dir: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_obj: Option<ObjectConfigV2>,
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub build_base: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub build_target: bool,
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub rebuild_on_changes: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub auto_update_check: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub watch_patterns: Vec<Glob>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub recent_projects: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diff_obj_config: DiffObjConfigV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfigV2 {
|
||||||
|
fn into_config(self) -> AppConfig {
|
||||||
|
log::info!("Upgrading configuration from v2");
|
||||||
|
AppConfig {
|
||||||
|
custom_make: self.custom_make,
|
||||||
|
custom_args: self.custom_args,
|
||||||
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
|
build_base: self.build_base,
|
||||||
|
build_target: self.build_target,
|
||||||
|
rebuild_on_changes: self.rebuild_on_changes,
|
||||||
|
auto_update_check: self.auto_update_check,
|
||||||
|
watch_patterns: self.watch_patterns,
|
||||||
|
recent_projects: self.recent_projects,
|
||||||
|
diff_obj_config: self.diff_obj_config.into_config(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ScratchConfigV1 {
|
pub struct ScratchConfigV1 {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -58,7 +177,7 @@ pub struct ScratchConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub build_ctx: bool,
|
pub build_ctx: bool,
|
||||||
}
|
}
|
||||||
@@ -69,7 +188,7 @@ impl ScratchConfigV1 {
|
|||||||
platform: self.platform,
|
platform: self.platform,
|
||||||
compiler: self.compiler,
|
compiler: self.compiler,
|
||||||
c_flags: self.c_flags,
|
c_flags: self.c_flags,
|
||||||
ctx_path: self.ctx_path,
|
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||||
build_ctx: self.build_ctx.then_some(true),
|
build_ctx: self.build_ctx.then_some(true),
|
||||||
preset_id: None,
|
preset_id: None,
|
||||||
}
|
}
|
||||||
@@ -79,8 +198,8 @@ impl ScratchConfigV1 {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfigV1 {
|
pub struct ObjectConfigV1 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<String>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<String>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfigV1>,
|
pub scratch: Option<ScratchConfigV1>,
|
||||||
@@ -91,12 +210,12 @@ impl ObjectConfigV1 {
|
|||||||
fn into_config(self) -> ObjectConfig {
|
fn into_config(self) -> ObjectConfig {
|
||||||
ObjectConfig {
|
ObjectConfig {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
target_path: self.target_path,
|
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||||
base_path: self.base_path,
|
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
complete: self.complete,
|
complete: self.complete,
|
||||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||||
source_path: self.source_path,
|
source_path: None,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +266,11 @@ impl Default for DiffObjConfigV1 {
|
|||||||
impl DiffObjConfigV1 {
|
impl DiffObjConfigV1 {
|
||||||
fn into_config(self) -> DiffObjConfig {
|
fn into_config(self) -> DiffObjConfig {
|
||||||
DiffObjConfig {
|
DiffObjConfig {
|
||||||
relax_reloc_diffs: self.relax_reloc_diffs,
|
function_reloc_diffs: if self.relax_reloc_diffs {
|
||||||
|
FunctionRelocDiffs::None
|
||||||
|
} else {
|
||||||
|
FunctionRelocDiffs::default()
|
||||||
|
},
|
||||||
space_between_args: self.space_between_args,
|
space_between_args: self.space_between_args,
|
||||||
combine_data_sections: self.combine_data_sections,
|
combine_data_sections: self.combine_data_sections,
|
||||||
x86_formatter: self.x86_formatter,
|
x86_formatter: self.x86_formatter,
|
||||||
@@ -160,6 +283,7 @@ impl DiffObjConfigV1 {
|
|||||||
arm_sl_usage: self.arm_sl_usage,
|
arm_sl_usage: self.arm_sl_usage,
|
||||||
arm_fp_usage: self.arm_fp_usage,
|
arm_fp_usage: self.arm_fp_usage,
|
||||||
arm_ip_usage: self.arm_ip_usage,
|
arm_ip_usage: self.arm_ip_usage,
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,11 +301,11 @@ pub struct AppConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_obj: Option<ObjectConfigV1>,
|
pub selected_obj: Option<ObjectConfigV1>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
@@ -195,7 +319,7 @@ pub struct AppConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub recent_projects: Vec<PathBuf>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfigV1,
|
pub diff_obj_config: DiffObjConfigV1,
|
||||||
}
|
}
|
||||||
@@ -207,9 +331,9 @@ impl AppConfigV1 {
|
|||||||
custom_make: self.custom_make,
|
custom_make: self.custom_make,
|
||||||
custom_args: self.custom_args,
|
custom_args: self.custom_args,
|
||||||
selected_wsl_distro: self.selected_wsl_distro,
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
project_dir: self.project_dir,
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
target_obj_dir: self.target_obj_dir,
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
base_obj_dir: self.base_obj_dir,
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
build_base: self.build_base,
|
build_base: self.build_base,
|
||||||
build_target: self.build_target,
|
build_target: self.build_target,
|
||||||
@@ -226,8 +350,8 @@ impl AppConfigV1 {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfigV0 {
|
pub struct ObjectConfigV0 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: PathBuf,
|
pub target_path: String,
|
||||||
pub base_path: PathBuf,
|
pub base_path: String,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +359,8 @@ impl ObjectConfigV0 {
|
|||||||
fn into_config(self) -> ObjectConfig {
|
fn into_config(self) -> ObjectConfig {
|
||||||
ObjectConfig {
|
ObjectConfig {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
target_path: Some(self.target_path),
|
target_path: Some(Utf8PlatformPathBuf::from(self.target_path)),
|
||||||
base_path: Some(self.base_path),
|
base_path: Some(Utf8PlatformPathBuf::from(self.base_path)),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@@ -247,9 +371,9 @@ impl ObjectConfigV0 {
|
|||||||
pub struct AppConfigV0 {
|
pub struct AppConfigV0 {
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<String>,
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<String>,
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<String>,
|
||||||
pub selected_obj: Option<ObjectConfigV0>,
|
pub selected_obj: Option<ObjectConfigV0>,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub auto_update_check: bool,
|
pub auto_update_check: bool,
|
||||||
@@ -262,9 +386,9 @@ impl AppConfigV0 {
|
|||||||
AppConfig {
|
AppConfig {
|
||||||
custom_make: self.custom_make,
|
custom_make: self.custom_make,
|
||||||
selected_wsl_distro: self.selected_wsl_distro,
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
project_dir: self.project_dir,
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
target_obj_dir: self.target_obj_dir,
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
base_obj_dir: self.base_obj_dir,
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
build_target: self.build_target,
|
build_target: self.build_target,
|
||||||
auto_update_check: self.auto_update_check,
|
auto_update_check: self.auto_update_check,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::path::{Component, Path};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
use objdiff_core::config::{DEFAULT_WATCH_PATTERNS, try_project_config};
|
||||||
|
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
|
||||||
|
|
||||||
use crate::app::{AppState, ObjectConfig};
|
use crate::app::{AppState, ObjectConfig};
|
||||||
|
|
||||||
@@ -47,32 +46,19 @@ fn find_dir<'a>(
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_nodes(
|
fn build_nodes(units: &mut [ObjectConfig]) -> Vec<ProjectObjectNode> {
|
||||||
units: &mut [ProjectObject],
|
|
||||||
project_dir: &Path,
|
|
||||||
target_obj_dir: Option<&Path>,
|
|
||||||
base_obj_dir: Option<&Path>,
|
|
||||||
) -> Vec<ProjectObjectNode> {
|
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for (idx, unit) in units.iter_mut().enumerate() {
|
for (idx, unit) in units.iter_mut().enumerate() {
|
||||||
unit.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
|
|
||||||
let mut out_nodes = &mut nodes;
|
let mut out_nodes = &mut nodes;
|
||||||
let path = if let Some(name) = &unit.name {
|
let path = Utf8UnixPath::new(&unit.name);
|
||||||
Path::new(name)
|
|
||||||
} else if let Some(path) = &unit.path {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
for component in parent.components() {
|
for component in parent.components() {
|
||||||
if let Component::Normal(name) = component {
|
if let Utf8UnixComponent::Normal(name) = component {
|
||||||
let name = name.to_str().unwrap();
|
|
||||||
out_nodes = find_dir(name, out_nodes);
|
out_nodes = find_dir(name, out_nodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path.file_name().unwrap().to_string();
|
||||||
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||||
}
|
}
|
||||||
// Within the top-level module directories, join paths. Leave the
|
// Within the top-level module directories, join paths. Leave the
|
||||||
@@ -90,13 +76,18 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
let Some(project_dir) = &state.config.project_dir else {
|
let Some(project_dir) = &state.config.project_dir else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some((result, info)) = try_project_config(project_dir) {
|
if let Some((result, info)) = try_project_config(project_dir.as_ref()) {
|
||||||
let project_config = result?;
|
let project_config = result?;
|
||||||
state.config.custom_make = project_config.custom_make.clone();
|
state.config.custom_make = project_config.custom_make.clone();
|
||||||
state.config.custom_args = project_config.custom_args.clone();
|
state.config.custom_args = project_config.custom_args.clone();
|
||||||
state.config.target_obj_dir =
|
state.config.target_obj_dir = project_config
|
||||||
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
|
.target_dir
|
||||||
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
.as_deref()
|
||||||
|
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
state.config.base_obj_dir = project_config
|
||||||
|
.base_dir
|
||||||
|
.as_deref()
|
||||||
|
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
state.config.build_base = project_config.build_base.unwrap_or(true);
|
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||||
state.config.build_target = project_config.build_target.unwrap_or(false);
|
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||||
if let Some(watch_patterns) = &project_config.watch_patterns {
|
if let Some(watch_patterns) = &project_config.watch_patterns {
|
||||||
@@ -109,21 +100,28 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||||
}
|
}
|
||||||
state.watcher_change = true;
|
state.watcher_change = true;
|
||||||
state.objects = project_config.units.clone().unwrap_or_default();
|
state.objects = project_config
|
||||||
state.object_nodes = build_nodes(
|
.units
|
||||||
&mut state.objects,
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(
|
||||||
|
o,
|
||||||
project_dir,
|
project_dir,
|
||||||
state.config.target_obj_dir.as_deref(),
|
state.config.target_obj_dir.as_deref(),
|
||||||
state.config.base_obj_dir.as_deref(),
|
state.config.base_obj_dir.as_deref(),
|
||||||
);
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
state.object_nodes = build_nodes(&mut state.objects);
|
||||||
state.current_project_config = Some(project_config);
|
state.current_project_config = Some(project_config);
|
||||||
state.project_config_info = Some(info);
|
state.project_config_info = Some(info);
|
||||||
|
|
||||||
// Reload selected object
|
// Reload selected object
|
||||||
if let Some(selected_obj) = &state.config.selected_obj {
|
if let Some(selected_obj) = &state.config.selected_obj {
|
||||||
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
|
if let Some(obj) = state.objects.iter().find(|o| o.name == selected_obj.name) {
|
||||||
let config = ObjectConfig::from(obj);
|
state.set_selected_obj(obj.clone());
|
||||||
state.set_selected_obj(config);
|
|
||||||
} else {
|
} else {
|
||||||
state.clear_selected_obj();
|
state.clear_selected_obj();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ pub fn load_font_if_needed(
|
|||||||
// FIXME clean up
|
// FIXME clean up
|
||||||
let default_font_ref = family.fonts.get(family.default_index).unwrap();
|
let default_font_ref = family.fonts.get(family.default_index).unwrap();
|
||||||
let default_font = family.handles.get(family.default_index).unwrap();
|
let default_font = family.handles.get(family.default_index).unwrap();
|
||||||
let default_font_data = load_font(default_font).unwrap();
|
let default_font_data = load_font(default_font)?;
|
||||||
log::info!("Loaded font family '{}'", family.family_name);
|
log::info!("Loaded font family '{}'", family.family_name);
|
||||||
fonts.font_data.insert(default_font_ref.full_name(), Arc::new(default_font_data.font_data));
|
fonts.font_data.insert(default_font_ref.full_name(), Arc::new(default_font_data.font_data));
|
||||||
fonts
|
fonts
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use egui::{
|
use egui::{
|
||||||
style::ScrollAnimation, vec2, Context, Key, KeyboardShortcut, Modifiers, PointerButton,
|
Context, Key, KeyboardShortcut, Modifiers, PointerButton, style::ScrollAnimation, vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
|
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ use std::{
|
|||||||
task::{Wake, Waker},
|
task::{Wake, Waker},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use jobs::create_scratch;
|
use jobs::create_scratch;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
build::BuildConfig,
|
build::BuildConfig,
|
||||||
diff::MappingConfig,
|
diff::MappingConfig,
|
||||||
jobs,
|
jobs,
|
||||||
jobs::{check_update::CheckUpdateConfig, objdiff, update::UpdateConfig, Job, JobQueue},
|
jobs::{Job, JobQueue, check_update::CheckUpdateConfig, objdiff, update::UpdateConfig},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, AppState},
|
app::{AppConfig, AppState},
|
||||||
update::{build_updater, BIN_NAME_NEW, BIN_NAME_OLD},
|
update::{BIN_NAME_NEW, BIN_NAME_OLD, build_updater},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EguiWaker(egui::Context);
|
struct EguiWaker(egui::Context);
|
||||||
@@ -73,7 +73,7 @@ fn create_scratch_config(
|
|||||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||||
function_name,
|
function_name,
|
||||||
target_obj: target_path.to_path_buf(),
|
target_obj: target_path.clone(),
|
||||||
preset_id: scratch_config.preset_id,
|
preset_id: scratch_config.preset_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
@@ -16,16 +17,15 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{ensure, Result};
|
use anyhow::{Result, ensure};
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use crate::views::graphics::{load_graphics_config, GraphicsBackend, GraphicsConfig};
|
use crate::views::graphics::{GraphicsBackend, GraphicsConfig, load_graphics_config};
|
||||||
|
|
||||||
fn load_icon() -> Result<egui::IconData> {
|
fn load_icon() -> Result<egui::IconData> {
|
||||||
use bytes::Buf;
|
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
|
||||||
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").reader());
|
|
||||||
let mut reader = decoder.read_info()?;
|
let mut reader = decoder.read_info()?;
|
||||||
let mut buf = vec![0; reader.output_buffer_size()];
|
let mut buf = vec![0; reader.output_buffer_size()];
|
||||||
let info = reader.next_frame(&mut buf)?;
|
let info = reader.next_frame(&mut buf)?;
|
||||||
@@ -37,8 +37,6 @@ fn load_icon() -> Result<egui::IconData> {
|
|||||||
|
|
||||||
const APP_NAME: &str = "objdiff";
|
const APP_NAME: &str = "objdiff";
|
||||||
|
|
||||||
// When compiling natively:
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
@@ -88,26 +86,18 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
{
|
{
|
||||||
use eframe::egui_wgpu::{wgpu::Backends, WgpuSetup};
|
use eframe::egui_wgpu::{WgpuSetup, wgpu};
|
||||||
if graphics_config.desired_backend.is_supported() {
|
if graphics_config.desired_backend.is_supported() {
|
||||||
native_options.wgpu_options.wgpu_setup = match native_options.wgpu_options.wgpu_setup {
|
native_options.wgpu_options.wgpu_setup = match native_options.wgpu_options.wgpu_setup {
|
||||||
WgpuSetup::CreateNew {
|
WgpuSetup::CreateNew(mut setup) => {
|
||||||
supported_backends: backends,
|
setup.instance_descriptor.backends = match graphics_config.desired_backend {
|
||||||
power_preference,
|
GraphicsBackend::Auto => setup.instance_descriptor.backends,
|
||||||
device_descriptor,
|
GraphicsBackend::Dx12 => wgpu::Backends::DX12,
|
||||||
} => {
|
GraphicsBackend::Metal => wgpu::Backends::METAL,
|
||||||
let backend = match graphics_config.desired_backend {
|
GraphicsBackend::Vulkan => wgpu::Backends::VULKAN,
|
||||||
GraphicsBackend::Auto => backends,
|
GraphicsBackend::OpenGL => wgpu::Backends::GL,
|
||||||
GraphicsBackend::Dx12 => Backends::DX12,
|
|
||||||
GraphicsBackend::Metal => Backends::METAL,
|
|
||||||
GraphicsBackend::Vulkan => Backends::VULKAN,
|
|
||||||
GraphicsBackend::OpenGL => Backends::GL,
|
|
||||||
};
|
};
|
||||||
WgpuSetup::CreateNew {
|
WgpuSetup::CreateNew(setup)
|
||||||
supported_backends: backend,
|
|
||||||
power_preference,
|
|
||||||
device_descriptor,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// WgpuConfiguration::Default is CreateNew until we call run_eframe()
|
// WgpuConfiguration::Default is CreateNew until we call run_eframe()
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@@ -229,21 +219,3 @@ fn run_eframe(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// when compiling to web using trunk.
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn main() {
|
|
||||||
// Make sure panics are logged using `console.error`.
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
// Redirect tracing to console.log and friends:
|
|
||||||
tracing_wasm::set_as_global_default();
|
|
||||||
|
|
||||||
let web_options = eframe::WebOptions::default();
|
|
||||||
eframe::start_web(
|
|
||||||
"the_canvas_id", // hardcode it
|
|
||||||
web_options,
|
|
||||||
Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))),
|
|
||||||
)
|
|
||||||
.expect("failed to start eframe");
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat, TextStyle, Widget};
|
use egui::{Color32, FontFamily, FontId, TextFormat, TextStyle, Widget, text::LayoutJob};
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
|
||||||
use crate::fonts::load_font_if_needed;
|
use crate::fonts::load_font_if_needed;
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
#[cfg(all(windows, feature = "wsl"))]
|
#[cfg(all(windows, feature = "wsl"))]
|
||||||
use std::string::FromUtf16Error;
|
use std::string::FromUtf16Error;
|
||||||
use std::{
|
use std::{mem::take, path::MAIN_SEPARATOR};
|
||||||
mem::take,
|
|
||||||
path::{Path, PathBuf, MAIN_SEPARATOR},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "wsl"))]
|
#[cfg(all(windows, feature = "wsl"))]
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use egui::{
|
use egui::{
|
||||||
output::OpenUrl, text::LayoutJob, CollapsingHeader, FontFamily, FontId, RichText,
|
CollapsingHeader, FontFamily, FontId, RichText, SelectableLabel, TextFormat, Widget,
|
||||||
SelectableLabel, TextFormat, Widget,
|
output::OpenUrl, text::LayoutJob,
|
||||||
};
|
};
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
config::{DEFAULT_WATCH_PATTERNS, path::check_path_buf},
|
||||||
diff::{
|
diff::{
|
||||||
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
CONFIG_GROUPS, ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||||
ConfigPropertyValue, CONFIG_GROUPS,
|
ConfigPropertyValue,
|
||||||
},
|
},
|
||||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
jobs::{Job, JobQueue, JobResult, check_update::CheckUpdateResult},
|
||||||
};
|
};
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||||
@@ -89,7 +87,7 @@ impl ConfigViewState {
|
|||||||
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
||||||
let target_path = target_dir.join(obj_path);
|
let target_path = target_dir.join(obj_path);
|
||||||
guard.set_selected_obj(ObjectConfig {
|
guard.set_selected_obj(ObjectConfig {
|
||||||
name: obj_path.display().to_string(),
|
name: obj_path.to_string(),
|
||||||
target_path: Some(target_path),
|
target_path: Some(target_path),
|
||||||
base_path: Some(path),
|
base_path: Some(path),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -97,7 +95,7 @@ impl ConfigViewState {
|
|||||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||||
let base_path = base_dir.join(obj_path);
|
let base_path = base_dir.join(obj_path);
|
||||||
guard.set_selected_obj(ObjectConfig {
|
guard.set_selected_obj(ObjectConfig {
|
||||||
name: obj_path.display().to_string(),
|
name: obj_path.to_string(),
|
||||||
target_path: Some(path),
|
target_path: Some(path),
|
||||||
base_path: Some(base_path),
|
base_path: Some(base_path),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -169,10 +167,7 @@ pub fn config_ui(
|
|||||||
) {
|
) {
|
||||||
let mut state_guard = state.write().unwrap();
|
let mut state_guard = state.write().unwrap();
|
||||||
let AppState {
|
let AppState {
|
||||||
config:
|
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
|
||||||
AppConfig {
|
|
||||||
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
|
|
||||||
},
|
|
||||||
objects,
|
objects,
|
||||||
object_nodes,
|
object_nodes,
|
||||||
..
|
..
|
||||||
@@ -206,10 +201,7 @@ pub fn config_ui(
|
|||||||
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
|
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
ui.output_mut(|output| {
|
ui.ctx().open_url(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true });
|
||||||
output.open_url =
|
|
||||||
Some(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true })
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -223,9 +215,9 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
|
let selected_index = selected_obj
|
||||||
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
|
.as_ref()
|
||||||
});
|
.and_then(|selected_obj| objects.iter().position(|obj| obj.name == selected_obj.name));
|
||||||
let mut new_selected_index = selected_index;
|
let mut new_selected_index = selected_index;
|
||||||
if objects.is_empty() {
|
if objects.is_empty() {
|
||||||
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||||
@@ -324,22 +316,14 @@ pub fn config_ui(
|
|||||||
config_state.show_hidden,
|
config_state.show_hidden,
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
display_node(
|
display_node(ui, &mut new_selected_index, objects, &node, appearance, node_open);
|
||||||
ui,
|
|
||||||
&mut new_selected_index,
|
|
||||||
project_dir.as_deref(),
|
|
||||||
objects,
|
|
||||||
&node,
|
|
||||||
appearance,
|
|
||||||
node_open,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if new_selected_index != selected_index {
|
if new_selected_index != selected_index {
|
||||||
if let Some(idx) = new_selected_index {
|
if let Some(idx) = new_selected_index {
|
||||||
// Will set obj_changed, which will trigger a rebuild
|
// Will set obj_changed, which will trigger a rebuild
|
||||||
let config = ObjectConfig::from(&objects[idx]);
|
let config = objects[idx].clone();
|
||||||
state_guard.set_selected_obj(config);
|
state_guard.set_selected_obj(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,9 +337,8 @@ pub fn config_ui(
|
|||||||
fn display_unit(
|
fn display_unit(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<usize>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
|
||||||
name: &str,
|
name: &str,
|
||||||
units: &[ProjectObject],
|
units: &[ObjectConfig],
|
||||||
index: usize,
|
index: usize,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) {
|
) {
|
||||||
@@ -363,12 +346,8 @@ fn display_unit(
|
|||||||
let selected = *selected_obj == Some(index);
|
let selected = *selected_obj == Some(index);
|
||||||
let color = if selected {
|
let color = if selected {
|
||||||
appearance.emphasized_text_color
|
appearance.emphasized_text_color
|
||||||
} else if let Some(complete) = object.complete() {
|
} else if let Some(complete) = object.complete {
|
||||||
if complete {
|
if complete { appearance.insert_color } else { appearance.delete_color }
|
||||||
appearance.insert_color
|
|
||||||
} else {
|
|
||||||
appearance.delete_color
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
appearance.text_color
|
appearance.text_color
|
||||||
};
|
};
|
||||||
@@ -382,27 +361,23 @@ fn display_unit(
|
|||||||
.color(color),
|
.color(color),
|
||||||
)
|
)
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
if get_source_path(project_dir, object).is_some() {
|
if object.source_path.is_some() {
|
||||||
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
response.context_menu(|ui| object_context_ui(ui, object));
|
||||||
}
|
}
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*selected_obj = Some(index);
|
*selected_obj = Some(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
|
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
||||||
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
|
if let Some(source_path) = &object.source_path {
|
||||||
}
|
|
||||||
|
|
||||||
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
|
|
||||||
if let Some(source_path) = get_source_path(project_dir, object) {
|
|
||||||
if ui
|
if 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.display());
|
log::info!("Opening file {}", source_path);
|
||||||
if let Err(e) = open::that_detached(&source_path) {
|
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_menu();
|
ui.close_menu();
|
||||||
@@ -422,15 +397,14 @@ enum NodeOpen {
|
|||||||
fn display_node(
|
fn display_node(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<usize>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
units: &[ObjectConfig],
|
||||||
units: &[ProjectObject],
|
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
node_open: NodeOpen,
|
node_open: NodeOpen,
|
||||||
) {
|
) {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::Unit(name, idx) => {
|
ProjectObjectNode::Unit(name, idx) => {
|
||||||
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
|
display_unit(ui, selected_obj, name, units, *idx, appearance);
|
||||||
}
|
}
|
||||||
ProjectObjectNode::Dir(name, children) => {
|
ProjectObjectNode::Dir(name, children) => {
|
||||||
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
||||||
@@ -456,7 +430,7 @@ fn display_node(
|
|||||||
.open(open)
|
.open(open)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for node in children {
|
for node in children {
|
||||||
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
|
display_node(ui, selected_obj, units, node, appearance, node_open);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -473,7 +447,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_node(
|
fn filter_node(
|
||||||
units: &[ProjectObject],
|
units: &[ObjectConfig],
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
search: &str,
|
search: &str,
|
||||||
filter_diffable: bool,
|
filter_diffable: bool,
|
||||||
@@ -485,8 +459,8 @@ fn filter_node(
|
|||||||
let unit = &units[*idx];
|
let unit = &units[*idx];
|
||||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||||
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
||||||
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
|
&& (!filter_incomplete || matches!(unit.complete, None | Some(false)))
|
||||||
&& (show_hidden || !unit.hidden())
|
&& (show_hidden || !unit.hidden)
|
||||||
{
|
{
|
||||||
Some(node.clone())
|
Some(node.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -524,13 +498,16 @@ fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
|
fn format_path(path: &Option<Utf8PlatformPathBuf>, appearance: &Appearance) -> RichText {
|
||||||
let mut color = appearance.replace_color;
|
let mut color = appearance.replace_color;
|
||||||
let text = if let Some(dir) = path {
|
let text = if let Some(dir) = path {
|
||||||
if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) {
|
if let Some(rel) = dirs::home_dir()
|
||||||
format!("~{}{}", MAIN_SEPARATOR, rel.display())
|
.and_then(|home| check_path_buf(home).ok())
|
||||||
|
.and_then(|home| dir.strip_prefix(&home).ok())
|
||||||
|
{
|
||||||
|
format!("~{}{}", MAIN_SEPARATOR, rel)
|
||||||
} else {
|
} else {
|
||||||
format!("{}", dir.display())
|
dir.to_string()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
color = appearance.delete_color;
|
color = appearance.delete_color;
|
||||||
@@ -544,7 +521,7 @@ pub const CONFIG_DISABLED_TEXT: &str =
|
|||||||
|
|
||||||
fn pick_folder_ui(
|
fn pick_folder_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
dir: &Option<PathBuf>,
|
dir: &Option<Utf8PlatformPathBuf>,
|
||||||
label: &str,
|
label: &str,
|
||||||
tooltip: impl FnOnce(&mut egui::Ui),
|
tooltip: impl FnOnce(&mut egui::Ui),
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
|
|||||||
@@ -1,30 +1,113 @@
|
|||||||
use std::{cmp::min, default::Default, mem::take};
|
use std::{cmp::min, default::Default, mem::take};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Id, Label, RichText, Sense, Widget};
|
use egui::{Label, Sense, Widget, text::LayoutJob};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDiff},
|
diff::{
|
||||||
obj::ObjInfo,
|
DataDiff, DataDiffKind, DataRelocationDiff,
|
||||||
};
|
data::resolve_relocation,
|
||||||
use time::format_description;
|
display::{ContextItem, HoverItem, relocation_context, relocation_hover},
|
||||||
|
|
||||||
use crate::{
|
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_table},
|
|
||||||
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
|
|
||||||
write_text,
|
|
||||||
},
|
},
|
||||||
|
obj::Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
use super::diff::{context_menu_items_ui, hover_items_ui};
|
||||||
|
use crate::views::{appearance::Appearance, write_text};
|
||||||
|
|
||||||
fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> {
|
pub(crate) const BYTES_PER_ROW: usize = 16;
|
||||||
obj.sections.iter().position(|section| section.name == section_name)
|
|
||||||
|
fn data_row_hover(obj: &Object, diffs: &[(DataDiff, Vec<DataRelocationDiff>)]) -> Vec<HoverItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
|
||||||
|
let mut prev_reloc = None;
|
||||||
|
for reloc_diff in reloc_diffs {
|
||||||
|
let reloc = &reloc_diff.reloc;
|
||||||
|
if prev_reloc == Some(reloc) {
|
||||||
|
// Avoid showing consecutive duplicate relocations.
|
||||||
|
// We do this because a single relocation can span across multiple diffs if the
|
||||||
|
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prev_reloc = Some(reloc);
|
||||||
|
|
||||||
|
// TODO: Change hover text color depending on Insert/Delete/Replace kind
|
||||||
|
// let color = get_color_for_diff_kind(reloc_diff.kind, appearance);
|
||||||
|
|
||||||
|
let reloc = resolve_relocation(&obj.symbols, reloc);
|
||||||
|
out.append(&mut relocation_hover(obj, reloc));
|
||||||
|
}
|
||||||
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
fn data_row_context(
|
||||||
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
obj: &Object,
|
||||||
|
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
|
||||||
|
let mut prev_reloc = None;
|
||||||
|
for reloc_diff in reloc_diffs {
|
||||||
|
let reloc = &reloc_diff.reloc;
|
||||||
|
if prev_reloc == Some(reloc) {
|
||||||
|
// Avoid showing consecutive duplicate relocations.
|
||||||
|
// We do this because a single relocation can span across multiple diffs if the
|
||||||
|
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
prev_reloc = Some(reloc);
|
||||||
|
|
||||||
|
let reloc = resolve_relocation(&obj.symbols, reloc);
|
||||||
|
out.append(&mut relocation_context(obj, reloc, None));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_row_hover_ui(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
obj: &Object,
|
||||||
|
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
hover_items_ui(ui, data_row_hover(obj, diffs), appearance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_row_context_menu(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
obj: &Object,
|
||||||
|
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
|
||||||
|
column: usize,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
context_menu_items_ui(ui, data_row_context(obj, diffs), column, appearance);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color_for_diff_kind(diff_kind: DataDiffKind, appearance: &Appearance) -> egui::Color32 {
|
||||||
|
match diff_kind {
|
||||||
|
DataDiffKind::None => appearance.text_color,
|
||||||
|
DataDiffKind::Replace => appearance.replace_color,
|
||||||
|
DataDiffKind::Delete => appearance.delete_color,
|
||||||
|
DataDiffKind::Insert => appearance.insert_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn data_row_ui(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
obj: Option<&Object>,
|
||||||
|
address: usize,
|
||||||
|
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
|
||||||
|
appearance: &Appearance,
|
||||||
|
column: usize,
|
||||||
|
) {
|
||||||
|
if diffs.iter().any(|(dd, rds)| {
|
||||||
|
dd.kind != DataDiffKind::None || rds.iter().any(|rd| rd.kind != DataDiffKind::None)
|
||||||
|
}) {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
@@ -34,29 +117,36 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
|||||||
&mut job,
|
&mut job,
|
||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
);
|
);
|
||||||
|
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||||
let mut cur_addr = 0usize;
|
let mut cur_addr = 0usize;
|
||||||
for diff in diffs {
|
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||||
let base_color = match diff.kind {
|
let mut cur_addr_actual = address;
|
||||||
ObjDataDiffKind::None => appearance.text_color,
|
for (diff, reloc_diffs) in diffs {
|
||||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
let base_color = get_color_for_diff_kind(diff.kind, appearance);
|
||||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
|
||||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
|
||||||
};
|
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
let mut str = " ".repeat(diff.len);
|
let mut str = " ".repeat(diff.len);
|
||||||
str.push_str(" ".repeat(diff.len / 8).as_str());
|
let n1 = cur_addr / 8;
|
||||||
|
let n2 = (diff.len + cur_addr) / 8;
|
||||||
|
str.push_str(" ".repeat(n2 - n1).as_str());
|
||||||
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
|
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||||
cur_addr += diff.len;
|
cur_addr += diff.len;
|
||||||
} else {
|
} else {
|
||||||
let mut text = String::new();
|
|
||||||
for byte in &diff.data {
|
for byte in &diff.data {
|
||||||
text.push_str(format!("{byte:02x} ").as_str());
|
let mut byte_color = base_color;
|
||||||
|
if let Some(reloc_diff) = reloc_diffs.iter().find(|reloc_diff| {
|
||||||
|
reloc_diff.kind != DataDiffKind::None
|
||||||
|
&& reloc_diff.range.contains(&cur_addr_actual)
|
||||||
|
}) {
|
||||||
|
byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance);
|
||||||
|
}
|
||||||
|
let byte_text = format!("{byte:02x} ");
|
||||||
|
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
|
||||||
cur_addr += 1;
|
cur_addr += 1;
|
||||||
|
cur_addr_actual += 1;
|
||||||
if cur_addr % 8 == 0 {
|
if cur_addr % 8 == 0 {
|
||||||
text.push(' ');
|
write_text(" ", base_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cur_addr < BYTES_PER_ROW {
|
if cur_addr < BYTES_PER_ROW {
|
||||||
@@ -67,13 +157,8 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
|||||||
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
for diff in diffs {
|
for (diff, _) in diffs {
|
||||||
let base_color = match diff.kind {
|
let base_color = get_color_for_diff_kind(diff.kind, appearance);
|
||||||
ObjDataDiffKind::None => appearance.text_color,
|
|
||||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
|
||||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
|
||||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
|
||||||
};
|
|
||||||
if diff.data.is_empty() {
|
if diff.data.is_empty() {
|
||||||
write_text(
|
write_text(
|
||||||
" ".repeat(diff.len).as_str(),
|
" ".repeat(diff.len).as_str(),
|
||||||
@@ -94,22 +179,32 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
|||||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label::new(job).sense(Sense::click()).ui(ui);
|
|
||||||
// .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
|
let response = Label::new(job).sense(Sense::click()).ui(ui);
|
||||||
// .context_menu(|ui| ins_context_menu(ui, ins));
|
if let Some(obj) = obj {
|
||||||
|
response.context_menu(|ui| data_row_context_menu(ui, obj, diffs, column, appearance));
|
||||||
|
response.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diffs, appearance));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
pub(crate) fn split_diffs(
|
||||||
let mut split_diffs = Vec::<Vec<ObjDataDiff>>::new();
|
diffs: &[DataDiff],
|
||||||
let mut row_diffs = Vec::<ObjDataDiff>::new();
|
reloc_diffs: &[DataRelocationDiff],
|
||||||
|
) -> Vec<Vec<(DataDiff, Vec<DataRelocationDiff>)>> {
|
||||||
|
let mut split_diffs = Vec::<Vec<(DataDiff, Vec<DataRelocationDiff>)>>::new();
|
||||||
|
let mut row_diffs = Vec::<(DataDiff, Vec<DataRelocationDiff>)>::new();
|
||||||
|
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||||
let mut cur_addr = 0usize;
|
let mut cur_addr = 0usize;
|
||||||
|
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||||
|
let mut cur_addr_actual = 0usize;
|
||||||
for diff in diffs {
|
for diff in diffs {
|
||||||
let mut cur_len = 0usize;
|
let mut cur_len = 0usize;
|
||||||
while cur_len < diff.len {
|
while cur_len < diff.len {
|
||||||
let remaining_len = diff.len - cur_len;
|
let remaining_len = diff.len - cur_len;
|
||||||
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
|
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
|
||||||
let len = min(remaining_len, remaining_in_row);
|
let len = min(remaining_len, remaining_in_row);
|
||||||
row_diffs.push(ObjDataDiff {
|
|
||||||
|
let data_diff = DataDiff {
|
||||||
data: if diff.data.is_empty() {
|
data: if diff.data.is_empty() {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
@@ -117,9 +212,28 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
|||||||
},
|
},
|
||||||
kind: diff.kind,
|
kind: diff.kind,
|
||||||
len,
|
len,
|
||||||
// TODO
|
symbol: String::new(), // TODO
|
||||||
symbol: String::new(),
|
};
|
||||||
});
|
let row_reloc_diffs: Vec<DataRelocationDiff> = if diff.data.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len;
|
||||||
|
reloc_diffs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|reloc_diff| {
|
||||||
|
if reloc_diff.range.start < diff_range.end
|
||||||
|
&& diff_range.start < reloc_diff.range.end
|
||||||
|
{
|
||||||
|
Some(reloc_diff.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
let row_diff = (data_diff, row_reloc_diffs);
|
||||||
|
|
||||||
|
row_diffs.push(row_diff);
|
||||||
remaining_in_row -= len;
|
remaining_in_row -= len;
|
||||||
cur_len += len;
|
cur_len += len;
|
||||||
cur_addr += len;
|
cur_addr += len;
|
||||||
@@ -127,171 +241,10 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
|||||||
split_diffs.push(take(&mut row_diffs));
|
split_diffs.push(take(&mut row_diffs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cur_addr_actual += diff.data.len();
|
||||||
}
|
}
|
||||||
if !row_diffs.is_empty() {
|
if !row_diffs.is_empty() {
|
||||||
split_diffs.push(take(&mut row_diffs));
|
split_diffs.push(take(&mut row_diffs));
|
||||||
}
|
}
|
||||||
split_diffs
|
split_diffs
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct SectionDiffContext<'a> {
|
|
||||||
obj: &'a ObjInfo,
|
|
||||||
diff: &'a ObjDiff,
|
|
||||||
section_index: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SectionDiffContext<'a> {
|
|
||||||
pub fn new(obj: Option<&'a (ObjInfo, ObjDiff)>, section_name: Option<&str>) -> Option<Self> {
|
|
||||||
obj.map(|(obj, diff)| Self {
|
|
||||||
obj,
|
|
||||||
diff,
|
|
||||||
section_index: section_name.and_then(|section_name| find_section(obj, section_name)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_section(&self) -> bool { self.section_index.is_some() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_table_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
available_width: f32,
|
|
||||||
left_ctx: Option<SectionDiffContext<'_>>,
|
|
||||||
right_ctx: Option<SectionDiffContext<'_>>,
|
|
||||||
config: &Appearance,
|
|
||||||
) -> Option<()> {
|
|
||||||
let left_section = left_ctx
|
|
||||||
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
|
||||||
let right_section = right_ctx
|
|
||||||
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
|
||||||
let total_bytes = left_section
|
|
||||||
.or(right_section)?
|
|
||||||
.1
|
|
||||||
.data_diff
|
|
||||||
.iter()
|
|
||||||
.fold(0usize, |accum, item| accum + item.len);
|
|
||||||
if total_bytes == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
|
||||||
|
|
||||||
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
|
||||||
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
|
||||||
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
|
|
||||||
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
|
|
||||||
let i = row.index();
|
|
||||||
let address = i * BYTES_PER_ROW;
|
|
||||||
row.col(|ui| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(left_diffs) = &left_diffs {
|
|
||||||
data_row_ui(ui, address, &left_diffs[i], config);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(right_diffs) = &right_diffs {
|
|
||||||
data_row_ui(ui, address, &right_diffs[i], config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn data_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let section_name =
|
|
||||||
state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()).or_else(
|
|
||||||
|| state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()),
|
|
||||||
);
|
|
||||||
let left_ctx = SectionDiffContext::new(result.first_obj.as_ref(), section_name);
|
|
||||||
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if !right_ctx.is_some_and(|ctx| ctx.has_section())
|
|
||||||
&& !left_ctx.is_some_and(|ctx| ctx.has_section())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(section) =
|
|
||||||
left_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
|
||||||
{
|
|
||||||
ui.label(
|
|
||||||
RichText::new(section.name.clone())
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(section) =
|
|
||||||
right_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
|
||||||
{
|
|
||||||
ui.label(
|
|
||||||
RichText::new(section.name.clone())
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table
|
|
||||||
let id =
|
|
||||||
Id::new(state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()))
|
|
||||||
.with(state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()));
|
|
||||||
ui.push_id(id, |ui| {
|
|
||||||
data_table_ui(ui, available_width, left_ctx, right_ctx, appearance);
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub fn demangle_window(
|
|||||||
ui.colored_label(appearance.replace_color, &demangled);
|
ui.colored_label(appearance.replace_color, &demangled);
|
||||||
});
|
});
|
||||||
if ui.button("Copy").clicked() {
|
if ui.button("Copy").clicked() {
|
||||||
ui.output_mut(|output| output.copied_text = demangled);
|
ctx.copy_text(demangled);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
|||||||
876
objdiff-gui/src/views/diff.rs
Normal file
876
objdiff-gui/src/views/diff.rs
Normal file
@@ -0,0 +1,876 @@
|
|||||||
|
use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJob};
|
||||||
|
use objdiff_core::{
|
||||||
|
build::BuildStatus,
|
||||||
|
diff::{
|
||||||
|
DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff,
|
||||||
|
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
|
||||||
|
},
|
||||||
|
obj::{Object, Section, Symbol},
|
||||||
|
};
|
||||||
|
use time::format_description;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
hotkeys,
|
||||||
|
views::{
|
||||||
|
appearance::Appearance,
|
||||||
|
column_layout::{render_header, render_strips, render_table},
|
||||||
|
data_diff::{BYTES_PER_ROW, data_row_ui, split_diffs},
|
||||||
|
extab_diff::extab_ui,
|
||||||
|
function_diff::{FunctionDiffContext, asm_col_ui},
|
||||||
|
symbol_diff::{
|
||||||
|
DiffViewAction, DiffViewNavigation, DiffViewState, SymbolDiffContext, SymbolRefByName,
|
||||||
|
View, match_color_for_symbol, symbol_context_menu_ui, symbol_hover_ui, symbol_list_ui,
|
||||||
|
},
|
||||||
|
write_text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum SelectedSymbol {
|
||||||
|
Symbol(usize),
|
||||||
|
Section(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct DiffColumnContext<'a> {
|
||||||
|
status: &'a BuildStatus,
|
||||||
|
obj: Option<&'a (Object, ObjectDiff)>,
|
||||||
|
section: Option<(&'a Section, &'a SectionDiff, usize)>,
|
||||||
|
symbol: Option<(&'a Symbol, &'a SymbolDiff, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DiffColumnContext<'a> {
|
||||||
|
pub fn new(
|
||||||
|
view: View,
|
||||||
|
status: &'a BuildStatus,
|
||||||
|
obj: Option<&'a (Object, ObjectDiff)>,
|
||||||
|
selected_symbol: Option<&SymbolRefByName>,
|
||||||
|
) -> Self {
|
||||||
|
let selected_symbol = match view {
|
||||||
|
View::SymbolDiff => None,
|
||||||
|
View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) {
|
||||||
|
(Some(obj), Some(s)) => find_symbol(&obj.0, s).map(SelectedSymbol::Symbol),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
View::DataDiff => match (obj, selected_symbol) {
|
||||||
|
(Some(obj), Some(SymbolRefByName { section_name: Some(section_name), .. })) => {
|
||||||
|
find_section(&obj.0, section_name).map(SelectedSymbol::Section)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let (section, symbol) = match (obj, selected_symbol) {
|
||||||
|
(Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => {
|
||||||
|
let symbol = &obj.symbols[symbol_ref];
|
||||||
|
(
|
||||||
|
symbol.section.map(|section_idx| {
|
||||||
|
(&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)
|
||||||
|
}),
|
||||||
|
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => (
|
||||||
|
Some((&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
Self { status, obj, section, symbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn has_symbol(&self) -> bool { self.section.is_some() || self.symbol.is_some() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> Option<&str> {
|
||||||
|
self.symbol
|
||||||
|
.map(|(symbol, _, _)| symbol.name.as_str())
|
||||||
|
.or_else(|| self.section.map(|(section, _, _)| section.name.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn diff_view_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &DiffViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
let Some(result) = &state.build else {
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_ctx = DiffColumnContext::new(
|
||||||
|
state.current_view,
|
||||||
|
&result.first_status,
|
||||||
|
result.first_obj.as_ref(),
|
||||||
|
state.symbol_state.left_symbol.as_ref(),
|
||||||
|
);
|
||||||
|
let right_ctx = DiffColumnContext::new(
|
||||||
|
state.current_view,
|
||||||
|
&result.second_status,
|
||||||
|
result.second_obj.as_ref(),
|
||||||
|
state.symbol_state.right_symbol.as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if we need to perform any navigation
|
||||||
|
let current_navigation = DiffViewNavigation {
|
||||||
|
kind: match state.current_view {
|
||||||
|
View::ExtabDiff => SymbolNavigationKind::Extab,
|
||||||
|
_ => SymbolNavigationKind::Normal,
|
||||||
|
},
|
||||||
|
left_symbol: left_ctx.symbol.map(|(_, _, idx)| idx),
|
||||||
|
right_symbol: right_ctx.symbol.map(|(_, _, idx)| idx),
|
||||||
|
};
|
||||||
|
let mut navigation = current_navigation.clone();
|
||||||
|
if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol {
|
||||||
|
// If a matching symbol appears, select it
|
||||||
|
if !right_ctx.has_symbol() {
|
||||||
|
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
||||||
|
navigation.right_symbol = Some(target_symbol_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if navigation.left_symbol.is_some()
|
||||||
|
&& left_ctx.obj.is_some()
|
||||||
|
&& left_ctx.section.is_none()
|
||||||
|
{
|
||||||
|
// Clear selection if symbol goes missing
|
||||||
|
navigation.left_symbol = None;
|
||||||
|
}
|
||||||
|
if let Some((_symbol, symbol_diff, _symbol_idx)) = right_ctx.symbol {
|
||||||
|
// If a matching symbol appears, select it
|
||||||
|
if !left_ctx.has_symbol() {
|
||||||
|
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
|
||||||
|
navigation.left_symbol = Some(target_symbol_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if navigation.right_symbol.is_some()
|
||||||
|
&& right_ctx.obj.is_some()
|
||||||
|
&& right_ctx.section.is_none()
|
||||||
|
{
|
||||||
|
// Clear selection if symbol goes missing
|
||||||
|
navigation.right_symbol = None;
|
||||||
|
}
|
||||||
|
// If both sides are missing a symbol, switch to symbol diff view
|
||||||
|
if navigation.left_symbol.is_none() && navigation.right_symbol.is_none() {
|
||||||
|
navigation = DiffViewNavigation::default();
|
||||||
|
}
|
||||||
|
// Execute navigation if it changed
|
||||||
|
if navigation != current_navigation && state.post_build_nav.is_none() {
|
||||||
|
ret = Some(DiffViewAction::Navigate(navigation));
|
||||||
|
}
|
||||||
|
|
||||||
|
let available_width = ui.available_width();
|
||||||
|
let mut open_sections = (None, None);
|
||||||
|
|
||||||
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
|
if column == 0 {
|
||||||
|
// Left column
|
||||||
|
|
||||||
|
// First row
|
||||||
|
if state.current_view == View::SymbolDiff {
|
||||||
|
ui.label(RichText::new("Target object").text_style(egui::TextStyle::Monospace));
|
||||||
|
} else {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
||||||
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((symbol, _, _)) = left_ctx.symbol {
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
!state.scratch_running
|
||||||
|
&& state.scratch_available
|
||||||
|
&& left_ctx.has_symbol(),
|
||||||
|
egui::Button::new("📲 decomp.me"),
|
||||||
|
)
|
||||||
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
|
.on_disabled_hover_text("Scratch configuration missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second row
|
||||||
|
if !left_ctx.status.success {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Fail")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.delete_color),
|
||||||
|
);
|
||||||
|
} else if state.current_view == View::SymbolDiff {
|
||||||
|
if left_ctx.obj.is_some() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(state.object_name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((symbol, _symbol_diff, symbol_idx)) = left_ctx.symbol {
|
||||||
|
if let Some(action) =
|
||||||
|
symbol_label_ui(ui, left_ctx, symbol, symbol_idx, column, appearance)
|
||||||
|
{
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if let Some((section, _, _)) = left_ctx.section {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(section.name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if right_ctx.has_symbol() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Choose target symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third row
|
||||||
|
if left_ctx.has_symbol() && right_ctx.has_symbol() {
|
||||||
|
if state.current_view == View::FunctionDiff
|
||||||
|
&& ui
|
||||||
|
.button("Change target")
|
||||||
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_target_shortcut(ui.ctx())
|
||||||
|
{
|
||||||
|
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
||||||
|
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if left_ctx.status.success && !left_ctx.has_symbol() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut search = state.search.clone();
|
||||||
|
let response =
|
||||||
|
TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
||||||
|
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
if response.changed() {
|
||||||
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
|
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||||
|
open_sections.0 = Some(true);
|
||||||
|
}
|
||||||
|
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||||
|
{
|
||||||
|
open_sections.0 = Some(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
// Right column
|
||||||
|
|
||||||
|
// First row
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
||||||
|
ret = Some(DiffViewAction::Build);
|
||||||
|
}
|
||||||
|
if state.build_running {
|
||||||
|
ui.colored_label(
|
||||||
|
appearance.replace_color,
|
||||||
|
RichText::new("Building…").text_style(egui::TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(RichText::new("Last built:").text_style(egui::TextStyle::Monospace));
|
||||||
|
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
|
ui.label(
|
||||||
|
RichText::new(
|
||||||
|
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
||||||
|
)
|
||||||
|
.text_style(egui::TextStyle::Monospace),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
||||||
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::OpenSourcePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second row
|
||||||
|
if !right_ctx.status.success {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Fail")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.delete_color),
|
||||||
|
);
|
||||||
|
} else if state.current_view == View::SymbolDiff {
|
||||||
|
if right_ctx.obj.is_some() {
|
||||||
|
if left_ctx.obj.is_some() {
|
||||||
|
ui.label(RichText::new("Base object").font(appearance.code_font.clone()));
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(state.object_name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((symbol, _symbol_diff, symbol_idx)) = right_ctx.symbol {
|
||||||
|
if let Some(action) =
|
||||||
|
symbol_label_ui(ui, right_ctx, symbol, symbol_idx, column, appearance)
|
||||||
|
{
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if let Some((section, _, _)) = right_ctx.section {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(section.name.clone())
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
} else if left_ctx.has_symbol() {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Choose base symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third row
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if let Some((_, symbol_diff, _symbol_idx)) = right_ctx.symbol {
|
||||||
|
let mut needs_separator = false;
|
||||||
|
if let Some(match_percent) = symbol_diff.match_percent {
|
||||||
|
let response = ui.label(
|
||||||
|
RichText::new(format!("{:.2}%", match_percent))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(match_color_for_symbol(match_percent, appearance)),
|
||||||
|
);
|
||||||
|
if let Some((diff_score, max_score)) = symbol_diff.diff_score {
|
||||||
|
response.on_hover_ui_at_pointer(|ui| {
|
||||||
|
ui.label(
|
||||||
|
RichText::new(format!("Score: {}/{}", diff_score, max_score))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.text_color),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
needs_separator = true;
|
||||||
|
}
|
||||||
|
if state.current_view == View::FunctionDiff && left_ctx.has_symbol() {
|
||||||
|
if needs_separator {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.button("Change base")
|
||||||
|
.on_hover_text_at_pointer(
|
||||||
|
"Choose a different symbol to use as the base",
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|
||||||
|
{
|
||||||
|
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
||||||
|
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if right_ctx.status.success && !right_ctx.has_symbol() {
|
||||||
|
let mut search = state.search.clone();
|
||||||
|
let response =
|
||||||
|
TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
||||||
|
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||||
|
response.request_focus();
|
||||||
|
}
|
||||||
|
if response.changed() {
|
||||||
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||||
|
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||||
|
open_sections.1 = Some(true);
|
||||||
|
}
|
||||||
|
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||||
|
{
|
||||||
|
open_sections.1 = Some(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Table
|
||||||
|
ui.push_id(Id::new(left_ctx.id()).with(right_ctx.id()), |ui| {
|
||||||
|
if let (
|
||||||
|
View::FunctionDiff,
|
||||||
|
Some((left_obj, left_diff)),
|
||||||
|
Some((right_obj, right_diff)),
|
||||||
|
Some((_, left_symbol_diff, left_symbol_idx)),
|
||||||
|
Some((_, right_symbol_diff, right_symbol_idx)),
|
||||||
|
) = (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol)
|
||||||
|
{
|
||||||
|
// Joint diff view
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, true);
|
||||||
|
if left_symbol_diff.instruction_rows.len() != right_symbol_diff.instruction_rows.len() {
|
||||||
|
ui.label("Instruction count mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let instructions_len = left_symbol_diff.instruction_rows.len();
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width,
|
||||||
|
2,
|
||||||
|
appearance.code_font.size,
|
||||||
|
instructions_len,
|
||||||
|
|row, column| {
|
||||||
|
if column == 0 {
|
||||||
|
if let Some(action) = asm_col_ui(
|
||||||
|
row,
|
||||||
|
FunctionDiffContext {
|
||||||
|
obj: left_obj,
|
||||||
|
diff: left_diff,
|
||||||
|
symbol_ref: Some(left_symbol_idx),
|
||||||
|
},
|
||||||
|
appearance,
|
||||||
|
&state.function_state,
|
||||||
|
diff_config,
|
||||||
|
column,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
if let Some(action) = asm_col_ui(
|
||||||
|
row,
|
||||||
|
FunctionDiffContext {
|
||||||
|
obj: right_obj,
|
||||||
|
diff: right_diff,
|
||||||
|
symbol_ref: Some(right_symbol_idx),
|
||||||
|
},
|
||||||
|
appearance,
|
||||||
|
&state.function_state,
|
||||||
|
diff_config,
|
||||||
|
column,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
if row.response().clicked() {
|
||||||
|
ret = Some(DiffViewAction::ClearDiffHighlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let (
|
||||||
|
View::DataDiff,
|
||||||
|
Some((left_obj, _left_diff)),
|
||||||
|
Some((right_obj, _right_diff)),
|
||||||
|
Some((_left_section, left_section_diff, _left_symbol_idx)),
|
||||||
|
Some((_right_section, right_section_diff, _right_symbol_idx)),
|
||||||
|
) =
|
||||||
|
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section)
|
||||||
|
{
|
||||||
|
// Joint diff view
|
||||||
|
let left_total_bytes =
|
||||||
|
left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
let right_total_bytes =
|
||||||
|
right_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
if left_total_bytes != right_total_bytes {
|
||||||
|
ui.label("Data size mismatch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if left_total_bytes == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||||
|
let left_diffs =
|
||||||
|
split_diffs(&left_section_diff.data_diff, &left_section_diff.reloc_diff);
|
||||||
|
let right_diffs =
|
||||||
|
split_diffs(&right_section_diff.data_diff, &right_section_diff.reloc_diff);
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width,
|
||||||
|
2,
|
||||||
|
appearance.code_font.size,
|
||||||
|
total_rows,
|
||||||
|
|row, column| {
|
||||||
|
let i = row.index();
|
||||||
|
let address = i * BYTES_PER_ROW;
|
||||||
|
row.col(|ui| {
|
||||||
|
if column == 0 {
|
||||||
|
data_row_ui(
|
||||||
|
ui,
|
||||||
|
Some(left_obj),
|
||||||
|
address,
|
||||||
|
&left_diffs[i],
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
);
|
||||||
|
} else if column == 1 {
|
||||||
|
data_row_ui(
|
||||||
|
ui,
|
||||||
|
Some(right_obj),
|
||||||
|
address,
|
||||||
|
&right_diffs[i],
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Split view
|
||||||
|
render_strips(ui, available_width, 2, |ui, column| {
|
||||||
|
if column == 0 {
|
||||||
|
if let Some(action) = diff_col_ui(
|
||||||
|
ui,
|
||||||
|
state,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
left_ctx,
|
||||||
|
right_ctx,
|
||||||
|
available_width,
|
||||||
|
open_sections.0,
|
||||||
|
diff_config,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
if let Some(action) = diff_col_ui(
|
||||||
|
ui,
|
||||||
|
state,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
right_ctx,
|
||||||
|
left_ctx,
|
||||||
|
available_width,
|
||||||
|
open_sections.1,
|
||||||
|
diff_config,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_label_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
ctx: DiffColumnContext,
|
||||||
|
symbol: &Symbol,
|
||||||
|
symbol_idx: usize,
|
||||||
|
column: usize,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let (obj, diff) = ctx.obj.unwrap();
|
||||||
|
let ctx = SymbolDiffContext { obj, diff };
|
||||||
|
let mut ret = None;
|
||||||
|
egui::Label::new(
|
||||||
|
RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name))
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
)
|
||||||
|
.selectable(false)
|
||||||
|
// TODO .show_tooltip_when_elided(false)
|
||||||
|
// https://github.com/emilk/egui/commit/071e090e2b2601e5ed4726a63a753188503dfaf2
|
||||||
|
.ui(ui)
|
||||||
|
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance))
|
||||||
|
.context_menu(|ui| {
|
||||||
|
let section = symbol.section.and_then(|section_idx| ctx.obj.sections.get(section_idx));
|
||||||
|
if let Some(result) =
|
||||||
|
symbol_context_menu_ui(ui, ctx, symbol_idx, symbol, section, column, appearance)
|
||||||
|
{
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn diff_col_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &DiffViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
column: usize,
|
||||||
|
ctx: DiffColumnContext,
|
||||||
|
other_ctx: DiffColumnContext,
|
||||||
|
available_width: f32,
|
||||||
|
open_sections: Option<bool>,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
if !ctx.status.success {
|
||||||
|
build_log_ui(ui, ctx.status, appearance);
|
||||||
|
} else if let Some((obj, diff)) = ctx.obj {
|
||||||
|
if let Some((_symbol, symbol_diff, symbol_idx)) = ctx.symbol {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
|
let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_idx) };
|
||||||
|
if state.current_view == View::ExtabDiff {
|
||||||
|
extab_ui(ui, ctx, appearance, column);
|
||||||
|
} else {
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width / 2.0,
|
||||||
|
1,
|
||||||
|
appearance.code_font.size,
|
||||||
|
symbol_diff.instruction_rows.len(),
|
||||||
|
|row, column| {
|
||||||
|
if let Some(action) = asm_col_ui(
|
||||||
|
row,
|
||||||
|
ctx,
|
||||||
|
appearance,
|
||||||
|
&state.function_state,
|
||||||
|
diff_config,
|
||||||
|
column,
|
||||||
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
if row.response().clicked() {
|
||||||
|
ret = Some(DiffViewAction::ClearDiffHighlight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if let Some((_section, section_diff, _section_idx)) = ctx.section {
|
||||||
|
hotkeys::check_scroll_hotkeys(ui, false);
|
||||||
|
let total_bytes =
|
||||||
|
section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||||
|
if total_bytes == 0 {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||||
|
let diffs = split_diffs(§ion_diff.data_diff, §ion_diff.reloc_diff);
|
||||||
|
render_table(
|
||||||
|
ui,
|
||||||
|
available_width / 2.0,
|
||||||
|
1,
|
||||||
|
appearance.code_font.size,
|
||||||
|
total_rows,
|
||||||
|
|row, _column| {
|
||||||
|
let i = row.index();
|
||||||
|
let address = i * BYTES_PER_ROW;
|
||||||
|
row.col(|ui| {
|
||||||
|
data_row_ui(ui, Some(obj), address, &diffs[i], appearance, column);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if let Some((_other_symbol, _other_symbol_diff, other_symbol_idx)) = other_ctx.symbol
|
||||||
|
{
|
||||||
|
if let Some(action) = symbol_list_ui(
|
||||||
|
ui,
|
||||||
|
SymbolDiffContext { obj, diff },
|
||||||
|
&state.symbol_state,
|
||||||
|
SymbolFilter::Mapping(other_symbol_idx, None),
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
open_sections,
|
||||||
|
) {
|
||||||
|
match (column, action) {
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DiffViewAction::Navigate(DiffViewNavigation {
|
||||||
|
left_symbol: Some(symbol_idx),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
) => {
|
||||||
|
ret = Some(DiffViewAction::SetMapping(symbol_idx, other_symbol_idx));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DiffViewAction::Navigate(DiffViewNavigation {
|
||||||
|
right_symbol: Some(symbol_idx),
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
) => {
|
||||||
|
ret = Some(DiffViewAction::SetMapping(other_symbol_idx, symbol_idx));
|
||||||
|
}
|
||||||
|
(_, action) => {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let filter = match &state.search_regex {
|
||||||
|
Some(regex) => SymbolFilter::Search(regex),
|
||||||
|
_ => SymbolFilter::None,
|
||||||
|
};
|
||||||
|
if let Some(result) = symbol_list_ui(
|
||||||
|
ui,
|
||||||
|
SymbolDiffContext { obj, diff },
|
||||||
|
&state.symbol_state,
|
||||||
|
filter,
|
||||||
|
appearance,
|
||||||
|
column,
|
||||||
|
open_sections,
|
||||||
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
missing_obj_ui(ui, appearance);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
|
||||||
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if !status.cmdline.is_empty() && ui.button("Copy command").clicked() {
|
||||||
|
ui.ctx().copy_text(status.cmdline.clone());
|
||||||
|
}
|
||||||
|
if ui.button("Copy log").clicked() {
|
||||||
|
ui.ctx().copy_text(format!("{}\n{}", status.stdout, status.stderr));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
|
if !status.cmdline.is_empty() {
|
||||||
|
ui.label(&status.cmdline);
|
||||||
|
}
|
||||||
|
if !status.stdout.is_empty() {
|
||||||
|
ui.colored_label(appearance.replace_color, &status.stdout);
|
||||||
|
}
|
||||||
|
if !status.stderr.is_empty() {
|
||||||
|
ui.colored_label(appearance.delete_color, &status.stderr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
||||||
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
|
|
||||||
|
ui.colored_label(appearance.replace_color, "No object configured");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_symbol(obj: &Object, selected_symbol: &SymbolRefByName) -> Option<usize> {
|
||||||
|
obj.symbols.iter().position(|symbol| symbol.name == selected_symbol.symbol_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_section(obj: &Object, section_name: &str) -> Option<usize> {
|
||||||
|
obj.sections.iter().position(|section| section.name == section_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_items_ui(ui: &mut Ui, items: Vec<HoverItem>, appearance: &Appearance) {
|
||||||
|
for item in items {
|
||||||
|
match item {
|
||||||
|
HoverItem::Text { label, value, color } => {
|
||||||
|
let mut job = LayoutJob::default();
|
||||||
|
if !label.is_empty() {
|
||||||
|
let label_color = match color {
|
||||||
|
HoverItemColor::Special => appearance.replace_color,
|
||||||
|
_ => appearance.highlight_color,
|
||||||
|
};
|
||||||
|
write_text(&label, label_color, &mut job, appearance.code_font.clone());
|
||||||
|
write_text(": ", label_color, &mut job, appearance.code_font.clone());
|
||||||
|
}
|
||||||
|
write_text(
|
||||||
|
&value,
|
||||||
|
match color {
|
||||||
|
HoverItemColor::Emphasized => appearance.highlight_color,
|
||||||
|
_ => appearance.text_color,
|
||||||
|
},
|
||||||
|
&mut job,
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
);
|
||||||
|
ui.label(job);
|
||||||
|
}
|
||||||
|
HoverItem::Separator => {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context_menu_items_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
items: Vec<ContextItem>,
|
||||||
|
column: usize,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
for item in items {
|
||||||
|
match item {
|
||||||
|
ContextItem::Copy { value, label } => {
|
||||||
|
let mut job = LayoutJob::default();
|
||||||
|
write_text(
|
||||||
|
"Copy \"",
|
||||||
|
appearance.text_color,
|
||||||
|
&mut job,
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
);
|
||||||
|
write_text(
|
||||||
|
&value,
|
||||||
|
appearance.highlight_color,
|
||||||
|
&mut job,
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
);
|
||||||
|
write_text("\"", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
|
if let Some(label) = label {
|
||||||
|
write_text(" (", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
|
write_text(
|
||||||
|
&label,
|
||||||
|
appearance.text_color,
|
||||||
|
&mut job,
|
||||||
|
appearance.code_font.clone(),
|
||||||
|
);
|
||||||
|
write_text(")", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
|
}
|
||||||
|
if ui.button(job).clicked() {
|
||||||
|
ui.ctx().copy_text(value);
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextItem::Navigate { label, symbol_index, kind } => {
|
||||||
|
if ui.button(label).clicked() {
|
||||||
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::new(
|
||||||
|
kind,
|
||||||
|
symbol_index,
|
||||||
|
column,
|
||||||
|
)));
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextItem::Separator => {
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
@@ -1,22 +1,10 @@
|
|||||||
use egui::{RichText, ScrollArea};
|
use egui::ScrollArea;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ppc::ExceptionInfo,
|
arch::ppc::ExceptionInfo,
|
||||||
obj::{ObjInfo, ObjSymbol},
|
obj::{Object, Symbol},
|
||||||
};
|
};
|
||||||
use time::format_description;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
|
||||||
column_layout::{render_header, render_strips},
|
|
||||||
function_diff::FunctionDiffContext,
|
|
||||||
symbol_diff::{
|
|
||||||
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState,
|
|
||||||
SymbolRefByName, View,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn decode_extab(extab: &ExceptionInfo) -> String {
|
fn decode_extab(extab: &ExceptionInfo) -> String {
|
||||||
let mut text = String::from("");
|
let mut text = String::from("");
|
||||||
@@ -38,14 +26,16 @@ fn decode_extab(extab: &ExceptionInfo) -> String {
|
|||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_extab_entry<'a>(obj: &'a ObjInfo, symbol: &ObjSymbol) -> Option<&'a ExceptionInfo> {
|
fn find_extab_entry<'a>(_obj: &'a Object, _symbol: &Symbol) -> Option<&'a ExceptionInfo> {
|
||||||
obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol))
|
// TODO
|
||||||
|
// obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol))
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extab_text_ui(
|
fn extab_text_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
symbol: &ObjSymbol,
|
symbol: &Symbol,
|
||||||
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) {
|
||||||
@@ -57,7 +47,7 @@ fn extab_text_ui(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extab_ui(
|
pub(crate) fn extab_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
@@ -68,186 +58,11 @@ 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((_section, symbol)) =
|
if let Some(symbol) =
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
ctx.symbol_ref.and_then(|symbol_ref| ctx.obj.symbols.get(symbol_ref))
|
||||||
{
|
{
|
||||||
extab_text_ui(ui, ctx, symbol, appearance);
|
extab_text_ui(ui, ctx, symbol, appearance);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn extab_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut left_ctx = FunctionDiffContext::new(
|
|
||||||
result.first_obj.as_ref(),
|
|
||||||
state.symbol_state.left_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
let mut right_ctx = FunctionDiffContext::new(
|
|
||||||
result.second_obj.as_ref(),
|
|
||||||
state.symbol_state.right_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If one side is missing a symbol, but the diff process found a match, use that symbol
|
|
||||||
let left_diff_symbol = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
|
||||||
right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
|
||||||
right_symbol: Some(symbol_ref),
|
|
||||||
}));
|
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
|
||||||
left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: Some(symbol_ref),
|
|
||||||
right_symbol: state.symbol_state.right_symbol.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(
|
|
||||||
!state.scratch_running
|
|
||||||
&& state.scratch_available
|
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
|
||||||
egui::Button::new("📲 decomp.me"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
|
||||||
.on_disabled_hover_text("Scratch configuration missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
if let Some((_section, symbol)) = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
|
||||||
}) {
|
|
||||||
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((_section, symbol)) = left_ctx
|
|
||||||
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
|
||||||
{
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| {
|
|
||||||
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
|
||||||
ui.label(
|
|
||||||
RichText::new(format!("{:.0}%", match_percent.floor()))
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
|
|
||||||
// Table
|
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
extab_ui(ui, ctx, appearance, column);
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
extab_ui(ui, ctx, appearance, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
||||||
|
|
||||||
|
use objdiff_core::config::path::check_path_buf;
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use rfd::FileHandle;
|
use rfd::FileHandle;
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub enum FileDialogResult {
|
pub enum FileDialogResult {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
ProjectDir(PathBuf),
|
ProjectDir(Utf8PlatformPathBuf),
|
||||||
TargetDir(PathBuf),
|
TargetDir(Utf8PlatformPathBuf),
|
||||||
BaseDir(PathBuf),
|
BaseDir(Utf8PlatformPathBuf),
|
||||||
Object(PathBuf),
|
Object(Utf8PlatformPathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -22,7 +24,7 @@ impl FileDialogState {
|
|||||||
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
||||||
where
|
where
|
||||||
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
||||||
ResultCb: FnOnce(PathBuf) -> FileDialogResult + Send + 'static,
|
ResultCb: FnOnce(Utf8PlatformPathBuf) -> FileDialogResult + Send + 'static,
|
||||||
{
|
{
|
||||||
if self.thread.is_some() {
|
if self.thread.is_some() {
|
||||||
return;
|
return;
|
||||||
@@ -30,7 +32,8 @@ impl FileDialogState {
|
|||||||
let future = init();
|
let future = init();
|
||||||
self.thread = Some(std::thread::spawn(move || {
|
self.thread = Some(std::thread::spawn(move || {
|
||||||
if let Some(handle) = future.block_on() {
|
if let Some(handle) = future.block_on() {
|
||||||
result_cb(PathBuf::from(handle))
|
let path = PathBuf::from(handle);
|
||||||
|
check_path_buf(path).map(result_cb).unwrap_or(FileDialogResult::None)
|
||||||
} else {
|
} else {
|
||||||
FileDialogResult::None
|
FileDialogResult::None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,14 +61,9 @@ impl FrameHistory {
|
|||||||
);
|
);
|
||||||
egui::warn_if_debug_build(ui);
|
egui::warn_if_debug_build(ui);
|
||||||
|
|
||||||
if !cfg!(target_arch = "wasm32") {
|
egui::CollapsingHeader::new("📊 CPU usage history").default_open(false).show(ui, |ui| {
|
||||||
egui::CollapsingHeader::new("📊 CPU usage history").default_open(false).show(
|
|
||||||
ui,
|
|
||||||
|ui| {
|
|
||||||
self.graph(ui);
|
self.graph(ui);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
@@ -91,9 +86,10 @@ impl FrameHistory {
|
|||||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||||
shapes.push(Shape::Rect(epaint::RectShape::new(
|
shapes.push(Shape::Rect(epaint::RectShape::new(
|
||||||
rect,
|
rect,
|
||||||
style.rounding,
|
style.corner_radius,
|
||||||
ui.visuals().extreme_bg_color,
|
ui.visuals().extreme_bg_color,
|
||||||
ui.style().noninteractive().bg_stroke,
|
ui.style().noninteractive().bg_stroke,
|
||||||
|
StrokeKind::Inside,
|
||||||
)));
|
)));
|
||||||
|
|
||||||
let rect = rect.shrink(4.0);
|
let rect = rect.shrink(4.0);
|
||||||
|
|||||||
@@ -1,29 +1,23 @@
|
|||||||
use std::{cmp::Ordering, default::Default};
|
use std::{cmp::Ordering, default::Default};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Id, Label, Layout, Response, RichText, Sense, Widget};
|
use egui::{Label, Response, Sense, Widget, text::LayoutJob};
|
||||||
use egui_extras::TableRow;
|
use egui_extras::TableRow;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{
|
diff::{
|
||||||
display::{display_diff, DiffText, HighlightKind},
|
DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff,
|
||||||
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
display::{
|
||||||
|
DiffText, DiffTextColor, DiffTextSegment, HighlightKind, display_row,
|
||||||
|
instruction_context, instruction_hover,
|
||||||
},
|
},
|
||||||
obj::{
|
|
||||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSectionKind, ObjSymbol,
|
|
||||||
SymbolRef,
|
|
||||||
},
|
},
|
||||||
|
obj::{InstructionArgValue, InstructionRef, Object},
|
||||||
|
util::ReallySigned,
|
||||||
};
|
};
|
||||||
use time::format_description;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{
|
||||||
hotkeys,
|
|
||||||
views::{
|
|
||||||
appearance::Appearance,
|
appearance::Appearance,
|
||||||
column_layout::{render_header, render_strips, render_table},
|
diff::{context_menu_items_ui, hover_items_ui},
|
||||||
symbol_diff::{
|
symbol_diff::DiffViewAction,
|
||||||
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
|
|
||||||
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -79,266 +73,127 @@ impl FunctionViewState {
|
|||||||
|
|
||||||
fn ins_hover_ui(
|
fn ins_hover_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
obj: &ObjInfo,
|
obj: &Object,
|
||||||
section: &ObjSection,
|
symbol_idx: usize,
|
||||||
ins: &ObjIns,
|
ins_ref: InstructionRef,
|
||||||
symbol: &ObjSymbol,
|
diff_config: &DiffObjConfig,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) {
|
) {
|
||||||
ui.scope(|ui| {
|
let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.colored_label(appearance.delete_color, "Failed to resolve instruction");
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
return;
|
||||||
|
|
||||||
let offset = ins.address - section.address;
|
|
||||||
ui.label(format!(
|
|
||||||
"{:02x?}",
|
|
||||||
§ion.data[offset as usize..(offset + ins.size as u64) as usize]
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(virtual_address) = symbol.virtual_address {
|
|
||||||
let offset = ins.address - symbol.address;
|
|
||||||
ui.colored_label(
|
|
||||||
appearance.replace_color,
|
|
||||||
format!("Virtual address: {:#x}", virtual_address + offset),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(orig) = &ins.orig {
|
|
||||||
ui.label(format!("Original: {}", orig));
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in &ins.args {
|
|
||||||
if let ObjInsArg::Arg(arg) = arg {
|
|
||||||
match arg {
|
|
||||||
ObjInsArgValue::Signed(v) => {
|
|
||||||
ui.label(format!("{arg} == {v}"));
|
|
||||||
}
|
|
||||||
ObjInsArgValue::Unsigned(v) => {
|
|
||||||
ui.label(format!("{arg} == {v}"));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(reloc) = &ins.reloc {
|
|
||||||
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
|
|
||||||
let addend_str = match reloc.addend.cmp(&0i64) {
|
|
||||||
Ordering::Greater => format!("+{:x}", reloc.addend),
|
|
||||||
Ordering::Less => format!("-{:x}", -reloc.addend),
|
|
||||||
_ => "".to_string(),
|
|
||||||
};
|
};
|
||||||
|
let ins = match obj.arch.process_instruction(resolved, diff_config) {
|
||||||
|
Ok(ins) => ins,
|
||||||
|
Err(e) => {
|
||||||
ui.colored_label(
|
ui.colored_label(
|
||||||
appearance.highlight_color,
|
appearance.delete_color,
|
||||||
format!("Name: {}{}", reloc.target.name, addend_str),
|
format!("Failed to process instruction: {e}"),
|
||||||
);
|
|
||||||
if let Some(orig_section_index) = reloc.target.orig_section_index {
|
|
||||||
if let Some(section) =
|
|
||||||
obj.sections.iter().find(|s| s.orig_index == orig_section_index)
|
|
||||||
{
|
|
||||||
ui.colored_label(
|
|
||||||
appearance.highlight_color,
|
|
||||||
format!("Section: {}", section.name),
|
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ui.colored_label(
|
};
|
||||||
appearance.highlight_color,
|
|
||||||
format!("Address: {:x}{}", reloc.target.address, addend_str),
|
|
||||||
);
|
|
||||||
ui.colored_label(
|
|
||||||
appearance.highlight_color,
|
|
||||||
format!("Size: {:x}", reloc.target.size),
|
|
||||||
);
|
|
||||||
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
|
|
||||||
if let Some(s) = obj.arch.guess_data_type(ins).and_then(|ty| {
|
|
||||||
obj.arch.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
|
|
||||||
}) {
|
|
||||||
ui.colored_label(appearance.highlight_color, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.highlight_color, "Extern".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(decoded) = rlwinmdec::decode(&ins.formatted) {
|
|
||||||
ui.colored_label(appearance.highlight_color, decoded.trim());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ins_context_menu(ui: &mut egui::Ui, section: &ObjSection, ins: &ObjIns, symbol: &ObjSymbol) {
|
|
||||||
ui.scope(|ui| {
|
ui.scope(|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::Wrap);
|
||||||
|
hover_items_ui(ui, instruction_hover(obj, resolved, &ins), appearance);
|
||||||
if ui.button(format!("Copy \"{}\"", ins.formatted)).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text.clone_from(&ins.formatted));
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hex_string = "0x".to_string();
|
|
||||||
for byte in §ion.data[ins.address as usize..(ins.address + ins.size as u64) as usize] {
|
|
||||||
hex_string.push_str(&format!("{:02x}", byte));
|
|
||||||
}
|
|
||||||
if ui.button(format!("Copy \"{hex_string}\" (instruction bytes)")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = hex_string);
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(virtual_address) = symbol.virtual_address {
|
|
||||||
let offset = ins.address - symbol.address;
|
|
||||||
let offset_string = format!("{:#x}", virtual_address + offset);
|
|
||||||
if ui.button(format!("Copy \"{offset_string}\" (virtual address)")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = offset_string);
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for arg in &ins.args {
|
|
||||||
if let ObjInsArg::Arg(arg) = arg {
|
|
||||||
match arg {
|
|
||||||
ObjInsArgValue::Signed(v) => {
|
|
||||||
if ui.button(format!("Copy \"{arg}\"")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = arg.to_string());
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
if ui.button(format!("Copy \"{v}\"")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = v.to_string());
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObjInsArgValue::Unsigned(v) => {
|
|
||||||
if ui.button(format!("Copy \"{arg}\"")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = arg.to_string());
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
if ui.button(format!("Copy \"{v}\"")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text = v.to_string());
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(reloc) = &ins.reloc {
|
|
||||||
if let Some(name) = &reloc.target.demangled_name {
|
|
||||||
if ui.button(format!("Copy \"{name}\"")).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text.clone_from(name));
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() {
|
|
||||||
ui.output_mut(|output| output.copied_text.clone_from(&reloc.target.name));
|
|
||||||
ui.close_menu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> {
|
fn ins_context_menu(
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
ui: &mut egui::Ui,
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
obj: &Object,
|
||||||
if symbol.name == selected_symbol.symbol_name {
|
symbol_idx: usize,
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
ins_ref: InstructionRef,
|
||||||
|
column: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) {
|
||||||
|
let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else {
|
||||||
|
ui.colored_label(appearance.delete_color, "Failed to resolve instruction");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ins = match obj.arch.process_instruction(resolved, diff_config) {
|
||||||
|
Ok(ins) => ins,
|
||||||
|
Err(e) => {
|
||||||
|
ui.colored_label(
|
||||||
|
appearance.delete_color,
|
||||||
|
format!("Failed to process instruction: {e}"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
None
|
ui.scope(|ui| {
|
||||||
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
||||||
|
context_menu_items_ui(ui, instruction_context(obj, resolved, &ins), column, appearance);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
fn diff_text_ui(
|
fn diff_text_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
text: DiffText<'_>,
|
segment: DiffTextSegment,
|
||||||
ins_diff: &ObjInsDiff,
|
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
column: usize,
|
column: usize,
|
||||||
space_width: f32,
|
space_width: f32,
|
||||||
response_cb: impl Fn(Response) -> Response,
|
response_cb: impl Fn(Response) -> Response,
|
||||||
) -> Option<DiffViewAction> {
|
) -> Option<DiffViewAction> {
|
||||||
let mut ret = None;
|
let highlight_kind = HighlightKind::from(&segment.text);
|
||||||
let label_text;
|
let label_text = match segment.text {
|
||||||
let mut base_color = match ins_diff.kind {
|
DiffText::Basic(text) => text.to_string(),
|
||||||
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
DiffText::Line(num) => format!("{num} "),
|
||||||
appearance.text_color
|
DiffText::Address(addr) => format!("{:x}:", addr),
|
||||||
}
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
ObjInsDiffKind::Replace => appearance.replace_color,
|
DiffText::Argument(arg) => match arg {
|
||||||
ObjInsDiffKind::Delete => appearance.delete_color,
|
InstructionArgValue::Signed(v) => format!("{:#x}", ReallySigned(v)),
|
||||||
ObjInsDiffKind::Insert => appearance.insert_color,
|
InstructionArgValue::Unsigned(v) => format!("{:#x}", v),
|
||||||
};
|
InstructionArgValue::Opaque(v) => v.into_owned(),
|
||||||
let mut pad_to = 0;
|
},
|
||||||
match text {
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
DiffText::Basic(text) => {
|
DiffText::Symbol(sym) => sym.demangled_name.as_ref().unwrap_or(&sym.name).clone(),
|
||||||
label_text = text.to_string();
|
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||||
}
|
Ordering::Greater => format!("+{:#x}", addend),
|
||||||
DiffText::BasicColor(s, idx) => {
|
Ordering::Less => format!("-{:#x}", -addend),
|
||||||
label_text = s.to_string();
|
_ => String::new(),
|
||||||
base_color = appearance.diff_colors[idx % appearance.diff_colors.len()];
|
},
|
||||||
}
|
|
||||||
DiffText::Line(num) => {
|
|
||||||
label_text = num.to_string();
|
|
||||||
base_color = appearance.deemphasized_text_color;
|
|
||||||
pad_to = 5;
|
|
||||||
}
|
|
||||||
DiffText::Address(addr) => {
|
|
||||||
label_text = format!("{:x}:", addr);
|
|
||||||
pad_to = 5;
|
|
||||||
}
|
|
||||||
DiffText::Opcode(mnemonic, _op) => {
|
|
||||||
label_text = mnemonic.to_string();
|
|
||||||
if ins_diff.kind == ObjInsDiffKind::OpMismatch {
|
|
||||||
base_color = appearance.replace_color;
|
|
||||||
}
|
|
||||||
pad_to = 8;
|
|
||||||
}
|
|
||||||
DiffText::Argument(arg, diff) => {
|
|
||||||
label_text = arg.to_string();
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffText::BranchDest(addr, diff) => {
|
|
||||||
label_text = format!("{addr:x}");
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffText::Symbol(sym, diff) => {
|
|
||||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
|
||||||
label_text = name.clone();
|
|
||||||
if let Some(diff) = diff {
|
|
||||||
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
|
|
||||||
} else {
|
|
||||||
base_color = appearance.emphasized_text_color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffText::Spacing(n) => {
|
DiffText::Spacing(n) => {
|
||||||
ui.add_space(n as f32 * space_width);
|
ui.add_space(n as f32 * space_width);
|
||||||
return ret;
|
return None;
|
||||||
}
|
|
||||||
DiffText::Eol => {
|
|
||||||
label_text = "\n".to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
DiffText::Eol => "\n".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let len = label_text.len();
|
let len = label_text.len();
|
||||||
let highlight = *ins_view_state.highlight(column) == text;
|
let highlight = highlight_kind != HighlightKind::None
|
||||||
|
&& *ins_view_state.highlight(column) == highlight_kind;
|
||||||
|
let color = match segment.color {
|
||||||
|
DiffTextColor::Normal => appearance.text_color,
|
||||||
|
DiffTextColor::Dim => appearance.deemphasized_text_color,
|
||||||
|
DiffTextColor::Bright => appearance.emphasized_text_color,
|
||||||
|
DiffTextColor::Replace => appearance.replace_color,
|
||||||
|
DiffTextColor::Delete => appearance.delete_color,
|
||||||
|
DiffTextColor::Insert => appearance.insert_color,
|
||||||
|
DiffTextColor::Rotating(i) => {
|
||||||
|
appearance.diff_colors[i as usize % appearance.diff_colors.len()]
|
||||||
|
}
|
||||||
|
};
|
||||||
let mut response = Label::new(LayoutJob::single_section(
|
let mut response = Label::new(LayoutJob::single_section(
|
||||||
label_text,
|
label_text,
|
||||||
appearance.code_text_format(base_color, highlight),
|
appearance.code_text_format(color, highlight),
|
||||||
))
|
))
|
||||||
.sense(Sense::click())
|
.sense(Sense::click())
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
response = response_cb(response);
|
response = response_cb(response);
|
||||||
|
let mut ret = None;
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
ret = Some(DiffViewAction::SetDiffHighlight(column, text.into()));
|
ret = Some(DiffViewAction::SetDiffHighlight(column, highlight_kind));
|
||||||
}
|
}
|
||||||
if len < pad_to {
|
if len < segment.pad_to as usize {
|
||||||
ui.add_space((pad_to - len) as f32 * space_width);
|
ui.add_space((segment.pad_to as usize - len) as f32 * space_width);
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
@@ -346,66 +201,70 @@ fn diff_text_ui(
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
fn asm_row_ui(
|
fn asm_row_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ins_diff: &ObjInsDiff,
|
obj: &Object,
|
||||||
symbol: &ObjSymbol,
|
ins_diff: &InstructionDiffRow,
|
||||||
|
symbol_idx: usize,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
column: usize,
|
column: usize,
|
||||||
response_cb: impl Fn(Response) -> Response,
|
response_cb: impl Fn(Response) -> Response,
|
||||||
) -> Option<DiffViewAction> {
|
) -> Option<DiffViewAction> {
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
if ins_diff.kind != ObjInsDiffKind::None {
|
if ins_diff.kind != InstructionDiffKind::None {
|
||||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||||
}
|
}
|
||||||
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
|
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
|
||||||
display_diff(ins_diff, symbol.address, |text| {
|
display_row(obj, symbol_idx, ins_diff, diff_config, |segment| {
|
||||||
if let Some(action) = diff_text_ui(
|
if let Some(action) =
|
||||||
ui,
|
diff_text_ui(ui, segment, appearance, ins_view_state, column, space_width, &response_cb)
|
||||||
text,
|
{
|
||||||
ins_diff,
|
|
||||||
appearance,
|
|
||||||
ins_view_state,
|
|
||||||
column,
|
|
||||||
space_width,
|
|
||||||
&response_cb,
|
|
||||||
) {
|
|
||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
Ok::<_, ()>(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn asm_col_ui(
|
pub(crate) fn asm_col_ui(
|
||||||
row: &mut TableRow<'_, '_>,
|
row: &mut TableRow<'_, '_>,
|
||||||
ctx: FunctionDiffContext<'_>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
column: usize,
|
column: usize,
|
||||||
) -> Option<DiffViewAction> {
|
) -> Option<DiffViewAction> {
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
let symbol_ref = ctx.symbol_ref?;
|
let symbol_ref = ctx.symbol_ref?;
|
||||||
let (section, symbol) = ctx.obj.section_symbol(symbol_ref);
|
let ins_row = &ctx.diff.symbols[symbol_ref].instruction_rows[row.index()];
|
||||||
let section = section?;
|
|
||||||
let ins_diff = &ctx.diff.symbol_diff(symbol_ref).instructions[row.index()];
|
|
||||||
let response_cb = |response: Response| {
|
let response_cb = |response: Response| {
|
||||||
if let Some(ins) = &ins_diff.ins {
|
if let Some(ins_ref) = ins_row.ins_ref {
|
||||||
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
|
response.context_menu(|ui| {
|
||||||
|
ins_context_menu(ui, ctx.obj, symbol_ref, ins_ref, column, diff_config, appearance)
|
||||||
|
});
|
||||||
response.on_hover_ui_at_pointer(|ui| {
|
response.on_hover_ui_at_pointer(|ui| {
|
||||||
ins_hover_ui(ui, ctx.obj, section, ins, symbol, appearance)
|
ins_hover_ui(ui, ctx.obj, symbol_ref, ins_ref, diff_config, appearance)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (_, response) = row.col(|ui| {
|
let (_, response) = row.col(|ui| {
|
||||||
if let Some(action) =
|
if let Some(action) = asm_row_ui(
|
||||||
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb)
|
ui,
|
||||||
{
|
ctx.obj,
|
||||||
|
ins_row,
|
||||||
|
symbol_ref,
|
||||||
|
appearance,
|
||||||
|
ins_view_state,
|
||||||
|
diff_config,
|
||||||
|
column,
|
||||||
|
response_cb,
|
||||||
|
) {
|
||||||
ret = Some(action);
|
ret = Some(action);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -413,464 +272,9 @@ fn asm_col_ui(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
#[expect(clippy::too_many_arguments)]
|
|
||||||
fn asm_table_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
available_width: f32,
|
|
||||||
left_ctx: Option<FunctionDiffContext<'_>>,
|
|
||||||
right_ctx: Option<FunctionDiffContext<'_>>,
|
|
||||||
appearance: &Appearance,
|
|
||||||
ins_view_state: &FunctionViewState,
|
|
||||||
symbol_state: &SymbolViewState,
|
|
||||||
open_sections: (Option<bool>, Option<bool>),
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let left_len = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len())
|
|
||||||
});
|
|
||||||
let right_len = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len())
|
|
||||||
});
|
|
||||||
let instructions_len = match (left_len, right_len) {
|
|
||||||
(Some(left_len), Some(right_len)) => {
|
|
||||||
if left_len != right_len {
|
|
||||||
ui.label("Instruction count mismatch");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
left_len
|
|
||||||
}
|
|
||||||
(Some(left_len), None) => left_len,
|
|
||||||
(None, Some(right_len)) => right_len,
|
|
||||||
(None, None) => {
|
|
||||||
ui.label("No symbol selected");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if left_len.is_some() && right_len.is_some() {
|
|
||||||
// Joint view
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, true);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width,
|
|
||||||
2,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Split view, one side is the symbol list
|
|
||||||
render_strips(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
if let Some(ctx) = left_ctx {
|
|
||||||
if ctx.has_symbol() {
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, false);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width / 2.0,
|
|
||||||
1,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if let Some((right_ctx, right_symbol_ref)) =
|
|
||||||
right_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref)))
|
|
||||||
{
|
|
||||||
if let Some(action) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj: ctx.obj, diff: ctx.diff },
|
|
||||||
None,
|
|
||||||
symbol_state,
|
|
||||||
SymbolFilter::Mapping(right_symbol_ref),
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.0,
|
|
||||||
) {
|
|
||||||
match action {
|
|
||||||
DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
left_symbol: Some(left_symbol_ref),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.obj.section_symbol(right_symbol_ref);
|
|
||||||
ret = Some(DiffViewAction::SetMapping(
|
|
||||||
match right_section.map(|s| s.kind) {
|
|
||||||
Some(ObjSectionKind::Code) => View::FunctionDiff,
|
|
||||||
_ => View::SymbolDiff,
|
|
||||||
},
|
|
||||||
left_symbol_ref,
|
|
||||||
SymbolRefByName::new(right_symbol, right_section),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label("No left object");
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
if let Some(ctx) = right_ctx {
|
|
||||||
if ctx.has_symbol() {
|
|
||||||
hotkeys::check_scroll_hotkeys(ui, false);
|
|
||||||
render_table(
|
|
||||||
ui,
|
|
||||||
available_width / 2.0,
|
|
||||||
1,
|
|
||||||
appearance.code_font.size,
|
|
||||||
instructions_len,
|
|
||||||
|row, column| {
|
|
||||||
if let Some(action) =
|
|
||||||
asm_col_ui(row, ctx, appearance, ins_view_state, column)
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
if row.response().clicked() {
|
|
||||||
ret = Some(DiffViewAction::ClearDiffHighlight);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if let Some((left_ctx, left_symbol_ref)) =
|
|
||||||
left_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref)))
|
|
||||||
{
|
|
||||||
if let Some(action) = symbol_list_ui(
|
|
||||||
ui,
|
|
||||||
SymbolDiffContext { obj: ctx.obj, diff: ctx.diff },
|
|
||||||
None,
|
|
||||||
symbol_state,
|
|
||||||
SymbolFilter::Mapping(left_symbol_ref),
|
|
||||||
appearance,
|
|
||||||
column,
|
|
||||||
open_sections.1,
|
|
||||||
) {
|
|
||||||
match action {
|
|
||||||
DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
right_symbol: Some(right_symbol_ref),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.obj.section_symbol(left_symbol_ref);
|
|
||||||
ret = Some(DiffViewAction::SetMapping(
|
|
||||||
match left_section.map(|s| s.kind) {
|
|
||||||
Some(ObjSectionKind::Code) => View::FunctionDiff,
|
|
||||||
_ => View::SymbolDiff,
|
|
||||||
},
|
|
||||||
SymbolRefByName::new(left_symbol, left_section),
|
|
||||||
right_symbol_ref,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label("No right object");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct FunctionDiffContext<'a> {
|
pub struct FunctionDiffContext<'a> {
|
||||||
pub obj: &'a ObjInfo,
|
pub obj: &'a Object,
|
||||||
pub diff: &'a ObjDiff,
|
pub diff: &'a ObjectDiff,
|
||||||
pub symbol_ref: Option<SymbolRef>,
|
pub symbol_ref: Option<usize>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FunctionDiffContext<'a> {
|
|
||||||
pub fn new(
|
|
||||||
obj: Option<&'a (ObjInfo, ObjDiff)>,
|
|
||||||
selected_symbol: Option<&SymbolRefByName>,
|
|
||||||
) -> Option<Self> {
|
|
||||||
obj.map(|(obj, diff)| Self {
|
|
||||||
obj,
|
|
||||||
diff,
|
|
||||||
symbol_ref: selected_symbol.and_then(|s| find_symbol(obj, s)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn has_symbol(&self) -> bool { self.symbol_ref.is_some() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn function_diff_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
state: &DiffViewState,
|
|
||||||
appearance: &Appearance,
|
|
||||||
) -> Option<DiffViewAction> {
|
|
||||||
let mut ret = None;
|
|
||||||
let Some(result) = &state.build else {
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut left_ctx = FunctionDiffContext::new(
|
|
||||||
result.first_obj.as_ref(),
|
|
||||||
state.symbol_state.left_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
let mut right_ctx = FunctionDiffContext::new(
|
|
||||||
result.second_obj.as_ref(),
|
|
||||||
state.symbol_state.right_symbol.as_ref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If one side is missing a symbol, but the diff process found a match, use that symbol
|
|
||||||
let left_diff_symbol = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
let right_diff_symbol = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
|
|
||||||
});
|
|
||||||
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (right_section, right_symbol) =
|
|
||||||
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
|
|
||||||
right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: state.symbol_state.left_symbol.clone(),
|
|
||||||
right_symbol: Some(symbol_ref),
|
|
||||||
}));
|
|
||||||
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
|
|
||||||
let (left_section, left_symbol) =
|
|
||||||
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
|
|
||||||
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
|
|
||||||
left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref));
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation {
|
|
||||||
view: Some(View::FunctionDiff),
|
|
||||||
left_symbol: Some(symbol_ref),
|
|
||||||
right_symbol: state.symbol_state.right_symbol.clone(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both sides are missing a symbol, switch to symbol diff view
|
|
||||||
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
|
|
||||||
{
|
|
||||||
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header
|
|
||||||
let available_width = ui.available_width();
|
|
||||||
let mut open_sections = (None, None);
|
|
||||||
render_header(ui, available_width, 2, |ui, column| {
|
|
||||||
if column == 0 {
|
|
||||||
// Left column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) {
|
|
||||||
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
|
||||||
}
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(
|
|
||||||
!state.scratch_running
|
|
||||||
&& state.scratch_available
|
|
||||||
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
|
|
||||||
egui::Button::new("📲 decomp.me"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
|
||||||
.on_disabled_hover_text("Scratch configuration missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
if let Some((_section, symbol)) = left_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
|
||||||
}) {
|
|
||||||
ret = Some(DiffViewAction::CreateScratch(symbol.name.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some((_section, symbol)) = left_ctx
|
|
||||||
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
|
||||||
{
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
if right_ctx.is_some_and(|m| m.has_symbol())
|
|
||||||
&& (ui
|
|
||||||
.button("Change target")
|
|
||||||
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
|
||||||
.clicked()
|
|
||||||
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
|
|
||||||
{
|
|
||||||
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
|
|
||||||
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Choose target symbol")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.0 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
|
||||||
{
|
|
||||||
open_sections.0 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if column == 1 {
|
|
||||||
// Right column
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
|
||||||
ret = Some(DiffViewAction::Build);
|
|
||||||
}
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
if state.build_running {
|
|
||||||
ui.colored_label(appearance.replace_color, "Building…");
|
|
||||||
} else {
|
|
||||||
ui.label("Last built:");
|
|
||||||
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
|
||||||
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
ret = Some(DiffViewAction::OpenSourcePath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
|
||||||
ctx.symbol_ref.map(|symbol_ref| {
|
|
||||||
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
|
||||||
})
|
|
||||||
}) {
|
|
||||||
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
|
||||||
ui.label(
|
|
||||||
RichText::new(name)
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
|
||||||
ui.label(
|
|
||||||
RichText::new(format!("{:.0}%", match_percent.floor()))
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(match_color_for_symbol(match_percent, appearance)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if left_ctx.is_some_and(|m| m.has_symbol()) {
|
|
||||||
ui.separator();
|
|
||||||
if ui
|
|
||||||
.button("Change base")
|
|
||||||
.on_hover_text_at_pointer(
|
|
||||||
"Choose a different symbol to use as the base",
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|
|
||||||
{
|
|
||||||
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
|
|
||||||
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Missing")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.replace_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(
|
|
||||||
RichText::new("Choose base symbol")
|
|
||||||
.font(appearance.code_font.clone())
|
|
||||||
.color(appearance.highlight_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
|
||||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
|
||||||
open_sections.1 = Some(true);
|
|
||||||
}
|
|
||||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
|
||||||
{
|
|
||||||
open_sections.1 = Some(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table
|
|
||||||
let id = Id::new(state.symbol_state.left_symbol.as_ref().map(|s| s.symbol_name.as_str()))
|
|
||||||
.with(state.symbol_state.right_symbol.as_ref().map(|s| s.symbol_name.as_str()));
|
|
||||||
if let Some(action) = ui
|
|
||||||
.push_id(id, |ui| {
|
|
||||||
asm_table_ui(
|
|
||||||
ui,
|
|
||||||
available_width,
|
|
||||||
left_ctx,
|
|
||||||
right_ctx,
|
|
||||||
appearance,
|
|
||||||
&state.function_state,
|
|
||||||
&state.symbol_state,
|
|
||||||
open_sections,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.inner
|
|
||||||
{
|
|
||||||
ret = Some(action);
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use egui::{text::LayoutJob, Context, FontId, RichText, TextFormat, TextStyle, Window};
|
use egui::{Context, FontId, RichText, TextFormat, TextStyle, Window, text::LayoutJob};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumMessage, IntoEnumIterator};
|
|
||||||
|
|
||||||
use crate::views::{appearance::Appearance, frame_history::FrameHistory};
|
use crate::views::{appearance::Appearance, frame_history::FrameHistory};
|
||||||
|
|
||||||
@@ -20,23 +19,24 @@ pub struct GraphicsViewState {
|
|||||||
pub should_relaunch: bool,
|
pub should_relaunch: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
Copy, Clone, Debug, Default, PartialEq, Eq, EnumIter, EnumMessage, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
pub enum GraphicsBackend {
|
pub enum GraphicsBackend {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto")]
|
|
||||||
Auto,
|
Auto,
|
||||||
#[strum(message = "Vulkan")]
|
|
||||||
Vulkan,
|
Vulkan,
|
||||||
#[strum(message = "Metal")]
|
|
||||||
Metal,
|
Metal,
|
||||||
#[strum(message = "DirectX 12")]
|
|
||||||
Dx12,
|
Dx12,
|
||||||
#[strum(message = "OpenGL")]
|
|
||||||
OpenGL,
|
OpenGL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ALL_BACKENDS: &[GraphicsBackend] = &[
|
||||||
|
GraphicsBackend::Auto,
|
||||||
|
GraphicsBackend::Vulkan,
|
||||||
|
GraphicsBackend::Metal,
|
||||||
|
GraphicsBackend::Dx12,
|
||||||
|
GraphicsBackend::OpenGL,
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct GraphicsConfig {
|
pub struct GraphicsConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -70,6 +70,16 @@ impl GraphicsBackend {
|
|||||||
GraphicsBackend::OpenGL => true,
|
GraphicsBackend::OpenGL => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
GraphicsBackend::Auto => "Auto",
|
||||||
|
GraphicsBackend::Vulkan => "Vulkan",
|
||||||
|
GraphicsBackend::Metal => "Metal",
|
||||||
|
GraphicsBackend::Dx12 => "DirectX 12",
|
||||||
|
GraphicsBackend::OpenGL => "OpenGL",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn graphics_window(
|
pub fn graphics_window(
|
||||||
@@ -134,9 +144,9 @@ pub fn graphics_window(
|
|||||||
ui.add_enabled_ui(state.graphics_config_path.is_some(), |ui| {
|
ui.add_enabled_ui(state.graphics_config_path.is_some(), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Desired backend:");
|
ui.label("Desired backend:");
|
||||||
for backend in GraphicsBackend::iter().filter(GraphicsBackend::is_supported) {
|
for backend in ALL_BACKENDS.iter().copied().filter(GraphicsBackend::is_supported) {
|
||||||
let selected = state.graphics_config.desired_backend == backend;
|
let selected = state.graphics_config.desired_backend == backend;
|
||||||
if ui.selectable_label(selected, backend.get_message().unwrap()).clicked() {
|
if ui.selectable_label(selected, backend.display_name()).clicked() {
|
||||||
let prev_backend = state.graphics_config.desired_backend;
|
let prev_backend = state.graphics_config.desired_backend;
|
||||||
state.graphics_config.desired_backend = backend;
|
state.graphics_config.desired_backend = backend;
|
||||||
match save_graphics_config(
|
match save_graphics_config(
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance)
|
|||||||
.on_hover_text_at_pointer(RichText::new(&err_string).color(appearance.delete_color))
|
.on_hover_text_at_pointer(RichText::new(&err_string).color(appearance.delete_color))
|
||||||
.context_menu(|ui| {
|
.context_menu(|ui| {
|
||||||
if ui.button("Copy full message").clicked() {
|
if ui.button("Copy full message").clicked() {
|
||||||
ui.output_mut(|o| o.copied_text = err_string);
|
ui.ctx().copy_text(err_string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -61,7 +61,7 @@ pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance)
|
|||||||
.on_hover_text_at_pointer(&status.status)
|
.on_hover_text_at_pointer(&status.status)
|
||||||
.context_menu(|ui| {
|
.context_menu(|ui| {
|
||||||
if ui.button("Copy full message").clicked() {
|
if ui.button("Copy full message").clicked() {
|
||||||
ui.output_mut(|o| o.copied_text = status.status.clone());
|
ui.ctx().copy_text(status.status.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use egui::{text::LayoutJob, Color32, FontId, TextFormat};
|
use egui::{Color32, FontId, TextFormat, text::LayoutJob};
|
||||||
|
|
||||||
pub(crate) mod appearance;
|
pub(crate) mod appearance;
|
||||||
pub(crate) mod column_layout;
|
pub(crate) mod column_layout;
|
||||||
@@ -6,6 +6,7 @@ pub(crate) mod config;
|
|||||||
pub(crate) mod data_diff;
|
pub(crate) mod data_diff;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod demangle;
|
pub(crate) mod demangle;
|
||||||
|
pub(crate) mod diff;
|
||||||
pub(crate) mod extab_diff;
|
pub(crate) mod extab_diff;
|
||||||
pub(crate) mod file;
|
pub(crate) mod file;
|
||||||
pub(crate) mod frame_history;
|
pub(crate) mod frame_history;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub fn rlwinm_decode_window(
|
|||||||
ui.colored_label(appearance.replace_color, decoded.trim());
|
ui.colored_label(appearance.replace_color, decoded.trim());
|
||||||
});
|
});
|
||||||
if ui.button("Copy").clicked() {
|
if ui.button("Copy").clicked() {
|
||||||
ui.output_mut(|output| output.copied_text = decoded);
|
ctx.copy_text(decoded);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user