mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-15 16:16:15 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2b7a9ef25 | |||
| 2cf9cf24d6 | |||
|
|
5ef3416457 | ||
|
|
6ff8d002f7 | ||
| 9ca157d717 | |||
|
|
67b63311fc | ||
| 72ea1c8911 | |||
| d4a540857d | |||
| 676488433f | |||
| 83de98b5ee | |||
| c1ba4e91d1 | |||
| 575900024d | |||
| cbe299e859 | |||
| 741d93e211 | |||
| 603dbd6882 | |||
| 6fb0a63de2 | |||
| ab2e84a2c6 | |||
| 9596051cb4 |
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld"
|
||||||
|
|
||||||
|
[target.aarch64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld"
|
||||||
18
.github/workflows/build.yaml
vendored
18
.github/workflows/build.yaml
vendored
@@ -30,6 +30,8 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-features --all-targets
|
run: cargo check --all-features --all-targets
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
@@ -85,6 +87,8 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --all-features
|
run: cargo test --release --all-features
|
||||||
|
|
||||||
@@ -142,11 +146,19 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
run: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
||||||
- 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 }}
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
@@ -198,6 +210,10 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,10 +3,6 @@ target/
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
generated/
|
generated/
|
||||||
|
|
||||||
# cargo-mobile
|
|
||||||
.cargo/
|
|
||||||
/gen
|
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
40
Cargo.lock
generated
40
Cargo.lock
generated
@@ -395,6 +395,15 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bimap"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -1517,15 +1526,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
@@ -1542,9 +1551,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1553,21 +1562,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
@@ -2852,7 +2861,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-cli"
|
name = "objdiff-cli"
|
||||||
version = "2.2.2"
|
version = "2.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argp",
|
"argp",
|
||||||
@@ -2874,10 +2883,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-core"
|
name = "objdiff-core"
|
||||||
version = "2.2.2"
|
version = "2.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arm-attr",
|
"arm-attr",
|
||||||
|
"bimap",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
"console_log",
|
||||||
@@ -2913,7 +2923,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-gui"
|
name = "objdiff-gui"
|
||||||
version = "2.2.2"
|
version = "2.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.2.2"
|
version = "2.3.4"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -133,6 +133,13 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ref": "#/$defs/metadata"
|
"ref": "#/$defs/metadata"
|
||||||
|
},
|
||||||
|
"symbol_mappings": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Manual symbol mappings from target to base.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ feature-depth = 1
|
|||||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
# output a note when they are encountered.
|
# output a note when they are encountered.
|
||||||
ignore = [
|
ignore = [
|
||||||
"RUSTSEC-2024-0370",
|
|
||||||
#{ 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" },
|
||||||
@@ -240,7 +239,7 @@ 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 = ["encounter"]
|
github = ["notify-rs"]
|
||||||
# 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
|
||||||
|
|||||||
@@ -102,7 +102,12 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
let unit_path =
|
let unit_path =
|
||||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
||||||
|
|
||||||
let Some(object) = project_config.objects.iter_mut().find_map(|obj| {
|
let Some(object) = project_config
|
||||||
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter_mut()
|
||||||
|
.find_map(|obj| {
|
||||||
if obj.name.as_deref() == Some(u) {
|
if obj.name.as_deref() == Some(u) {
|
||||||
resolve_paths(obj);
|
resolve_paths(obj);
|
||||||
return Some(obj);
|
return Some(obj);
|
||||||
@@ -121,7 +126,8 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}) else {
|
})
|
||||||
|
else {
|
||||||
bail!("Unit not found: {}", u)
|
bail!("Unit not found: {}", u)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -129,7 +135,13 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
} 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.objects.iter_mut().enumerate() {
|
for (i, obj) in project_config
|
||||||
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
resolve_paths(obj);
|
resolve_paths(obj);
|
||||||
|
|
||||||
if obj
|
if obj
|
||||||
@@ -148,7 +160,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.objects[i],
|
(1, Some(i)) => &mut project_config.units_mut()[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
|
||||||
@@ -303,7 +315,7 @@ fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
struct FunctionDiffUi {
|
struct FunctionDiffUi {
|
||||||
relax_reloc_diffs: bool,
|
relax_reloc_diffs: bool,
|
||||||
left_highlight: HighlightKind,
|
left_highlight: HighlightKind,
|
||||||
@@ -758,7 +770,7 @@ 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)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn print_sym(
|
fn print_sym(
|
||||||
&self,
|
&self,
|
||||||
out: &mut Text<'static>,
|
out: &mut Text<'static>,
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
};
|
};
|
||||||
info!(
|
info!(
|
||||||
"Generating report for {} units (using {} threads)",
|
"Generating report for {} units (using {} threads)",
|
||||||
project.objects.len(),
|
project.units().len(),
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
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 &mut project.objects {
|
for object in project.units.as_deref_mut().unwrap_or_default() {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(
|
||||||
object,
|
object,
|
||||||
project_dir,
|
project_dir,
|
||||||
@@ -116,7 +116,9 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = project
|
||||||
.objects
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.map(|object| {
|
.map(|object| {
|
||||||
report_object(
|
report_object(
|
||||||
@@ -132,7 +134,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
||||||
let mut categories = Vec::new();
|
let mut categories = Vec::new();
|
||||||
for category in &project.progress_categories {
|
for category in project.progress_categories() {
|
||||||
categories.push(ReportCategory {
|
categories.push(ReportCategory {
|
||||||
id: category.id.clone(),
|
id: category.id.clone(),
|
||||||
name: category.name.clone(),
|
name: category.name.clone(),
|
||||||
|
|||||||
@@ -17,35 +17,36 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
|
||||||
any-arch = [] # Implicit, used to check if any arch is enabled
|
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
|
||||||
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["dep:gimli"]
|
||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "dep:rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
|
||||||
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
|
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
|
||||||
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
|
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
|
||||||
bindings = ["serde_json", "prost", "pbjson"]
|
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
|
||||||
wasm = ["bindings", "console_error_panic_hook", "console_log"]
|
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
byteorder = "1.5"
|
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||||
filetime = "0.2"
|
byteorder = { version = "1.5", optional = true }
|
||||||
flagset = "0.4"
|
filetime = { version = "0.2", optional = true }
|
||||||
log = "0.4"
|
flagset = { version = "0.4", optional = true }
|
||||||
memmap2 = "0.9"
|
log = { version = "0.4", optional = true }
|
||||||
num-traits = "0.2"
|
memmap2 = { version = "0.9", optional = true }
|
||||||
|
num-traits = { version = "0.2", optional = true }
|
||||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
||||||
pbjson = { version = "0.7", optional = true }
|
pbjson = { version = "0.7", optional = true }
|
||||||
prost = { version = "0.13", optional = true }
|
prost = { version = "0.13", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
similar = { version = "2.6", default-features = false }
|
similar = { version = "2.6", default-features = false, optional = true }
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"], optional = true }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = { version = "0.2", optional = true }
|
||||||
tsify-next = { version = "0.5", default-features = false, features = ["js"] }
|
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
||||||
console_log = { version = "1.0", optional = true }
|
console_log = { version = "1.0", optional = true }
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
console_error_panic_hook = { version = "0.1", optional = true }
|
||||||
|
|
||||||
@@ -76,5 +77,5 @@ unarm = { version = "1.6", optional = true }
|
|||||||
arm-attr = { version = "0.1", optional = true }
|
arm-attr = { version = "0.1", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.13"
|
prost-build = { version = "0.13", optional = true }
|
||||||
pbjson-build = "0.7"
|
pbjson-build = { version = "0.7", optional = true }
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(feature = "bindings")]
|
||||||
|
compile_protos();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bindings")]
|
||||||
|
fn compile_protos() {
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
|
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
|
||||||
let descriptor_path = root.join("proto_descriptor.bin");
|
let descriptor_path = root.join("proto_descriptor.bin");
|
||||||
println!("cargo:rerun-if-changed={}", descriptor_path.display());
|
println!("cargo:rerun-if-changed={}", descriptor_path.display());
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ impl ObjArch for ObjArchMips {
|
|||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
_section_index: usize,
|
section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
relocations: &[ObjReloc],
|
||||||
line_info: &BTreeMap<u64, u32>,
|
line_info: &BTreeMap<u64, u32>,
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
@@ -140,11 +140,18 @@ impl ObjArch for ObjArchMips {
|
|||||||
| OperandType::cpu_label
|
| OperandType::cpu_label
|
||||||
| OperandType::cpu_branch_target_label => {
|
| OperandType::cpu_branch_target_label => {
|
||||||
if let Some(reloc) = reloc {
|
if let Some(reloc) = reloc {
|
||||||
if matches!(&reloc.target_section, Some(s) if s == ".text")
|
// If the relocation target is within the current function, we can
|
||||||
&& reloc.target.address > start_address
|
// convert it into a relative branch target. Note that we check
|
||||||
&& reloc.target.address < end_address
|
// 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)
|
||||||
{
|
{
|
||||||
args.push(ObjInsArg::BranchDest(reloc.target.address));
|
let target_address = target_address.unwrap();
|
||||||
|
args.push(ObjInsArg::BranchDest(target_address));
|
||||||
|
branch_dest = Some(target_address);
|
||||||
} else {
|
} else {
|
||||||
push_reloc(&mut args, reloc)?;
|
push_reloc(&mut args, reloc)?;
|
||||||
branch_dest = None;
|
branch_dest = None;
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ pub enum DataType {
|
|||||||
|
|
||||||
impl DataType {
|
impl DataType {
|
||||||
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
||||||
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
|
||||||
|
// fallback to intrepreting it as bytes.
|
||||||
|
// https://github.com/encounter/objdiff/issues/124
|
||||||
|
if self.required_len().is_some_and(|l| bytes.len() != l) {
|
||||||
|
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,13 @@ impl FunctionDiff {
|
|||||||
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
||||||
// Symbol::from(symbol)
|
// Symbol::from(symbol)
|
||||||
// });
|
// });
|
||||||
let instructions = symbol_diff.instructions.iter().map(InstructionDiff::from).collect();
|
let instructions = symbol_diff
|
||||||
|
.instructions
|
||||||
|
.iter()
|
||||||
|
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
|
||||||
|
.collect();
|
||||||
Self {
|
Self {
|
||||||
symbol: Some(Symbol::from(symbol)),
|
symbol: Some(Symbol::new(symbol)),
|
||||||
// diff_symbol,
|
// diff_symbol,
|
||||||
instructions,
|
instructions,
|
||||||
match_percent: symbol_diff.match_percent,
|
match_percent: symbol_diff.match_percent,
|
||||||
@@ -90,8 +94,8 @@ impl DataDiff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjSymbol> for Symbol {
|
impl Symbol {
|
||||||
fn from(value: &'a ObjSymbol) -> Self {
|
pub fn new(value: &ObjSymbol) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: value.name.to_string(),
|
name: value.name.to_string(),
|
||||||
demangled_name: value.demangled_name.clone(),
|
demangled_name: value.demangled_name.clone(),
|
||||||
@@ -122,29 +126,29 @@ fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
|||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjIns> for Instruction {
|
impl Instruction {
|
||||||
fn from(value: &'a ObjIns) -> Self {
|
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
|
||||||
Self {
|
Self {
|
||||||
address: value.address,
|
address: instruction.address,
|
||||||
size: value.size as u32,
|
size: instruction.size as u32,
|
||||||
opcode: value.op as u32,
|
opcode: instruction.op as u32,
|
||||||
mnemonic: value.mnemonic.clone(),
|
mnemonic: instruction.mnemonic.clone(),
|
||||||
formatted: value.formatted.clone(),
|
formatted: instruction.formatted.clone(),
|
||||||
arguments: value.args.iter().map(Argument::from).collect(),
|
arguments: instruction.args.iter().map(Argument::new).collect(),
|
||||||
relocation: value.reloc.as_ref().map(Relocation::from),
|
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
||||||
branch_dest: value.branch_dest,
|
branch_dest: instruction.branch_dest,
|
||||||
line_number: value.line,
|
line_number: instruction.line,
|
||||||
original: value.orig.clone(),
|
original: instruction.orig.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsArg> for Argument {
|
impl Argument {
|
||||||
fn from(value: &'a ObjInsArg) -> Self {
|
pub fn new(value: &ObjInsArg) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: Some(match value {
|
value: Some(match value {
|
||||||
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
||||||
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::from(v)),
|
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
|
||||||
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
||||||
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
||||||
}),
|
}),
|
||||||
@@ -152,8 +156,8 @@ impl<'a> From<&'a ObjInsArg> for Argument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ObjInsArgValue> for ArgumentValue {
|
impl ArgumentValue {
|
||||||
fn from(value: &ObjInsArgValue) -> Self {
|
pub fn new(value: &ObjInsArgValue) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: Some(match value {
|
value: Some(match value {
|
||||||
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
||||||
@@ -164,42 +168,39 @@ impl From<&ObjInsArgValue> for ArgumentValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjReloc> for Relocation {
|
impl Relocation {
|
||||||
fn from(value: &ObjReloc) -> Self {
|
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
|
||||||
Self {
|
Self {
|
||||||
r#type: match value.flags {
|
r#type: match reloc.flags {
|
||||||
object::RelocationFlags::Elf { r_type } => r_type,
|
object::RelocationFlags::Elf { r_type } => r_type,
|
||||||
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
||||||
object::RelocationFlags::Coff { typ } => typ as u32,
|
object::RelocationFlags::Coff { typ } => typ as u32,
|
||||||
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
|
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
type_name: String::new(), // TODO
|
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
|
||||||
target: Some(RelocationTarget::from(&value.target)),
|
target: Some(RelocationTarget {
|
||||||
|
symbol: Some(Symbol::new(&reloc.target)),
|
||||||
|
addend: reloc.addend,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjSymbol> for RelocationTarget {
|
impl InstructionDiff {
|
||||||
fn from(value: &'a ObjSymbol) -> Self {
|
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
|
||||||
Self { symbol: Some(Symbol::from(value)), addend: value.addend }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsDiff> for InstructionDiff {
|
|
||||||
fn from(value: &'a ObjInsDiff) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
instruction: value.ins.as_ref().map(Instruction::from),
|
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
|
||||||
diff_kind: DiffKind::from(value.kind) as i32,
|
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
|
||||||
branch_from: value.branch_from.as_ref().map(InstructionBranchFrom::from),
|
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
|
||||||
branch_to: value.branch_to.as_ref().map(InstructionBranchTo::from),
|
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
|
||||||
arg_diff: value.arg_diff.iter().map(ArgumentDiff::from).collect(),
|
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Option<ObjInsArgDiff>> for ArgumentDiff {
|
impl ArgumentDiff {
|
||||||
fn from(value: &Option<ObjInsArgDiff>) -> Self {
|
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
|
||||||
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,8 +229,8 @@ impl From<ObjDataDiffKind> for DiffKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
|
impl InstructionBranchFrom {
|
||||||
fn from(value: &'a ObjInsBranchFrom) -> Self {
|
pub fn new(value: &ObjInsBranchFrom) -> Self {
|
||||||
Self {
|
Self {
|
||||||
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
||||||
branch_index: value.branch_idx as u32,
|
branch_index: value.branch_idx as u32,
|
||||||
@@ -237,8 +238,8 @@ impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a ObjInsBranchTo> for InstructionBranchTo {
|
impl InstructionBranchTo {
|
||||||
fn from(value: &'a ObjInsBranchTo) -> Self {
|
pub fn new(value: &ObjInsBranchTo) -> Self {
|
||||||
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,100 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufReader, Read},
|
io::{BufReader, BufWriter, Read},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use bimap::BiBTreeMap;
|
||||||
use filetime::FileTime;
|
use filetime::FileTime;
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
#[inline]
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
fn bool_true() -> bool { true }
|
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub min_version: Option<String>,
|
pub min_version: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub target_dir: Option<PathBuf>,
|
pub target_dir: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_dir: Option<PathBuf>,
|
pub base_dir: Option<PathBuf>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_base: bool,
|
pub build_base: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_target: bool,
|
pub build_target: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub watch_patterns: Option<Vec<Glob>>,
|
pub watch_patterns: Option<Vec<Glob>>,
|
||||||
#[serde(default, alias = "units")]
|
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
||||||
pub objects: Vec<ProjectObject>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub progress_categories: Vec<ProjectProgressCategory>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
impl ProjectConfig {
|
||||||
|
#[inline]
|
||||||
|
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
|
||||||
|
self.units.get_or_insert_with(Vec::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
||||||
|
self.progress_categories.as_deref().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
|
||||||
|
self.progress_categories.get_or_insert_with(Vec::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, 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)]
|
#[serde(default, 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)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub symbol_mappings: Option<SymbolMappings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
pub type SymbolMappings = BiBTreeMap<String, String>;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectObjectMetadata {
|
pub struct ProjectObjectMetadata {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub source_path: Option<String>,
|
pub source_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub progress_categories: Option<Vec<String>>,
|
pub progress_categories: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub auto_generated: Option<bool>,
|
pub auto_generated: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Deserialize)]
|
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ProjectProgressCategory {
|
pub struct ProjectProgressCategory {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -112,12 +135,12 @@ impl ProjectObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn complete(&self) -> Option<bool> {
|
pub fn complete(&self) -> Option<bool> {
|
||||||
#[allow(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reverse_fn_order(&self) -> Option<bool> {
|
pub fn reverse_fn_order(&self) -> Option<bool> {
|
||||||
#[allow(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
|
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,16 +155,16 @@ impl ProjectObject {
|
|||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ScratchConfig {
|
pub struct ScratchConfig {
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub compiler: Option<String>,
|
pub compiler: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<PathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub build_ctx: bool,
|
pub build_ctx: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
||||||
@@ -154,7 +177,7 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
|||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ProjectConfigInfo {
|
pub struct ProjectConfigInfo {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub timestamp: FileTime,
|
pub timestamp: Option<FileTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||||
@@ -180,12 +203,41 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
result = Err(e);
|
result = Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Some((result, ProjectConfigInfo { path: config_path, timestamp: ts }));
|
return Some((result, ProjectConfigInfo { path: config_path, timestamp: Some(ts) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_project_config(
|
||||||
|
config: &ProjectConfig,
|
||||||
|
info: &ProjectConfigInfo,
|
||||||
|
) -> Result<ProjectConfigInfo> {
|
||||||
|
if let Some(last_ts) = info.timestamp {
|
||||||
|
// Check if the file has changed since we last read it
|
||||||
|
if let Ok(metadata) = fs::metadata(&info.path) {
|
||||||
|
let ts = FileTime::from_last_modification_time(&metadata);
|
||||||
|
if ts != last_ts {
|
||||||
|
return Err(anyhow!("Config file has changed since last read"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut writer =
|
||||||
|
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
|
||||||
|
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
||||||
|
match ext {
|
||||||
|
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
||||||
|
"yml" | "yaml" => {
|
||||||
|
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
|
||||||
|
}
|
||||||
|
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
||||||
|
}?;
|
||||||
|
let file = writer.into_inner().context("Failed to flush file")?;
|
||||||
|
let metadata = file.metadata().context("Failed to get file metadata")?;
|
||||||
|
let ts = FileTime::from_last_modification_time(&metadata);
|
||||||
|
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"))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
||||||
ObjSymbolDiff,
|
ObjSymbolDiff,
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn process_code_symbol(
|
pub fn process_code_symbol(
|
||||||
@@ -41,10 +41,12 @@ pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<Ob
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
resolve_branches(&mut diff);
|
resolve_branches(&mut diff);
|
||||||
Ok(ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: diff, match_percent: None })
|
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left_out: &ProcessCodeResult,
|
left_out: &ProcessCodeResult,
|
||||||
right_out: &ProcessCodeResult,
|
right_out: &ProcessCodeResult,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_ref: SymbolRef,
|
||||||
@@ -60,14 +62,14 @@ pub fn diff_code(
|
|||||||
|
|
||||||
let mut diff_state = InsDiffState::default();
|
let mut diff_state = InsDiffState::default();
|
||||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
||||||
let result = compare_ins(config, left, right, &mut diff_state)?;
|
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
|
||||||
left.kind = result.kind;
|
left.kind = result.kind;
|
||||||
right.kind = result.kind;
|
right.kind = result.kind;
|
||||||
left.arg_diff = result.left_args_diff;
|
left.arg_diff = result.left_args_diff;
|
||||||
right.arg_diff = result.right_args_diff;
|
right.arg_diff = result.right_args_diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total = left_out.insts.len();
|
let total = left_out.insts.len().max(right_out.insts.len());
|
||||||
let percent = if diff_state.diff_count >= total {
|
let percent = if diff_state.diff_count >= total {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
@@ -77,13 +79,13 @@ pub fn diff_code(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: left_diff,
|
instructions: left_diff,
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: right_diff,
|
instructions: right_diff,
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
@@ -170,12 +172,33 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
|
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
||||||
left.address as i64 + left.addend == right.address as i64 + right.addend
|
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_name_eq(
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
|
left_orig_section_index: usize,
|
||||||
|
right_orig_section_index: usize,
|
||||||
|
) -> bool {
|
||||||
|
let Some(left_section) =
|
||||||
|
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(right_section) =
|
||||||
|
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
left_section.name == right_section.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reloc_eq(
|
fn reloc_eq(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left_reloc: Option<&ObjReloc>,
|
left_reloc: Option<&ObjReloc>,
|
||||||
right_reloc: Option<&ObjReloc>,
|
right_reloc: Option<&ObjReloc>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -189,29 +212,32 @@ fn reloc_eq(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name_matches = left.target.name == right.target.name;
|
let symbol_name_matches = left.target.name == right.target.name;
|
||||||
match (&left.target_section, &right.target_section) {
|
match (&left.target.orig_section_index, &right.target.orig_section_index) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
// Match if section and name or address match
|
// Match if section and name or address match
|
||||||
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||||
|
&& (symbol_name_matches || address_eq(left, right))
|
||||||
}
|
}
|
||||||
(Some(_), None) => false,
|
(Some(_), None) => false,
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
// Match if possibly stripped weak symbol
|
// Match if possibly stripped weak symbol
|
||||||
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||||
}
|
}
|
||||||
(None, None) => name_matches,
|
(None, None) => symbol_name_matches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_eq(
|
fn arg_eq(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left: &ObjInsArg,
|
left: &ObjInsArg,
|
||||||
right: &ObjInsArg,
|
right: &ObjInsArg,
|
||||||
left_diff: &ObjInsDiff,
|
left_diff: &ObjInsDiff,
|
||||||
right_diff: &ObjInsDiff,
|
right_diff: &ObjInsDiff,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
return match left {
|
match left {
|
||||||
ObjInsArg::PlainText(l) => match right {
|
ObjInsArg::PlainText(l) => match right {
|
||||||
ObjInsArg::PlainText(r) => l == r,
|
ObjInsArg::PlainText(r) => l == r,
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -227,6 +253,8 @@ fn arg_eq(
|
|||||||
matches!(right, ObjInsArg::Reloc)
|
matches!(right, ObjInsArg::Reloc)
|
||||||
&& reloc_eq(
|
&& reloc_eq(
|
||||||
config,
|
config,
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
)
|
)
|
||||||
@@ -236,7 +264,7 @@ fn arg_eq(
|
|||||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -257,6 +285,8 @@ struct InsDiffResult {
|
|||||||
|
|
||||||
fn compare_ins(
|
fn compare_ins(
|
||||||
config: &DiffObjConfig,
|
config: &DiffObjConfig,
|
||||||
|
left_obj: &ObjInfo,
|
||||||
|
right_obj: &ObjInfo,
|
||||||
left: &ObjInsDiff,
|
left: &ObjInsDiff,
|
||||||
right: &ObjInsDiff,
|
right: &ObjInsDiff,
|
||||||
state: &mut InsDiffState,
|
state: &mut InsDiffState,
|
||||||
@@ -283,7 +313,7 @@ fn compare_ins(
|
|||||||
state.diff_count += 1;
|
state.diff_count += 1;
|
||||||
}
|
}
|
||||||
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
||||||
if arg_eq(config, a, b, left, right) {
|
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
||||||
result.left_args_diff.push(None);
|
result.left_args_diff.push(None);
|
||||||
result.right_args_diff.push(None);
|
result.right_args_diff.push(None);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ pub fn diff_bss_symbol(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ pub fn diff_bss_symbol(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
||||||
ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: vec![], match_percent: None }
|
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the data sections of two object files.
|
/// Compare the data sections of two object files.
|
||||||
@@ -158,13 +158,13 @@ pub fn diff_data_symbol(
|
|||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
symbol_ref: left_symbol_ref,
|
||||||
diff_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
ObjSymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
symbol_ref: right_symbol_ref,
|
||||||
diff_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub enum DiffText<'a> {
|
|||||||
Eol,
|
Eol,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub enum HighlightKind {
|
pub enum HighlightKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
@@ -94,9 +94,9 @@ fn display_reloc_name<E>(
|
|||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
||||||
) -> Result<(), E> {
|
) -> Result<(), E> {
|
||||||
cb(DiffText::Symbol(&reloc.target))?;
|
cb(DiffText::Symbol(&reloc.target))?;
|
||||||
match reloc.target.addend.cmp(&0i64) {
|
match reloc.addend.cmp(&0i64) {
|
||||||
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.target.addend))),
|
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
||||||
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.target.addend))),
|
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::collections::HashSet;
|
|||||||
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, process_code_symbol},
|
||||||
data::{
|
data::{
|
||||||
@@ -10,7 +11,7 @@ use crate::{
|
|||||||
diff_generic_section, no_diff_symbol,
|
diff_generic_section, no_diff_symbol,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
|
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod code;
|
pub mod code;
|
||||||
@@ -28,8 +29,8 @@ pub mod display;
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum X86Formatter {
|
pub enum X86Formatter {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Intel (default)")]
|
#[strum(message = "Intel (default)")]
|
||||||
@@ -53,8 +54,8 @@ pub enum X86Formatter {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum MipsAbi {
|
pub enum MipsAbi {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -78,8 +79,8 @@ pub enum MipsAbi {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum MipsInstrCategory {
|
pub enum MipsInstrCategory {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -107,8 +108,8 @@ pub enum MipsInstrCategory {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum ArmArchVersion {
|
pub enum ArmArchVersion {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(message = "Auto (default)")]
|
#[strum(message = "Auto (default)")]
|
||||||
@@ -132,8 +133,8 @@ pub enum ArmArchVersion {
|
|||||||
serde::Serialize,
|
serde::Serialize,
|
||||||
strum::VariantArray,
|
strum::VariantArray,
|
||||||
strum::EnumMessage,
|
strum::EnumMessage,
|
||||||
tsify_next::Tsify,
|
|
||||||
)]
|
)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
pub enum ArmR9Usage {
|
pub enum ArmR9Usage {
|
||||||
#[default]
|
#[default]
|
||||||
#[strum(
|
#[strum(
|
||||||
@@ -153,14 +154,17 @@ pub enum ArmR9Usage {
|
|||||||
#[inline]
|
#[inline]
|
||||||
const fn default_true() -> bool { true }
|
const fn default_true() -> bool { true }
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, tsify_next::Tsify)]
|
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
#[tsify(from_wasm_abi)]
|
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||||
|
#[cfg_attr(feature = "wasm", tsify(from_wasm_abi))]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct DiffObjConfig {
|
pub struct DiffObjConfig {
|
||||||
pub relax_reloc_diffs: bool,
|
pub relax_reloc_diffs: bool,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub space_between_args: bool,
|
pub space_between_args: bool,
|
||||||
pub combine_data_sections: bool,
|
pub combine_data_sections: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: MappingConfig,
|
||||||
// x86
|
// x86
|
||||||
pub x86_formatter: X86Formatter,
|
pub x86_formatter: X86Formatter,
|
||||||
// MIPS
|
// MIPS
|
||||||
@@ -182,6 +186,7 @@ impl Default for DiffObjConfig {
|
|||||||
relax_reloc_diffs: false,
|
relax_reloc_diffs: false,
|
||||||
space_between_args: true,
|
space_between_args: true,
|
||||||
combine_data_sections: false,
|
combine_data_sections: false,
|
||||||
|
symbol_mappings: Default::default(),
|
||||||
x86_formatter: Default::default(),
|
x86_formatter: Default::default(),
|
||||||
mips_abi: Default::default(),
|
mips_abi: Default::default(),
|
||||||
mips_instr_category: Default::default(),
|
mips_instr_category: Default::default(),
|
||||||
@@ -223,8 +228,10 @@ impl ObjSectionDiff {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ObjSymbolDiff {
|
pub struct ObjSymbolDiff {
|
||||||
|
/// The symbol ref this object
|
||||||
pub symbol_ref: SymbolRef,
|
pub symbol_ref: SymbolRef,
|
||||||
pub diff_symbol: Option<SymbolRef>,
|
/// The symbol ref in the _other_ object that this symbol was diffed against
|
||||||
|
pub target_symbol: Option<SymbolRef>,
|
||||||
pub instructions: Vec<ObjInsDiff>,
|
pub instructions: Vec<ObjInsDiff>,
|
||||||
pub match_percent: Option<f32>,
|
pub match_percent: Option<f32>,
|
||||||
}
|
}
|
||||||
@@ -294,8 +301,13 @@ pub struct ObjInsBranchTo {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ObjDiff {
|
pub struct ObjDiff {
|
||||||
|
/// A list of all section diffs in the object.
|
||||||
pub sections: Vec<ObjSectionDiff>,
|
pub sections: Vec<ObjSectionDiff>,
|
||||||
|
/// Common BSS symbols don't live in a section, so they're stored separately.
|
||||||
pub common: Vec<ObjSymbolDiff>,
|
pub common: Vec<ObjSymbolDiff>,
|
||||||
|
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols
|
||||||
|
/// that are being mapped to the other object.
|
||||||
|
pub mapping_symbols: Vec<ObjSymbolDiff>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjDiff {
|
impl ObjDiff {
|
||||||
@@ -303,13 +315,14 @@ impl ObjDiff {
|
|||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
sections: Vec::with_capacity(obj.sections.len()),
|
sections: Vec::with_capacity(obj.sections.len()),
|
||||||
common: Vec::with_capacity(obj.common.len()),
|
common: Vec::with_capacity(obj.common.len()),
|
||||||
|
mapping_symbols: vec![],
|
||||||
};
|
};
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
let mut symbols = Vec::with_capacity(section.symbols.len());
|
let mut symbols = Vec::with_capacity(section.symbols.len());
|
||||||
for (symbol_idx, _) in section.symbols.iter().enumerate() {
|
for (symbol_idx, _) in section.symbols.iter().enumerate() {
|
||||||
symbols.push(ObjSymbolDiff {
|
symbols.push(ObjSymbolDiff {
|
||||||
symbol_ref: SymbolRef { section_idx, symbol_idx },
|
symbol_ref: SymbolRef { section_idx, symbol_idx },
|
||||||
diff_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
});
|
});
|
||||||
@@ -327,8 +340,8 @@ impl ObjDiff {
|
|||||||
}
|
}
|
||||||
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
for (symbol_idx, _) in obj.common.iter().enumerate() {
|
||||||
result.common.push(ObjSymbolDiff {
|
result.common.push(ObjSymbolDiff {
|
||||||
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
|
symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx },
|
||||||
diff_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
});
|
});
|
||||||
@@ -348,7 +361,7 @@ impl ObjDiff {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
|
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
&self.common[symbol_ref.symbol_idx]
|
&self.common[symbol_ref.symbol_idx]
|
||||||
} else {
|
} else {
|
||||||
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
||||||
@@ -357,7 +370,7 @@ impl ObjDiff {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
|
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
&mut self.common[symbol_ref.symbol_idx]
|
&mut self.common[symbol_ref.symbol_idx]
|
||||||
} else {
|
} else {
|
||||||
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
|
||||||
@@ -378,7 +391,7 @@ pub fn diff_objs(
|
|||||||
right: Option<&ObjInfo>,
|
right: Option<&ObjInfo>,
|
||||||
prev: Option<&ObjInfo>,
|
prev: Option<&ObjInfo>,
|
||||||
) -> Result<DiffObjsResult> {
|
) -> Result<DiffObjsResult> {
|
||||||
let symbol_matches = matching_symbols(left, right, prev)?;
|
let symbol_matches = matching_symbols(left, right, prev, &config.symbol_mappings)?;
|
||||||
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, ObjDiff::new_from_obj(p)));
|
||||||
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
||||||
@@ -399,6 +412,8 @@ pub fn diff_objs(
|
|||||||
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
||||||
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
||||||
let (left_diff, right_diff) = diff_code(
|
let (left_diff, right_diff) = diff_code(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
&left_code,
|
&left_code,
|
||||||
&right_code,
|
&right_code,
|
||||||
left_symbol_ref,
|
left_symbol_ref,
|
||||||
@@ -412,6 +427,8 @@ pub fn diff_objs(
|
|||||||
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, config)?;
|
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
|
||||||
let (_, prev_diff) = diff_code(
|
let (_, prev_diff) = diff_code(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
&right_code,
|
&right_code,
|
||||||
&prev_code,
|
&prev_code,
|
||||||
right_symbol_ref,
|
right_symbol_ref,
|
||||||
@@ -529,6 +546,17 @@ pub fn diff_objs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
|
||||||
|
(right.as_mut(), left.as_mut())
|
||||||
|
{
|
||||||
|
if let Some(right_name) = &config.symbol_mappings.selecting_left {
|
||||||
|
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, config)?;
|
||||||
|
}
|
||||||
|
if let Some(left_name) = &config.symbol_mappings.selecting_right {
|
||||||
|
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, config)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(DiffObjsResult {
|
Ok(DiffObjsResult {
|
||||||
left: left.map(|(_, o)| o),
|
left: left.map(|(_, o)| o),
|
||||||
right: right.map(|(_, o)| o),
|
right: right.map(|(_, o)| o),
|
||||||
@@ -536,6 +564,65 @@ pub fn diff_objs(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
|
||||||
|
/// 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.
|
||||||
|
fn generate_mapping_symbols(
|
||||||
|
base_obj: &ObjInfo,
|
||||||
|
base_name: &str,
|
||||||
|
target_obj: &ObjInfo,
|
||||||
|
target_out: &mut ObjDiff,
|
||||||
|
config: &DiffObjConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let (base_section, base_symbol) = base_obj.section_symbol(base_symbol_ref);
|
||||||
|
let Some(base_section) = base_section else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
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().filter(|(_, s)| s.kind == base_symbol.kind)
|
||||||
|
{
|
||||||
|
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 => {
|
||||||
|
let (left_diff, _right_diff) =
|
||||||
|
diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
||||||
|
target_out.mapping_symbols.push(left_diff);
|
||||||
|
}
|
||||||
|
ObjSectionKind::Bss => {
|
||||||
|
let (left_diff, _right_diff) =
|
||||||
|
diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
|
||||||
|
target_out.mapping_symbols.push(left_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
struct SymbolMatch {
|
struct SymbolMatch {
|
||||||
left: Option<SymbolRef>,
|
left: Option<SymbolRef>,
|
||||||
@@ -551,19 +638,115 @@ struct SectionMatch {
|
|||||||
section_kind: ObjSectionKind,
|
section_kind: ObjSectionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct MappingConfig {
|
||||||
|
/// Manual symbol mappings
|
||||||
|
pub mappings: SymbolMappings,
|
||||||
|
/// The right object symbol name that we're selecting a left symbol for
|
||||||
|
pub selecting_left: Option<String>,
|
||||||
|
/// The left object symbol name that we're selecting a right symbol for
|
||||||
|
pub selecting_right: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
||||||
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
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(
|
||||||
|
left: &ObjInfo,
|
||||||
|
right: &ObjInfo,
|
||||||
|
mapping_config: &MappingConfig,
|
||||||
|
left_used: &mut HashSet<SymbolRef>,
|
||||||
|
right_used: &mut HashSet<SymbolRef>,
|
||||||
|
matches: &mut Vec<SymbolMatch>,
|
||||||
|
) -> Result<()> {
|
||||||
|
// If we're selecting a symbol to use as a comparison, mark it as used
|
||||||
|
// This ensures that we don't match it to another symbol at any point
|
||||||
|
if let Some(left_name) = &mapping_config.selecting_left {
|
||||||
|
if let Some(left_symbol) = symbol_ref_by_name(left, left_name) {
|
||||||
|
left_used.insert(left_symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(right_name) = &mapping_config.selecting_right {
|
||||||
|
if let Some(right_symbol) = symbol_ref_by_name(right, right_name) {
|
||||||
|
right_used.insert(right_symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply manual symbol mappings
|
||||||
|
for (left_name, right_name) in &mapping_config.mappings {
|
||||||
|
let Some(left_symbol) = symbol_ref_by_name(left, left_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if left_used.contains(&left_symbol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(right_symbol) = symbol_ref_by_name(right, right_name) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if right_used.contains(&right_symbol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let left_section = &left.sections[left_symbol.section_idx];
|
||||||
|
let right_section = &right.sections[right_symbol.section_idx];
|
||||||
|
if left_section.kind != right_section.kind {
|
||||||
|
log::warn!(
|
||||||
|
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
|
||||||
|
left_name,
|
||||||
|
left_section.kind,
|
||||||
|
right_name,
|
||||||
|
right_section.kind
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matches.push(SymbolMatch {
|
||||||
|
left: Some(left_symbol),
|
||||||
|
right: Some(right_symbol),
|
||||||
|
prev: None, // TODO
|
||||||
|
section_kind: left_section.kind,
|
||||||
|
});
|
||||||
|
left_used.insert(left_symbol);
|
||||||
|
right_used.insert(right_symbol);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Find matching symbols between each object.
|
/// Find matching symbols between each object.
|
||||||
fn matching_symbols(
|
fn matching_symbols(
|
||||||
left: Option<&ObjInfo>,
|
left: Option<&ObjInfo>,
|
||||||
right: Option<&ObjInfo>,
|
right: Option<&ObjInfo>,
|
||||||
prev: Option<&ObjInfo>,
|
prev: Option<&ObjInfo>,
|
||||||
|
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 right_used = HashSet::new();
|
let mut right_used = HashSet::new();
|
||||||
if let Some(left) = left {
|
if let Some(left) = left {
|
||||||
|
if let Some(right) = right {
|
||||||
|
apply_symbol_mappings(
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
mappings,
|
||||||
|
&mut left_used,
|
||||||
|
&mut right_used,
|
||||||
|
&mut matches,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
for (section_idx, section) in left.sections.iter().enumerate() {
|
for (section_idx, section) in left.sections.iter().enumerate() {
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||||
|
let symbol_ref = SymbolRef { section_idx, symbol_idx };
|
||||||
|
if left_used.contains(&symbol_ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let symbol_match = SymbolMatch {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(SymbolRef { section_idx, symbol_idx }),
|
left: Some(symbol_ref),
|
||||||
right: find_symbol(right, symbol, section, Some(&right_used)),
|
right: find_symbol(right, symbol, section, Some(&right_used)),
|
||||||
prev: find_symbol(prev, symbol, section, None),
|
prev: find_symbol(prev, symbol, section, None),
|
||||||
section_kind: section.kind,
|
section_kind: section.kind,
|
||||||
@@ -575,8 +758,12 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in left.common.iter().enumerate() {
|
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 {
|
let symbol_match = SymbolMatch {
|
||||||
left: Some(SymbolRef { section_idx: left.sections.len(), symbol_idx }),
|
left: Some(symbol_ref),
|
||||||
right: find_common_symbol(right, symbol),
|
right: find_common_symbol(right, symbol),
|
||||||
prev: find_common_symbol(prev, symbol),
|
prev: find_common_symbol(prev, symbol),
|
||||||
section_kind: ObjSectionKind::Bss,
|
section_kind: ObjSectionKind::Bss,
|
||||||
@@ -603,7 +790,7 @@ fn matching_symbols(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (symbol_idx, symbol) in right.common.iter().enumerate() {
|
for (symbol_idx, symbol) in right.common.iter().enumerate() {
|
||||||
let symbol_ref = SymbolRef { section_idx: right.sections.len(), symbol_idx };
|
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
|
||||||
if right_used.contains(&symbol_ref) {
|
if right_used.contains(&symbol_ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -696,7 +883,7 @@ fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option<Sy
|
|||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
|
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
|
||||||
if symbol.name == in_symbol.name {
|
if symbol.name == in_symbol.name {
|
||||||
return Some(SymbolRef { section_idx: obj.sections.len(), symbol_idx });
|
return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ pub mod config;
|
|||||||
pub mod diff;
|
pub mod diff;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod obj;
|
pub mod obj;
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
@@ -112,6 +112,15 @@ pub struct ObjIns {
|
|||||||
pub orig: Option<String>,
|
pub orig: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
|
pub enum ObjSymbolKind {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Function,
|
||||||
|
Object,
|
||||||
|
Section,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ObjSymbol {
|
pub struct ObjSymbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -120,8 +129,9 @@ pub struct ObjSymbol {
|
|||||||
pub section_address: u64,
|
pub section_address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub size_known: bool,
|
pub size_known: bool,
|
||||||
|
pub kind: ObjSymbolKind,
|
||||||
pub flags: ObjSymbolFlagSet,
|
pub flags: ObjSymbolFlagSet,
|
||||||
pub addend: i64,
|
pub orig_section_index: Option<usize>,
|
||||||
/// 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
|
/// Original index in object symbol table
|
||||||
@@ -145,7 +155,7 @@ pub struct ObjReloc {
|
|||||||
pub flags: RelocationFlags,
|
pub flags: RelocationFlags,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub target: ObjSymbol,
|
pub target: ObjSymbol,
|
||||||
pub target_section: Option<String>,
|
pub addend: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
@@ -154,9 +164,11 @@ pub struct SymbolRef {
|
|||||||
pub symbol_idx: usize,
|
pub symbol_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SECTION_COMMON: usize = usize::MAX - 1;
|
||||||
|
|
||||||
impl ObjInfo {
|
impl ObjInfo {
|
||||||
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
||||||
if symbol_ref.section_idx == self.sections.len() {
|
if symbol_ref.section_idx == SECTION_COMMON {
|
||||||
let symbol = &self.common[symbol_ref.symbol_idx];
|
let symbol = &self.common[symbol_ref.symbol_idx];
|
||||||
return (None, symbol);
|
return (None, symbol);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use object::{
|
|||||||
endian::LittleEndian as LE,
|
endian::LittleEndian as LE,
|
||||||
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
|
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
|
||||||
read::coff::{CoffFile, CoffHeader, ImageSymbol},
|
read::coff::{CoffFile, CoffHeader, ImageSymbol},
|
||||||
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex,
|
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, Section,
|
||||||
SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
|
SectionIndex, SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -23,6 +23,7 @@ use crate::{
|
|||||||
obj::{
|
obj::{
|
||||||
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
split_meta::{SplitMeta, SPLITMETA_SECTION},
|
||||||
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
ObjInfo, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
|
||||||
|
ObjSymbolKind,
|
||||||
},
|
},
|
||||||
util::{read_u16, read_u32},
|
util::{read_u16, read_u32},
|
||||||
};
|
};
|
||||||
@@ -40,7 +41,6 @@ fn to_obj_symbol(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
symbol: &Symbol<'_, '_>,
|
symbol: &Symbol<'_, '_>,
|
||||||
addend: i64,
|
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<ObjSymbol> {
|
) -> Result<ObjSymbol> {
|
||||||
let mut name = symbol.name().context("Failed to process symbol name")?;
|
let mut name = symbol.name().context("Failed to process symbol name")?;
|
||||||
@@ -64,6 +64,7 @@ fn to_obj_symbol(
|
|||||||
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
|
||||||
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
if arch
|
if arch
|
||||||
.ppc()
|
.ppc()
|
||||||
.and_then(|a| a.extab.as_ref())
|
.and_then(|a| a.extab.as_ref())
|
||||||
@@ -94,6 +95,13 @@ fn to_obj_symbol(
|
|||||||
})
|
})
|
||||||
.unwrap_or(&[]);
|
.unwrap_or(&[]);
|
||||||
|
|
||||||
|
let kind = match symbol.kind() {
|
||||||
|
SymbolKind::Text => ObjSymbolKind::Function,
|
||||||
|
SymbolKind::Data => ObjSymbolKind::Object,
|
||||||
|
SymbolKind::Section => ObjSymbolKind::Section,
|
||||||
|
_ => ObjSymbolKind::Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
demangled_name,
|
demangled_name,
|
||||||
@@ -101,8 +109,9 @@ fn to_obj_symbol(
|
|||||||
section_address,
|
section_address,
|
||||||
size: symbol.size(),
|
size: symbol.size(),
|
||||||
size_known: symbol.size() != 0,
|
size_known: symbol.size() != 0,
|
||||||
|
kind,
|
||||||
flags,
|
flags,
|
||||||
addend,
|
orig_section_index: symbol.section_index().map(|i| i.0),
|
||||||
virtual_address,
|
virtual_address,
|
||||||
original_index: Some(symbol.index().0),
|
original_index: Some(symbol.index().0),
|
||||||
bytes: bytes.to_vec(),
|
bytes: bytes.to_vec(),
|
||||||
@@ -152,16 +161,15 @@ fn symbols_by_section(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
name_counts: &mut HashMap<String, u32>,
|
name_counts: &mut HashMap<String, u32>,
|
||||||
) -> Result<Vec<ObjSymbol>> {
|
) -> Result<Vec<ObjSymbol>> {
|
||||||
let mut result = Vec::<ObjSymbol>::new();
|
let mut result = Vec::<ObjSymbol>::new();
|
||||||
for symbol in obj_file.symbols() {
|
for symbol in section_symbols {
|
||||||
if symbol.kind() == SymbolKind::Section {
|
if symbol.kind() == SymbolKind::Section {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(index) = symbol.section().index() {
|
|
||||||
if index.0 == section.orig_index {
|
|
||||||
if symbol.is_local() && section.kind == ObjSectionKind::Code {
|
if symbol.is_local() && section.kind == ObjSectionKind::Code {
|
||||||
// TODO strip local syms in diff?
|
// TODO strip local syms in diff?
|
||||||
let name = symbol.name().context("Failed to process symbol name")?;
|
let name = symbol.name().context("Failed to process symbol name")?;
|
||||||
@@ -169,9 +177,7 @@ fn symbols_by_section(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.push(to_obj_symbol(arch, obj_file, &symbol, 0, split_meta)?);
|
result.push(to_obj_symbol(arch, obj_file, symbol, split_meta)?);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
|
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
|
||||||
let mut iter = result.iter_mut().peekable();
|
let mut iter = result.iter_mut().peekable();
|
||||||
@@ -182,6 +188,13 @@ fn symbols_by_section(
|
|||||||
} else {
|
} else {
|
||||||
symbol.size = (section.address + section.size) - symbol.address;
|
symbol.size = (section.address + section.size) - symbol.address;
|
||||||
}
|
}
|
||||||
|
// Set symbol kind if we ended up with a non-zero size
|
||||||
|
if symbol.kind == ObjSymbolKind::Unknown && symbol.size > 0 {
|
||||||
|
symbol.kind = match section.kind {
|
||||||
|
ObjSectionKind::Code => ObjSymbolKind::Function,
|
||||||
|
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
@@ -199,8 +212,12 @@ fn symbols_by_section(
|
|||||||
section_address: 0,
|
section_address: 0,
|
||||||
size: section.size,
|
size: section.size,
|
||||||
size_known: true,
|
size_known: true,
|
||||||
|
kind: match section.kind {
|
||||||
|
ObjSectionKind::Code => ObjSymbolKind::Function,
|
||||||
|
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
|
||||||
|
},
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
addend: 0,
|
orig_section_index: Some(section.orig_index),
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
original_index: None,
|
original_index: None,
|
||||||
bytes: Vec::new(),
|
bytes: Vec::new(),
|
||||||
@@ -217,51 +234,75 @@ fn common_symbols(
|
|||||||
obj_file
|
obj_file
|
||||||
.symbols()
|
.symbols()
|
||||||
.filter(Symbol::is_common)
|
.filter(Symbol::is_common)
|
||||||
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, 0, split_meta))
|
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, split_meta))
|
||||||
.collect::<Result<Vec<ObjSymbol>>>()
|
.collect::<Result<Vec<ObjSymbol>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOW_PRIORITY_SYMBOLS: &[&str] =
|
||||||
|
&["__gnu_compiled_c", "__gnu_compiled_cplusplus", "gcc2_compiled."];
|
||||||
|
|
||||||
|
fn best_symbol<'r, 'data, 'file>(
|
||||||
|
symbols: &'r [Symbol<'data, 'file>],
|
||||||
|
address: u64,
|
||||||
|
) -> Option<&'r Symbol<'data, 'file>> {
|
||||||
|
let mut closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
|
||||||
|
Ok(index) => Some(index),
|
||||||
|
Err(index) => index.checked_sub(1),
|
||||||
|
}?;
|
||||||
|
// The binary search may not find the first symbol at the address, so work backwards
|
||||||
|
let target_address = symbols[closest_symbol_index].address();
|
||||||
|
while let Some(prev_index) = closest_symbol_index.checked_sub(1) {
|
||||||
|
if symbols[prev_index].address() != target_address {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
closest_symbol_index = prev_index;
|
||||||
|
}
|
||||||
|
let mut best_symbol: Option<&'r Symbol<'data, 'file>> = None;
|
||||||
|
for symbol in symbols.iter().skip(closest_symbol_index) {
|
||||||
|
if symbol.address() > address {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if symbol.kind() == SymbolKind::Section
|
||||||
|
|| (symbol.size() > 0 && (symbol.address() + symbol.size()) <= address)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// TODO priority ranking with visibility, etc
|
||||||
|
if let Some(best) = best_symbol {
|
||||||
|
if LOW_PRIORITY_SYMBOLS.contains(&best.name().unwrap_or_default())
|
||||||
|
&& !LOW_PRIORITY_SYMBOLS.contains(&symbol.name().unwrap_or_default())
|
||||||
|
{
|
||||||
|
best_symbol = Some(symbol);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
best_symbol = Some(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best_symbol
|
||||||
|
}
|
||||||
|
|
||||||
fn find_section_symbol(
|
fn find_section_symbol(
|
||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
target: &Symbol<'_, '_>,
|
section: &Section,
|
||||||
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
address: u64,
|
address: u64,
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<ObjSymbol> {
|
) -> Result<ObjSymbol> {
|
||||||
let section_index =
|
if let Some(symbol) = best_symbol(section_symbols, address) {
|
||||||
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
|
return to_obj_symbol(arch, obj_file, symbol, split_meta);
|
||||||
let section = obj_file.section_by_index(section_index)?;
|
|
||||||
let mut closest_symbol: Option<Symbol<'_, '_>> = None;
|
|
||||||
for symbol in obj_file.symbols() {
|
|
||||||
if !matches!(symbol.section_index(), Some(idx) if idx == section_index) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if symbol.kind() == SymbolKind::Section || symbol.address() != address {
|
// Fallback to section symbol
|
||||||
if symbol.address() < address
|
|
||||||
&& symbol.size() != 0
|
|
||||||
&& (closest_symbol.is_none()
|
|
||||||
|| matches!(&closest_symbol, Some(s) if s.address() <= symbol.address()))
|
|
||||||
{
|
|
||||||
closest_symbol = Some(symbol);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return to_obj_symbol(arch, obj_file, &symbol, 0, split_meta);
|
|
||||||
}
|
|
||||||
let (name, offset) = closest_symbol
|
|
||||||
.and_then(|s| s.name().map(|n| (n, s.address())).ok())
|
|
||||||
.or_else(|| section.name().map(|n| (n, section.address())).ok())
|
|
||||||
.unwrap_or(("<unknown>", 0));
|
|
||||||
let offset_addr = address - offset;
|
|
||||||
Ok(ObjSymbol {
|
Ok(ObjSymbol {
|
||||||
name: name.to_string(),
|
name: section.name()?.to_string(),
|
||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
address: offset,
|
address: section.address(),
|
||||||
section_address: address - section.address(),
|
section_address: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
size_known: false,
|
size_known: false,
|
||||||
|
kind: ObjSymbolKind::Section,
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
addend: offset_addr as i64,
|
orig_section_index: Some(section.index().0),
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
original_index: None,
|
original_index: None,
|
||||||
bytes: Vec::new(),
|
bytes: Vec::new(),
|
||||||
@@ -272,6 +313,7 @@ fn relocations_by_section(
|
|||||||
arch: &dyn ObjArch,
|
arch: &dyn ObjArch,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
|
section_symbols: &[Vec<Symbol<'_, '_>>],
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<Vec<ObjReloc>> {
|
) -> Result<Vec<ObjReloc>> {
|
||||||
let obj_section = obj_file.section_by_index(SectionIndex(section.orig_index))?;
|
let obj_section = obj_file.section_by_index(SectionIndex(section.orig_index))?;
|
||||||
@@ -296,30 +338,36 @@ fn relocations_by_section(
|
|||||||
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
|
||||||
};
|
};
|
||||||
let flags = reloc.flags(); // TODO validate reloc here?
|
let flags = reloc.flags(); // TODO validate reloc here?
|
||||||
let target_section = match symbol.section() {
|
let mut addend = if reloc.has_implicit_addend() {
|
||||||
SymbolSection::Common => Some(".comm".to_string()),
|
|
||||||
SymbolSection::Section(idx) => {
|
|
||||||
obj_file.section_by_index(idx).and_then(|s| s.name().map(|s| s.to_string())).ok()
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let addend = if reloc.has_implicit_addend() {
|
|
||||||
arch.implcit_addend(obj_file, section, address, &reloc)?
|
arch.implcit_addend(obj_file, section, address, &reloc)?
|
||||||
} else {
|
} else {
|
||||||
reloc.addend()
|
reloc.addend()
|
||||||
};
|
};
|
||||||
// println!("Reloc: {reloc:?}, symbol: {symbol:?}, addend: {addend:#x}");
|
|
||||||
let target = match symbol.kind() {
|
let target = match symbol.kind() {
|
||||||
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
|
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
|
||||||
to_obj_symbol(arch, obj_file, &symbol, addend, split_meta)
|
to_obj_symbol(arch, obj_file, &symbol, split_meta)?
|
||||||
}
|
}
|
||||||
SymbolKind::Section => {
|
SymbolKind::Section => {
|
||||||
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
|
ensure!(addend >= 0, "Negative addend in section reloc: {addend}");
|
||||||
find_section_symbol(arch, obj_file, &symbol, addend as u64, split_meta)
|
let section_index = symbol
|
||||||
|
.section_index()
|
||||||
|
.ok_or_else(|| anyhow!("Section symbol {symbol:?} has no section index"))?;
|
||||||
|
let section = obj_file.section_by_index(section_index)?;
|
||||||
|
let symbol = find_section_symbol(
|
||||||
|
arch,
|
||||||
|
obj_file,
|
||||||
|
§ion,
|
||||||
|
§ion_symbols[section_index.0],
|
||||||
|
addend as u64,
|
||||||
|
split_meta,
|
||||||
|
)?;
|
||||||
|
// Adjust addend to be relative to the selected symbol
|
||||||
|
addend = (symbol.address - section.address()) as i64;
|
||||||
|
symbol
|
||||||
}
|
}
|
||||||
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
|
kind => bail!("Unhandled relocation symbol type {kind:?}"),
|
||||||
}?;
|
};
|
||||||
relocations.push(ObjReloc { flags, address, target, target_section });
|
relocations.push(ObjReloc { flags, address, target, addend });
|
||||||
}
|
}
|
||||||
Ok(relocations)
|
Ok(relocations)
|
||||||
}
|
}
|
||||||
@@ -384,9 +432,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
let mut text_sections =
|
let mut text_sections =
|
||||||
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||||
let section_index = text_sections.next().map(|s| s.index().0);
|
let section_index = text_sections.next().map(|s| s.index().0);
|
||||||
let mut lines = section_index.map(|index| {
|
let mut lines = section_index
|
||||||
&mut sections.iter_mut().find(|s| s.orig_index == index).unwrap().line_info
|
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
|
||||||
});
|
.map(|s| &mut s.line_info);
|
||||||
|
|
||||||
let mut rows = program.rows();
|
let mut rows = program.rows();
|
||||||
while let Some((_header, row)) = rows.next_row()? {
|
while let Some((_header, row)) = rows.next_row()? {
|
||||||
@@ -397,13 +445,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
// The next row is the start of a new sequence, which means we must
|
// The next row is the start of a new sequence, which means we must
|
||||||
// advance to the next .text section.
|
// advance to the next .text section.
|
||||||
let section_index = text_sections.next().map(|s| s.index().0);
|
let section_index = text_sections.next().map(|s| s.index().0);
|
||||||
lines = section_index.map(|index| {
|
lines = section_index
|
||||||
&mut sections
|
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
|
||||||
.iter_mut()
|
.map(|s| &mut s.line_info);
|
||||||
.find(|s| s.orig_index == index)
|
|
||||||
.unwrap()
|
|
||||||
.line_info
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,8 +583,9 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
|
|||||||
section_address: (symbol.section_address as i64 + address_change).try_into()?,
|
section_address: (symbol.section_address as i64 + address_change).try_into()?,
|
||||||
size: symbol.size,
|
size: symbol.size,
|
||||||
size_known: symbol.size_known,
|
size_known: symbol.size_known,
|
||||||
|
kind: symbol.kind,
|
||||||
flags: symbol.flags,
|
flags: symbol.flags,
|
||||||
addend: symbol.addend,
|
orig_section_index: symbol.orig_section_index,
|
||||||
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
||||||
Some((virtual_address as i64 + address_change).try_into()?)
|
Some((virtual_address as i64 + address_change).try_into()?)
|
||||||
} else {
|
} else {
|
||||||
@@ -567,7 +612,7 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
|
|||||||
flags: reloc.flags,
|
flags: reloc.flags,
|
||||||
address: (reloc.address as i64 + address_change).try_into()?,
|
address: (reloc.address as i64 + address_change).try_into()?,
|
||||||
target: reloc.target, // TODO: Should be updated?
|
target: reloc.target, // TODO: Should be updated?
|
||||||
target_section: reloc.target_section, // TODO: Same as above
|
addend: reloc.addend,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,18 +692,40 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
|||||||
let obj_file = File::parse(data)?;
|
let obj_file = File::parse(data)?;
|
||||||
let arch = new_arch(&obj_file)?;
|
let arch = new_arch(&obj_file)?;
|
||||||
let split_meta = split_meta(&obj_file)?;
|
let split_meta = split_meta(&obj_file)?;
|
||||||
|
|
||||||
|
// Create sorted symbol list for each section
|
||||||
|
let mut section_symbols = Vec::with_capacity(obj_file.sections().count());
|
||||||
|
for section in obj_file.sections() {
|
||||||
|
let mut symbols = obj_file
|
||||||
|
.symbols()
|
||||||
|
.filter(|s| s.section_index() == Some(section.index()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
symbols.sort_by_key(|s| s.address());
|
||||||
|
let section_index = section.index().0;
|
||||||
|
if section_index >= section_symbols.len() {
|
||||||
|
section_symbols.resize_with(section_index + 1, Vec::new);
|
||||||
|
}
|
||||||
|
section_symbols[section_index] = symbols;
|
||||||
|
}
|
||||||
|
|
||||||
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
||||||
let mut name_counts: HashMap<String, u32> = HashMap::new();
|
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
|
||||||
for section in &mut sections {
|
for section in &mut sections {
|
||||||
section.symbols = symbols_by_section(
|
section.symbols = symbols_by_section(
|
||||||
arch.as_ref(),
|
arch.as_ref(),
|
||||||
&obj_file,
|
&obj_file,
|
||||||
section,
|
section,
|
||||||
|
§ion_symbols[section.orig_index],
|
||||||
|
split_meta.as_ref(),
|
||||||
|
&mut section_name_counts,
|
||||||
|
)?;
|
||||||
|
section.relocations = relocations_by_section(
|
||||||
|
arch.as_ref(),
|
||||||
|
&obj_file,
|
||||||
|
section,
|
||||||
|
§ion_symbols,
|
||||||
split_meta.as_ref(),
|
split_meta.as_ref(),
|
||||||
&mut name_counts,
|
|
||||||
)?;
|
)?;
|
||||||
section.relocations =
|
|
||||||
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
|
||||||
}
|
}
|
||||||
if config.combine_data_sections {
|
if config.combine_data_sections {
|
||||||
combine_data_sections(&mut sections)?;
|
combine_data_sections(&mut sections)?;
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ use globset::{Glob, GlobSet};
|
|||||||
use notify::{RecursiveMode, Watcher};
|
use notify::{RecursiveMode, Watcher};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{
|
config::{
|
||||||
build_globset, ProjectConfigInfo, ProjectObject, ScratchConfig, DEFAULT_WATCH_PATTERNS,
|
build_globset, save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject,
|
||||||
|
ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
||||||
},
|
},
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
};
|
};
|
||||||
@@ -42,11 +43,10 @@ use crate::{
|
|||||||
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
||||||
jobs::{jobs_menu_ui, jobs_window},
|
jobs::{jobs_menu_ui, jobs_window},
|
||||||
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
||||||
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ViewState {
|
pub struct ViewState {
|
||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub config_state: ConfigViewState,
|
pub config_state: ConfigViewState,
|
||||||
@@ -63,10 +63,34 @@ pub struct ViewState {
|
|||||||
pub show_debug: bool,
|
pub show_debug: bool,
|
||||||
pub show_graphics: bool,
|
pub show_graphics: bool,
|
||||||
pub show_jobs: bool,
|
pub show_jobs: bool,
|
||||||
|
pub show_side_panel: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ViewState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
jobs: Default::default(),
|
||||||
|
config_state: Default::default(),
|
||||||
|
demangle_state: Default::default(),
|
||||||
|
rlwinm_decode_state: Default::default(),
|
||||||
|
diff_state: Default::default(),
|
||||||
|
graphics_state: Default::default(),
|
||||||
|
frame_history: Default::default(),
|
||||||
|
show_appearance_config: false,
|
||||||
|
show_demangle: false,
|
||||||
|
show_rlwinm_decode: false,
|
||||||
|
show_project_config: false,
|
||||||
|
show_arch_config: false,
|
||||||
|
show_debug: false,
|
||||||
|
show_graphics: false,
|
||||||
|
show_jobs: false,
|
||||||
|
show_side_panel: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The configuration for a single object file.
|
/// The configuration for a single object file.
|
||||||
#[derive(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>,
|
pub target_path: Option<PathBuf>,
|
||||||
@@ -75,6 +99,23 @@ pub struct ObjectConfig {
|
|||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
pub source_path: Option<String>,
|
pub source_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: SymbolMappings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ProjectObject> for ObjectConfig {
|
||||||
|
fn from(object: &ProjectObject) -> Self {
|
||||||
|
Self {
|
||||||
|
name: object.name().to_string(),
|
||||||
|
target_path: object.target_path.clone(),
|
||||||
|
base_path: object.base_path.clone(),
|
||||||
|
reverse_fn_order: object.reverse_fn_order(),
|
||||||
|
complete: object.complete(),
|
||||||
|
scratch: object.scratch.clone(),
|
||||||
|
source_path: object.source_path().cloned(),
|
||||||
|
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -94,8 +135,14 @@ pub struct AppState {
|
|||||||
pub obj_change: bool,
|
pub obj_change: bool,
|
||||||
pub queue_build: bool,
|
pub queue_build: bool,
|
||||||
pub queue_reload: bool,
|
pub queue_reload: bool,
|
||||||
|
pub current_project_config: Option<ProjectConfig>,
|
||||||
pub project_config_info: Option<ProjectConfigInfo>,
|
pub project_config_info: Option<ProjectConfigInfo>,
|
||||||
pub last_mod_check: Instant,
|
pub last_mod_check: Instant,
|
||||||
|
/// The right object symbol name that we're selecting a left symbol for
|
||||||
|
pub selecting_left: Option<String>,
|
||||||
|
/// The left object symbol name that we're selecting a right symbol for
|
||||||
|
pub selecting_right: Option<String>,
|
||||||
|
pub config_error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppState {
|
impl Default for AppState {
|
||||||
@@ -109,8 +156,12 @@ impl Default for AppState {
|
|||||||
obj_change: false,
|
obj_change: false,
|
||||||
queue_build: false,
|
queue_build: false,
|
||||||
queue_reload: false,
|
queue_reload: false,
|
||||||
|
current_project_config: None,
|
||||||
project_config_info: None,
|
project_config_info: None,
|
||||||
last_mod_check: Instant::now(),
|
last_mod_check: Instant::now(),
|
||||||
|
selecting_left: None,
|
||||||
|
selecting_right: None,
|
||||||
|
config_error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +242,10 @@ impl AppState {
|
|||||||
self.config_change = true;
|
self.config_change = true;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
|
self.current_project_config = None;
|
||||||
self.project_config_info = None;
|
self.project_config_info = None;
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||||
@@ -199,6 +253,8 @@ impl AppState {
|
|||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||||
@@ -206,12 +262,132 @@ impl AppState {
|
|||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selected_obj(&mut self, object: ObjectConfig) {
|
pub fn set_selected_obj(&mut self, config: ObjectConfig) {
|
||||||
self.config.selected_obj = Some(object);
|
let mut unit_changed = true;
|
||||||
|
if let Some(existing) = self.config.selected_obj.as_ref() {
|
||||||
|
if existing == &config {
|
||||||
|
// Don't reload the object if there were no changes
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if existing.name == config.name {
|
||||||
|
unit_changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.config.selected_obj = Some(config);
|
||||||
|
if unit_changed {
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
self.queue_build = false;
|
self.queue_build = false;
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
|
} else {
|
||||||
|
self.queue_build = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_selected_obj(&mut self) {
|
||||||
|
self.config.selected_obj = None;
|
||||||
|
self.obj_change = true;
|
||||||
|
self.queue_build = false;
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selecting_left(&mut self, right: &str) {
|
||||||
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
object.symbol_mappings.remove_by_right(right);
|
||||||
|
self.selecting_left = Some(right.to_string());
|
||||||
|
self.queue_reload = true;
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selecting_right(&mut self, left: &str) {
|
||||||
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
object.symbol_mappings.remove_by_left(left);
|
||||||
|
self.selecting_right = Some(left.to_string());
|
||||||
|
self.queue_reload = true;
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_symbol_mapping(&mut self, left: String, right: String) {
|
||||||
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
||||||
|
log::warn!("No selected object");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
|
if left == right {
|
||||||
|
object.symbol_mappings.remove_by_left(&left);
|
||||||
|
object.symbol_mappings.remove_by_right(&right);
|
||||||
|
} else {
|
||||||
|
object.symbol_mappings.insert(left.clone(), right.clone());
|
||||||
|
}
|
||||||
|
self.queue_reload = true;
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_selection(&mut self) {
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
|
self.queue_reload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_mappings(&mut self) {
|
||||||
|
self.selecting_left = None;
|
||||||
|
self.selecting_right = None;
|
||||||
|
if let Some(object) = self.config.selected_obj.as_mut() {
|
||||||
|
object.symbol_mappings.clear();
|
||||||
|
}
|
||||||
|
self.queue_reload = true;
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_selecting_symbol(&self) -> bool {
|
||||||
|
self.selecting_left.is_some() || self.selecting_right.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(&mut self) {
|
||||||
|
let (Some(config), Some(info)) =
|
||||||
|
(self.current_project_config.as_mut(), self.project_config_info.as_mut())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Update the project config with the current state
|
||||||
|
if let Some(object) = self.config.selected_obj.as_ref() {
|
||||||
|
if let Some(existing) = config.units.as_mut().and_then(|v| {
|
||||||
|
v.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
||||||
|
}) {
|
||||||
|
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(object.symbol_mappings.clone())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Some(existing) =
|
||||||
|
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
||||||
|
{
|
||||||
|
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(object.symbol_mappings.clone())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save the updated project config
|
||||||
|
match save_project_config(config, info) {
|
||||||
|
Ok(new_info) => *info = new_info,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to save project config: {e}");
|
||||||
|
self.config_error = Some(format!("Failed to save project config: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,31 +526,41 @@ impl App {
|
|||||||
debug_assert!(jobs.results.is_empty());
|
debug_assert!(jobs.results.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_update(&mut self, ctx: &egui::Context) {
|
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
|
||||||
self.appearance.post_update(ctx);
|
self.appearance.post_update(ctx);
|
||||||
|
|
||||||
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
||||||
config_state.post_update(ctx, jobs, &self.state);
|
config_state.post_update(ctx, jobs, &self.state);
|
||||||
diff_state.post_update(ctx, jobs, &self.state);
|
diff_state.post_update(action, ctx, jobs, &self.state);
|
||||||
|
|
||||||
let Ok(mut state) = self.state.write() else {
|
let Ok(mut state) = self.state.write() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let state = &mut *state;
|
let state = &mut *state;
|
||||||
|
|
||||||
|
let mut mod_check = false;
|
||||||
|
if state.last_mod_check.elapsed().as_millis() >= 500 {
|
||||||
|
state.last_mod_check = Instant::now();
|
||||||
|
mod_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod_check {
|
||||||
if let Some(info) = &state.project_config_info {
|
if let Some(info) = &state.project_config_info {
|
||||||
if file_modified(&info.path, info.timestamp) {
|
if let Some(last_ts) = info.timestamp {
|
||||||
|
if file_modified(&info.path, last_ts) {
|
||||||
state.config_change = true;
|
state.config_change = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if state.config_change {
|
if state.config_change {
|
||||||
state.config_change = false;
|
state.config_change = false;
|
||||||
match load_project_config(state) {
|
match load_project_config(state) {
|
||||||
Ok(()) => config_state.load_error = None,
|
Ok(()) => state.config_error = None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load project config: {e}");
|
log::error!("Failed to load project config: {e}");
|
||||||
config_state.load_error = Some(format!("{e}"));
|
state.config_error = Some(format!("{e}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,8 +595,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = &diff_state.build {
|
if let Some(result) = &diff_state.build {
|
||||||
if state.last_mod_check.elapsed().as_millis() >= 500 {
|
if mod_check {
|
||||||
state.last_mod_check = Instant::now();
|
|
||||||
if let Some((obj, _)) = &result.first_obj {
|
if let Some((obj, _)) = &result.first_obj {
|
||||||
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
||||||
if file_modified(path, timestamp) {
|
if file_modified(path, timestamp) {
|
||||||
@@ -434,11 +619,11 @@ impl App {
|
|||||||
&& state.config.selected_obj.is_some()
|
&& state.config.selected_obj.is_some()
|
||||||
&& !jobs.is_running(Job::ObjDiff)
|
&& !jobs.is_running(Job::ObjDiff)
|
||||||
{
|
{
|
||||||
jobs.push(start_build(ctx, ObjDiffConfig::from_config(&state.config)));
|
jobs.push(start_build(ctx, ObjDiffConfig::from_state(state)));
|
||||||
state.queue_build = false;
|
state.queue_build = false;
|
||||||
state.queue_reload = false;
|
state.queue_reload = false;
|
||||||
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
||||||
let mut diff_config = ObjDiffConfig::from_config(&state.config);
|
let mut diff_config = ObjDiffConfig::from_state(state);
|
||||||
// Don't build, just reload the current files
|
// Don't build, just reload the current files
|
||||||
diff_config.build_base = false;
|
diff_config.build_base = false;
|
||||||
diff_config.build_target = false;
|
diff_config.build_target = false;
|
||||||
@@ -485,12 +670,26 @@ impl eframe::App for App {
|
|||||||
show_debug,
|
show_debug,
|
||||||
show_graphics,
|
show_graphics,
|
||||||
show_jobs,
|
show_jobs,
|
||||||
|
show_side_panel,
|
||||||
} = view_state;
|
} = view_state;
|
||||||
|
|
||||||
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||||
|
|
||||||
|
let side_panel_available = diff_state.current_view == View::SymbolDiff;
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| {
|
egui::menu::bar(ui, |ui| {
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
side_panel_available,
|
||||||
|
egui::Button::new(if *show_side_panel { "⏴" } else { "⏵" }),
|
||||||
|
)
|
||||||
|
.on_hover_text("Toggle side panel")
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
*show_side_panel = !*show_side_panel;
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if ui.button("Debug…").clicked() {
|
if ui.button("Debug…").clicked() {
|
||||||
@@ -599,6 +798,11 @@ impl eframe::App for App {
|
|||||||
{
|
{
|
||||||
state.queue_reload = true;
|
state.queue_reload = true;
|
||||||
}
|
}
|
||||||
|
if ui.button("Clear custom symbol mappings").clicked() {
|
||||||
|
state.clear_mappings();
|
||||||
|
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
||||||
|
state.queue_reload = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if jobs_menu_ui(ui, jobs, appearance) {
|
if jobs_menu_ui(ui, jobs, appearance) {
|
||||||
@@ -607,31 +811,28 @@ impl eframe::App for App {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
|
if side_panel_available {
|
||||||
if diff_state.current_view == View::FunctionDiff && build_success {
|
egui::SidePanel::left("side_panel").show_animated(ctx, *show_side_panel, |ui| {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
function_diff_ui(ui, diff_state, appearance);
|
|
||||||
});
|
|
||||||
} else if diff_state.current_view == View::DataDiff && build_success {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
data_diff_ui(ui, diff_state, appearance);
|
|
||||||
});
|
|
||||||
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
extab_diff_ui(ui, diff_state, appearance);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
config_ui(ui, state, show_project_config, config_state, appearance);
|
config_ui(ui, state, show_project_config, config_state, appearance);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
symbol_diff_ui(ui, diff_state, appearance);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut action = None;
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
|
||||||
|
action = if diff_state.current_view == View::FunctionDiff && build_success {
|
||||||
|
function_diff_ui(ui, diff_state, appearance)
|
||||||
|
} else if diff_state.current_view == View::DataDiff && build_success {
|
||||||
|
data_diff_ui(ui, diff_state, appearance)
|
||||||
|
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
||||||
|
extab_diff_ui(ui, diff_state, appearance)
|
||||||
|
} else {
|
||||||
|
symbol_diff_ui(ui, diff_state, appearance)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
project_window(ctx, state, show_project_config, config_state, appearance);
|
project_window(ctx, state, show_project_config, config_state, appearance);
|
||||||
appearance_window(ctx, show_appearance_config, appearance);
|
appearance_window(ctx, show_appearance_config, appearance);
|
||||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||||
@@ -641,10 +842,10 @@ impl eframe::App for App {
|
|||||||
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
|
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
|
||||||
jobs_window(ctx, show_jobs, jobs, appearance);
|
jobs_window(ctx, show_jobs, jobs, appearance);
|
||||||
|
|
||||||
self.post_update(ctx);
|
self.post_update(ctx, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by the frame work to save state before shutdown.
|
/// Called by the framework to save state before shutdown.
|
||||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||||
if let Ok(state) = self.state.read() {
|
if let Ok(state) = self.state.read() {
|
||||||
eframe::set_value(storage, CONFIG_KEY, &state.config);
|
eframe::set_value(storage, CONFIG_KEY, &state.config);
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use eframe::Storage;
|
use eframe::Storage;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
|
use objdiff_core::{
|
||||||
|
config::ScratchConfig,
|
||||||
|
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, MipsAbi, MipsInstrCategory, X86Formatter},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
||||||
|
|
||||||
@@ -11,7 +15,7 @@ pub struct AppConfigVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfigVersion {
|
impl Default for AppConfigVersion {
|
||||||
fn default() -> Self { Self { version: 1 } }
|
fn default() -> Self { Self { version: 2 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
|
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
|
||||||
@@ -19,7 +23,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 {
|
||||||
1 => from_str::<AppConfig>(&str),
|
2 => from_str::<AppConfig>(&str),
|
||||||
|
1 => from_str::<AppConfigV1>(&str).map(|c| c.into_config()),
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("Unknown config version: {}", version.version);
|
log::warn!("Unknown config version: {}", version.version);
|
||||||
None
|
None
|
||||||
@@ -44,6 +49,180 @@ where T: serde::de::DeserializeOwned {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ScratchConfigV1 {
|
||||||
|
#[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<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub build_ctx: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScratchConfigV1 {
|
||||||
|
fn into_config(self) -> ScratchConfig {
|
||||||
|
ScratchConfig {
|
||||||
|
platform: self.platform,
|
||||||
|
compiler: self.compiler,
|
||||||
|
c_flags: self.c_flags,
|
||||||
|
ctx_path: self.ctx_path,
|
||||||
|
build_ctx: self.build_ctx.then_some(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ObjectConfigV1 {
|
||||||
|
pub name: String,
|
||||||
|
pub target_path: Option<PathBuf>,
|
||||||
|
pub base_path: Option<PathBuf>,
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
pub complete: Option<bool>,
|
||||||
|
pub scratch: Option<ScratchConfigV1>,
|
||||||
|
pub source_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectConfigV1 {
|
||||||
|
fn into_config(self) -> ObjectConfig {
|
||||||
|
ObjectConfig {
|
||||||
|
name: self.name,
|
||||||
|
target_path: self.target_path,
|
||||||
|
base_path: self.base_path,
|
||||||
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
|
complete: self.complete,
|
||||||
|
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||||
|
source_path: self.source_path,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct DiffObjConfigV1 {
|
||||||
|
pub relax_reloc_diffs: bool,
|
||||||
|
#[serde(default = "bool_true")]
|
||||||
|
pub space_between_args: bool,
|
||||||
|
pub combine_data_sections: bool,
|
||||||
|
// x86
|
||||||
|
pub x86_formatter: X86Formatter,
|
||||||
|
// MIPS
|
||||||
|
pub mips_abi: MipsAbi,
|
||||||
|
pub mips_instr_category: MipsInstrCategory,
|
||||||
|
// ARM
|
||||||
|
pub arm_arch_version: ArmArchVersion,
|
||||||
|
pub arm_unified_syntax: bool,
|
||||||
|
pub arm_av_registers: bool,
|
||||||
|
pub arm_r9_usage: ArmR9Usage,
|
||||||
|
pub arm_sl_usage: bool,
|
||||||
|
pub arm_fp_usage: bool,
|
||||||
|
pub arm_ip_usage: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DiffObjConfigV1 {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
relax_reloc_diffs: false,
|
||||||
|
space_between_args: true,
|
||||||
|
combine_data_sections: false,
|
||||||
|
x86_formatter: Default::default(),
|
||||||
|
mips_abi: Default::default(),
|
||||||
|
mips_instr_category: Default::default(),
|
||||||
|
arm_arch_version: Default::default(),
|
||||||
|
arm_unified_syntax: true,
|
||||||
|
arm_av_registers: false,
|
||||||
|
arm_r9_usage: Default::default(),
|
||||||
|
arm_sl_usage: false,
|
||||||
|
arm_fp_usage: false,
|
||||||
|
arm_ip_usage: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffObjConfigV1 {
|
||||||
|
fn into_config(self) -> DiffObjConfig {
|
||||||
|
DiffObjConfig {
|
||||||
|
relax_reloc_diffs: self.relax_reloc_diffs,
|
||||||
|
space_between_args: self.space_between_args,
|
||||||
|
combine_data_sections: self.combine_data_sections,
|
||||||
|
x86_formatter: self.x86_formatter,
|
||||||
|
mips_abi: self.mips_abi,
|
||||||
|
mips_instr_category: self.mips_instr_category,
|
||||||
|
arm_arch_version: self.arm_arch_version,
|
||||||
|
arm_unified_syntax: self.arm_unified_syntax,
|
||||||
|
arm_av_registers: self.arm_av_registers,
|
||||||
|
arm_r9_usage: self.arm_r9_usage,
|
||||||
|
arm_sl_usage: self.arm_sl_usage,
|
||||||
|
arm_fp_usage: self.arm_fp_usage,
|
||||||
|
arm_ip_usage: self.arm_ip_usage,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn bool_true() -> bool { true }
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct AppConfigV1 {
|
||||||
|
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<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_obj_dir: Option<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub base_obj_dir: Option<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub selected_obj: Option<ObjectConfigV1>,
|
||||||
|
#[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<PathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diff_obj_config: DiffObjConfigV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfigV1 {
|
||||||
|
fn into_config(self) -> AppConfig {
|
||||||
|
log::info!("Upgrading configuration from v1");
|
||||||
|
AppConfig {
|
||||||
|
custom_make: self.custom_make,
|
||||||
|
custom_args: self.custom_args,
|
||||||
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
|
project_dir: self.project_dir,
|
||||||
|
target_obj_dir: self.target_obj_dir,
|
||||||
|
base_obj_dir: self.base_obj_dir,
|
||||||
|
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 ObjectConfigV0 {
|
pub struct ObjectConfigV0 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -59,9 +238,7 @@ impl ObjectConfigV0 {
|
|||||||
target_path: Some(self.target_path),
|
target_path: Some(self.target_path),
|
||||||
base_path: Some(self.base_path),
|
base_path: Some(self.base_path),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
complete: None,
|
..Default::default()
|
||||||
scratch: None,
|
|
||||||
source_path: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use anyhow::Result;
|
|||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
||||||
|
|
||||||
use crate::app::AppState;
|
use crate::app::{AppState, ObjectConfig};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ProjectObjectNode {
|
pub enum ProjectObjectNode {
|
||||||
File(String, Box<ProjectObject>),
|
Unit(String, usize),
|
||||||
Dir(String, Vec<ProjectObjectNode>),
|
Dir(String, Vec<ProjectObjectNode>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,17 +33,18 @@ fn find_dir<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_nodes(
|
fn build_nodes(
|
||||||
objects: &[ProjectObject],
|
units: &mut [ProjectObject],
|
||||||
project_dir: &Path,
|
project_dir: &Path,
|
||||||
target_obj_dir: Option<&Path>,
|
target_obj_dir: Option<&Path>,
|
||||||
base_obj_dir: Option<&Path>,
|
base_obj_dir: Option<&Path>,
|
||||||
) -> Vec<ProjectObjectNode> {
|
) -> Vec<ProjectObjectNode> {
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for object in objects {
|
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) = &object.name {
|
let path = if let Some(name) = &unit.name {
|
||||||
Path::new(name)
|
Path::new(name)
|
||||||
} else if let Some(path) = &object.path {
|
} else if let Some(path) = &unit.path {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -56,10 +57,8 @@ fn build_nodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut object = Box::new(object.clone());
|
|
||||||
object.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
|
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
out_nodes.push(ProjectObjectNode::File(filename, object));
|
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||||
}
|
}
|
||||||
nodes
|
nodes
|
||||||
}
|
}
|
||||||
@@ -70,24 +69,36 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
};
|
};
|
||||||
if let Some((result, info)) = try_project_config(project_dir) {
|
if let Some((result, info)) = try_project_config(project_dir) {
|
||||||
let project_config = result?;
|
let project_config = result?;
|
||||||
state.config.custom_make = project_config.custom_make;
|
state.config.custom_make = project_config.custom_make.clone();
|
||||||
state.config.custom_args = project_config.custom_args;
|
state.config.custom_args = project_config.custom_args.clone();
|
||||||
state.config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
|
state.config.target_obj_dir =
|
||||||
state.config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
|
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
|
||||||
state.config.build_base = project_config.build_base;
|
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
||||||
state.config.build_target = project_config.build_target;
|
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||||
state.config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| {
|
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||||
|
state.config.watch_patterns = project_config.watch_patterns.clone().unwrap_or_else(|| {
|
||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
});
|
});
|
||||||
state.watcher_change = true;
|
state.watcher_change = true;
|
||||||
state.objects = project_config.objects;
|
state.objects = project_config.units.clone().unwrap_or_default();
|
||||||
state.object_nodes = build_nodes(
|
state.object_nodes = build_nodes(
|
||||||
&state.objects,
|
&mut state.objects,
|
||||||
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(),
|
||||||
);
|
);
|
||||||
|
state.current_project_config = Some(project_config);
|
||||||
state.project_config_info = Some(info);
|
state.project_config_info = Some(info);
|
||||||
|
|
||||||
|
// Reload selected object
|
||||||
|
if let Some(selected_obj) = &state.config.selected_obj {
|
||||||
|
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
|
||||||
|
let config = ObjectConfig::from(obj);
|
||||||
|
state.set_selected_obj(config);
|
||||||
|
} else {
|
||||||
|
state.clear_selected_obj();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl CreateScratchConfig {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
build_config: BuildConfig::from_config(config),
|
build_config: BuildConfig::from_config(config),
|
||||||
context_path: scratch_config.ctx_path.clone(),
|
context_path: scratch_config.ctx_path.clone(),
|
||||||
build_context: scratch_config.build_ctx,
|
build_context: scratch_config.build_ctx.unwrap_or(false),
|
||||||
compiler: scratch_config.compiler.clone().unwrap_or_default(),
|
compiler: scratch_config.compiler.clone().unwrap_or_default(),
|
||||||
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(),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl JobQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether any job is running.
|
/// Returns whether any job is running.
|
||||||
#[allow(dead_code)]
|
#[expect(dead_code)]
|
||||||
pub fn any_running(&self) -> bool {
|
pub fn any_running(&self) -> bool {
|
||||||
self.jobs.iter().any(|job| {
|
self.jobs.iter().any(|job| {
|
||||||
if let Some(handle) = &job.handle {
|
if let Some(handle) = &job.handle {
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{diff_objs, DiffObjConfig, ObjDiff},
|
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff},
|
||||||
obj::{read, ObjInfo},
|
obj::{read, ObjInfo},
|
||||||
};
|
};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, ObjectConfig},
|
app::{AppConfig, AppState, ObjectConfig},
|
||||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,16 +60,20 @@ pub struct ObjDiffConfig {
|
|||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub selected_obj: Option<ObjectConfig>,
|
pub selected_obj: Option<ObjectConfig>,
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
|
pub selecting_left: Option<String>,
|
||||||
|
pub selecting_right: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjDiffConfig {
|
impl ObjDiffConfig {
|
||||||
pub(crate) fn from_config(config: &AppConfig) -> Self {
|
pub(crate) fn from_state(state: &AppState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
build_config: BuildConfig::from_config(config),
|
build_config: BuildConfig::from_config(&state.config),
|
||||||
build_base: config.build_base,
|
build_base: state.config.build_base,
|
||||||
build_target: config.build_target,
|
build_target: state.config.build_target,
|
||||||
selected_obj: config.selected_obj.clone(),
|
selected_obj: state.config.selected_obj.clone(),
|
||||||
diff_obj_config: config.diff_obj_config.clone(),
|
diff_obj_config: state.config.diff_obj_config.clone(),
|
||||||
|
selecting_left: state.selecting_left.clone(),
|
||||||
|
selecting_right: state.selecting_right.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,9 +162,16 @@ pub(crate) fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
|||||||
fn run_build(
|
fn run_build(
|
||||||
context: &JobContext,
|
context: &JobContext,
|
||||||
cancel: Receiver<()>,
|
cancel: Receiver<()>,
|
||||||
config: ObjDiffConfig,
|
mut config: ObjDiffConfig,
|
||||||
) -> Result<Box<ObjDiffResult>> {
|
) -> Result<Box<ObjDiffResult>> {
|
||||||
let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
let obj_config = config.selected_obj.ok_or_else(|| Error::msg("Missing obj path"))?;
|
||||||
|
// Use the per-object symbol mappings, we don't set mappings globally
|
||||||
|
config.diff_obj_config.symbol_mappings = MappingConfig {
|
||||||
|
mappings: obj_config.symbol_mappings,
|
||||||
|
selecting_left: config.selecting_left,
|
||||||
|
selecting_right: config.selecting_right,
|
||||||
|
};
|
||||||
|
|
||||||
let project_dir = config
|
let project_dir = config
|
||||||
.build_config
|
.build_config
|
||||||
.project_dir
|
.project_dir
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
|||||||
Color32::from_rgb(255, 0, 0),
|
Color32::from_rgb(255, 0, 0),
|
||||||
Color32::from_rgb(255, 255, 0),
|
Color32::from_rgb(255, 255, 0),
|
||||||
Color32::from_rgb(255, 192, 203),
|
Color32::from_rgb(255, 192, 203),
|
||||||
Color32::from_rgb(0, 0, 255),
|
Color32::from_rgb(128, 128, 255),
|
||||||
Color32::from_rgb(0, 255, 0),
|
Color32::from_rgb(0, 255, 0),
|
||||||
Color32::from_rgb(213, 138, 138),
|
Color32::from_rgb(213, 138, 138),
|
||||||
];
|
];
|
||||||
|
|||||||
82
objdiff-gui/src/views/column_layout.rs
Normal file
82
objdiff-gui/src/views/column_layout.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use egui::{Align, Layout, Sense, Vec2};
|
||||||
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder, TableRow};
|
||||||
|
|
||||||
|
pub fn render_header(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
available_width: f32,
|
||||||
|
num_columns: usize,
|
||||||
|
mut add_contents: impl FnMut(&mut egui::Ui, usize),
|
||||||
|
) {
|
||||||
|
let column_width = available_width / num_columns as f32;
|
||||||
|
ui.allocate_ui_with_layout(
|
||||||
|
Vec2 { x: available_width, y: 100.0 },
|
||||||
|
Layout::left_to_right(Align::Min),
|
||||||
|
|ui| {
|
||||||
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
||||||
|
for i in 0..num_columns {
|
||||||
|
ui.allocate_ui_with_layout(
|
||||||
|
Vec2 { x: column_width, y: 100.0 },
|
||||||
|
Layout::top_down(Align::Min),
|
||||||
|
|ui| {
|
||||||
|
ui.set_width(column_width);
|
||||||
|
add_contents(ui, i);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ui.separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_table(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
available_width: f32,
|
||||||
|
num_columns: usize,
|
||||||
|
row_height: f32,
|
||||||
|
total_rows: usize,
|
||||||
|
mut add_contents: impl FnMut(&mut TableRow, usize),
|
||||||
|
) {
|
||||||
|
ui.style_mut().interaction.selectable_labels = false;
|
||||||
|
let column_width = available_width / num_columns as f32;
|
||||||
|
let available_height = ui.available_height();
|
||||||
|
let table = TableBuilder::new(ui)
|
||||||
|
.striped(false)
|
||||||
|
.cell_layout(Layout::left_to_right(Align::Min))
|
||||||
|
.columns(Column::exact(column_width).clip(true), num_columns)
|
||||||
|
.resizable(false)
|
||||||
|
.auto_shrink([false, false])
|
||||||
|
.min_scrolled_height(available_height)
|
||||||
|
.sense(Sense::click());
|
||||||
|
table.body(|body| {
|
||||||
|
body.rows(row_height, total_rows, |mut row| {
|
||||||
|
row.set_hovered(false); // Disable hover effect
|
||||||
|
for i in 0..num_columns {
|
||||||
|
add_contents(&mut row, i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_strips(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
available_width: f32,
|
||||||
|
num_columns: usize,
|
||||||
|
mut add_contents: impl FnMut(&mut egui::Ui, usize),
|
||||||
|
) {
|
||||||
|
let column_width = available_width / num_columns as f32;
|
||||||
|
StripBuilder::new(ui).size(Size::remainder()).clip(true).vertical(|mut strip| {
|
||||||
|
strip.strip(|builder| {
|
||||||
|
builder.sizes(Size::exact(column_width), num_columns).clip(true).horizontal(
|
||||||
|
|mut strip| {
|
||||||
|
for i in 0..num_columns {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ui.push_id(i, |ui| {
|
||||||
|
add_contents(ui, i);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -43,7 +43,6 @@ pub struct ConfigViewState {
|
|||||||
pub build_running: bool,
|
pub build_running: bool,
|
||||||
pub queue_build: bool,
|
pub queue_build: bool,
|
||||||
pub watch_pattern_text: String,
|
pub watch_pattern_text: String,
|
||||||
pub load_error: Option<String>,
|
|
||||||
pub object_search: String,
|
pub object_search: String,
|
||||||
pub filter_diffable: bool,
|
pub filter_diffable: bool,
|
||||||
pub filter_incomplete: bool,
|
pub filter_incomplete: bool,
|
||||||
@@ -93,10 +92,7 @@ impl ConfigViewState {
|
|||||||
name: obj_path.display().to_string(),
|
name: obj_path.display().to_string(),
|
||||||
target_path: Some(target_path),
|
target_path: Some(target_path),
|
||||||
base_path: Some(path),
|
base_path: Some(path),
|
||||||
reverse_fn_order: None,
|
..Default::default()
|
||||||
complete: None,
|
|
||||||
scratch: None,
|
|
||||||
source_path: None,
|
|
||||||
});
|
});
|
||||||
} 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);
|
||||||
@@ -104,10 +100,7 @@ impl ConfigViewState {
|
|||||||
name: obj_path.display().to_string(),
|
name: obj_path.display().to_string(),
|
||||||
target_path: Some(path),
|
target_path: Some(path),
|
||||||
base_path: Some(base_path),
|
base_path: Some(base_path),
|
||||||
reverse_fn_order: None,
|
..Default::default()
|
||||||
complete: None,
|
|
||||||
scratch: None,
|
|
||||||
source_path: None,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +223,10 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut new_selected_obj = selected_obj.clone();
|
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
|
||||||
|
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
|
||||||
|
});
|
||||||
|
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) {
|
||||||
if ui.button("Select object").clicked() {
|
if ui.button("Select object").clicked() {
|
||||||
@@ -316,6 +312,7 @@ pub fn config_ui(
|
|||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||||
for node in object_nodes.iter().filter_map(|node| {
|
for node in object_nodes.iter().filter_map(|node| {
|
||||||
filter_node(
|
filter_node(
|
||||||
|
objects,
|
||||||
node,
|
node,
|
||||||
&search,
|
&search,
|
||||||
config_state.filter_diffable,
|
config_state.filter_diffable,
|
||||||
@@ -325,8 +322,9 @@ pub fn config_ui(
|
|||||||
}) {
|
}) {
|
||||||
display_node(
|
display_node(
|
||||||
ui,
|
ui,
|
||||||
&mut new_selected_obj,
|
&mut new_selected_index,
|
||||||
project_dir.as_deref(),
|
project_dir.as_deref(),
|
||||||
|
objects,
|
||||||
&node,
|
&node,
|
||||||
appearance,
|
appearance,
|
||||||
node_open,
|
node_open,
|
||||||
@@ -334,10 +332,11 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if new_selected_obj != *selected_obj {
|
if new_selected_index != selected_index {
|
||||||
if let Some(obj) = new_selected_obj {
|
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
|
||||||
state_guard.set_selected_obj(obj);
|
let config = ObjectConfig::from(&objects[idx]);
|
||||||
|
state_guard.set_selected_obj(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if state_guard.config.selected_obj.is_some()
|
if state_guard.config.selected_obj.is_some()
|
||||||
@@ -347,16 +346,17 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_object(
|
fn display_unit(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<ObjectConfig>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
project_dir: Option<&Path>,
|
||||||
name: &str,
|
name: &str,
|
||||||
object: &ProjectObject,
|
units: &[ProjectObject],
|
||||||
|
index: usize,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) {
|
) {
|
||||||
let object_name = object.name();
|
let object = &units[index];
|
||||||
let selected = matches!(selected_obj, Some(obj) if obj.name == object_name);
|
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() {
|
||||||
@@ -381,18 +381,8 @@ fn display_object(
|
|||||||
if get_source_path(project_dir, object).is_some() {
|
if get_source_path(project_dir, object).is_some() {
|
||||||
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
||||||
}
|
}
|
||||||
// Always recreate ObjectConfig if selected, in case the project config changed.
|
if response.clicked() {
|
||||||
// ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild.
|
*selected_obj = Some(index);
|
||||||
if selected || response.clicked() {
|
|
||||||
*selected_obj = Some(ObjectConfig {
|
|
||||||
name: object_name.to_string(),
|
|
||||||
target_path: object.target_path.clone(),
|
|
||||||
base_path: object.base_path.clone(),
|
|
||||||
reverse_fn_order: object.reverse_fn_order(),
|
|
||||||
complete: object.complete(),
|
|
||||||
scratch: object.scratch.clone(),
|
|
||||||
source_path: object.source_path().cloned(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,18 +417,19 @@ enum NodeOpen {
|
|||||||
|
|
||||||
fn display_node(
|
fn display_node(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<ObjectConfig>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
project_dir: Option<&Path>,
|
||||||
|
units: &[ProjectObject],
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
node_open: NodeOpen,
|
node_open: NodeOpen,
|
||||||
) {
|
) {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::File(name, object) => {
|
ProjectObjectNode::Unit(name, idx) => {
|
||||||
display_object(ui, selected_obj, project_dir, name, object, appearance);
|
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
|
||||||
}
|
}
|
||||||
ProjectObjectNode::Dir(name, children) => {
|
ProjectObjectNode::Dir(name, children) => {
|
||||||
let contains_obj = selected_obj.as_ref().map(|path| contains_node(node, path));
|
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
||||||
let open = match node_open {
|
let open = match node_open {
|
||||||
NodeOpen::Default => None,
|
NodeOpen::Default => None,
|
||||||
NodeOpen::Open => Some(true),
|
NodeOpen::Open => Some(true),
|
||||||
@@ -461,16 +452,16 @@ 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, node, appearance, node_open);
|
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_node(node: &ProjectObjectNode, selected_obj: &ObjectConfig) -> bool {
|
fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::File(_, object) => object.name() == selected_obj.name,
|
ProjectObjectNode::Unit(_, idx) => *idx == selected_obj,
|
||||||
ProjectObjectNode::Dir(_, children) => {
|
ProjectObjectNode::Dir(_, children) => {
|
||||||
children.iter().any(|node| contains_node(node, selected_obj))
|
children.iter().any(|node| contains_node(node, selected_obj))
|
||||||
}
|
}
|
||||||
@@ -478,6 +469,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: &ObjectConfig) -> bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_node(
|
fn filter_node(
|
||||||
|
units: &[ProjectObject],
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
search: &str,
|
search: &str,
|
||||||
filter_diffable: bool,
|
filter_diffable: bool,
|
||||||
@@ -485,12 +477,12 @@ fn filter_node(
|
|||||||
show_hidden: bool,
|
show_hidden: bool,
|
||||||
) -> Option<ProjectObjectNode> {
|
) -> Option<ProjectObjectNode> {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::File(name, object) => {
|
ProjectObjectNode::Unit(name, 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
|
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
||||||
|| (object.base_path.is_some() && object.target_path.is_some()))
|
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
|
||||||
&& (!filter_incomplete || matches!(object.complete(), None | Some(false)))
|
&& (show_hidden || !unit.hidden())
|
||||||
&& (show_hidden || !object.hidden())
|
|
||||||
{
|
{
|
||||||
Some(node.clone())
|
Some(node.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -501,7 +493,14 @@ fn filter_node(
|
|||||||
let new_children = children
|
let new_children = children
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|child| {
|
.filter_map(|child| {
|
||||||
filter_node(child, search, filter_diffable, filter_incomplete, show_hidden)
|
filter_node(
|
||||||
|
units,
|
||||||
|
child,
|
||||||
|
search,
|
||||||
|
filter_diffable,
|
||||||
|
filter_incomplete,
|
||||||
|
show_hidden,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if !new_children.is_empty() {
|
if !new_children.is_empty() {
|
||||||
@@ -570,14 +569,14 @@ pub fn project_window(
|
|||||||
split_obj_config_ui(ui, &mut state_guard, config_state, appearance);
|
split_obj_config_ui(ui, &mut state_guard, config_state, appearance);
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(error) = &config_state.load_error {
|
if let Some(error) = &state_guard.config_error {
|
||||||
let mut open = true;
|
let mut open = true;
|
||||||
egui::Window::new("Error").open(&mut open).show(ctx, |ui| {
|
egui::Window::new("Error").open(&mut open).show(ctx, |ui| {
|
||||||
ui.label("Failed to load project config:");
|
ui.label("Failed to load project config:");
|
||||||
ui.colored_label(appearance.delete_color, error);
|
ui.colored_label(appearance.delete_color, error);
|
||||||
});
|
});
|
||||||
if !open {
|
if !open {
|
||||||
config_state.load_error = None;
|
state_guard.config_error = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::{cmp::min, default::Default, mem::take};
|
use std::{cmp::min, default::Default, mem::take};
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2, Widget};
|
use egui::{text::LayoutJob, Id, Label, RichText, Sense, Widget};
|
||||||
use egui_extras::{Column, TableBuilder};
|
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDiff},
|
diff::{ObjDataDiff, ObjDataDiffKind, ObjDiff},
|
||||||
obj::ObjInfo,
|
obj::ObjInfo,
|
||||||
@@ -10,14 +9,15 @@ use time::format_description;
|
|||||||
|
|
||||||
use crate::views::{
|
use crate::views::{
|
||||||
appearance::Appearance,
|
appearance::Appearance,
|
||||||
symbol_diff::{DiffViewState, SymbolRefByName, View},
|
column_layout::{render_header, render_table},
|
||||||
|
symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState},
|
||||||
write_text,
|
write_text,
|
||||||
};
|
};
|
||||||
|
|
||||||
const BYTES_PER_ROW: usize = 16;
|
const BYTES_PER_ROW: usize = 16;
|
||||||
|
|
||||||
fn find_section(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<usize> {
|
fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> {
|
||||||
obj.sections.iter().position(|section| section.name == selected_symbol.section_name)
|
obj.sections.iter().position(|section| section.name == section_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
||||||
@@ -131,20 +131,37 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
|||||||
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(
|
fn data_table_ui(
|
||||||
table: TableBuilder<'_>,
|
ui: &mut egui::Ui,
|
||||||
left_obj: Option<&(ObjInfo, ObjDiff)>,
|
available_width: f32,
|
||||||
right_obj: Option<&(ObjInfo, ObjDiff)>,
|
left_ctx: Option<SectionDiffContext<'_>>,
|
||||||
selected_symbol: &SymbolRefByName,
|
right_ctx: Option<SectionDiffContext<'_>>,
|
||||||
config: &Appearance,
|
config: &Appearance,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let left_section = left_obj.and_then(|(obj, diff)| {
|
let left_section = left_ctx
|
||||||
find_section(obj, selected_symbol).map(|i| (&obj.sections[i], &diff.sections[i]))
|
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
||||||
});
|
let right_section = right_ctx
|
||||||
let right_section = right_obj.and_then(|(obj, diff)| {
|
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
||||||
find_section(obj, selected_symbol).map(|i| (&obj.sections[i], &diff.sections[i]))
|
|
||||||
});
|
|
||||||
|
|
||||||
let total_bytes = left_section
|
let total_bytes = left_section
|
||||||
.or(right_section)?
|
.or(right_section)?
|
||||||
.1
|
.1
|
||||||
@@ -159,72 +176,78 @@ fn data_table_ui(
|
|||||||
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||||
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||||
|
|
||||||
table.body(|body| {
|
render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| {
|
||||||
body.rows(config.code_font.size, total_rows, |mut row| {
|
let i = row.index();
|
||||||
let row_index = row.index();
|
let address = i * BYTES_PER_ROW;
|
||||||
let address = row_index * BYTES_PER_ROW;
|
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
|
if column == 0 {
|
||||||
if let Some(left_diffs) = &left_diffs {
|
if let Some(left_diffs) = &left_diffs {
|
||||||
data_row_ui(ui, address, &left_diffs[row_index], config);
|
data_row_ui(ui, address, &left_diffs[i], config);
|
||||||
}
|
}
|
||||||
});
|
} else if column == 1 {
|
||||||
row.col(|ui| {
|
|
||||||
if let Some(right_diffs) = &right_diffs {
|
if let Some(right_diffs) = &right_diffs {
|
||||||
data_row_ui(ui, address, &right_diffs[row_index], config);
|
data_row_ui(ui, address, &right_diffs[i], config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
#[must_use]
|
||||||
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
pub fn data_diff_ui(
|
||||||
else {
|
ui: &mut egui::Ui,
|
||||||
return;
|
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.map_or(false, |ctx| ctx.has_section())
|
||||||
|
&& !left_ctx.map_or(false, |ctx| ctx.has_section())
|
||||||
|
{
|
||||||
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
|
}
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let column_width = available_width / 2.0;
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
ui.allocate_ui_with_layout(
|
if column == 0 {
|
||||||
Vec2 { x: available_width, y: 100.0 },
|
|
||||||
Layout::left_to_right(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
|
||||||
|
|
||||||
// Left column
|
// Left column
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() {
|
||||||
state.current_view = View::SymbolDiff;
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.scope(|ui| {
|
if let Some(section) =
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
left_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
||||||
ui.colored_label(appearance.highlight_color, &selected_symbol.symbol_name);
|
|
||||||
ui.label("Diff target:");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right column
|
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui
|
|
||||||
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
|
||||||
.clicked()
|
|
||||||
{
|
{
|
||||||
state.queue_build = true;
|
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.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
@@ -232,45 +255,38 @@ pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &A
|
|||||||
ui.colored_label(appearance.replace_color, "Building…");
|
ui.colored_label(appearance.replace_color, "Building…");
|
||||||
} else {
|
} else {
|
||||||
ui.label("Last built:");
|
ui.label("Last built:");
|
||||||
let format =
|
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
ui.label(
|
||||||
result
|
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
||||||
.time
|
|
||||||
.to_offset(appearance.utc_offset)
|
|
||||||
.format(&format)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.scope(|ui| {
|
if let Some(section) =
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
right_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i]))
|
||||||
ui.label("");
|
{
|
||||||
ui.label("Diff base:");
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
ui.style_mut().interaction.selectable_labels = false;
|
let id =
|
||||||
let available_height = ui.available_height();
|
Id::new(state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()))
|
||||||
let table = TableBuilder::new(ui)
|
.with(state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()));
|
||||||
.striped(false)
|
ui.push_id(id, |ui| {
|
||||||
.cell_layout(Layout::left_to_right(Align::Min))
|
data_table_ui(ui, available_width, left_ctx, right_ctx, appearance);
|
||||||
.columns(Column::exact(column_width).clip(true), 2)
|
});
|
||||||
.resizable(false)
|
ret
|
||||||
.auto_shrink([false, false])
|
|
||||||
.min_scrolled_height(available_height);
|
|
||||||
data_table_ui(
|
|
||||||
table,
|
|
||||||
result.first_obj.as_ref(),
|
|
||||||
result.second_obj.as_ref(),
|
|
||||||
selected_symbol,
|
|
||||||
appearance,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
use egui::{Align, Layout, ScrollArea, Ui, Vec2};
|
use egui::{RichText, ScrollArea};
|
||||||
use egui_extras::{Size, StripBuilder};
|
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ppc::ExceptionInfo,
|
arch::ppc::ExceptionInfo,
|
||||||
diff::ObjDiff,
|
obj::{ObjInfo, ObjSymbol},
|
||||||
obj::{ObjInfo, ObjSymbol, SymbolRef},
|
|
||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::views::{
|
use crate::views::{
|
||||||
appearance::Appearance,
|
appearance::Appearance,
|
||||||
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolRefByName, View},
|
column_layout::{render_header, render_strips},
|
||||||
|
function_diff::FunctionDiffContext,
|
||||||
|
symbol_diff::{
|
||||||
|
match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState, SymbolRefByName,
|
||||||
|
View,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> {
|
|
||||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
|
||||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
|
||||||
if symbol.name == selected_symbol.symbol_name {
|
|
||||||
return Some(SymbolRef { section_idx, symbol_idx });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_extab(extab: &ExceptionInfo) -> String {
|
fn decode_extab(extab: &ExceptionInfo) -> String {
|
||||||
let mut text = String::from("");
|
let mut text = String::from("");
|
||||||
|
|
||||||
@@ -48,14 +40,12 @@ fn find_extab_entry<'a>(obj: &'a ObjInfo, symbol: &ObjSymbol) -> Option<&'a Exce
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extab_text_ui(
|
fn extab_text_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut egui::Ui,
|
||||||
obj: &(ObjInfo, ObjDiff),
|
ctx: FunctionDiffContext<'_>,
|
||||||
symbol_ref: SymbolRef,
|
symbol: &ObjSymbol,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let (_section, symbol) = obj.0.section_symbol(symbol_ref);
|
if let Some(extab_entry) = find_extab_entry(ctx.obj, symbol) {
|
||||||
|
|
||||||
if let Some(extab_entry) = find_extab_entry(&obj.0, symbol) {
|
|
||||||
let text = decode_extab(extab_entry);
|
let text = decode_extab(extab_entry);
|
||||||
ui.colored_label(appearance.replace_color, &text);
|
ui.colored_label(appearance.replace_color, &text);
|
||||||
return Some(());
|
return Some(());
|
||||||
@@ -65,79 +55,131 @@ fn extab_text_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extab_ui(
|
fn extab_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut egui::Ui,
|
||||||
obj: Option<&(ObjInfo, ObjDiff)>,
|
ctx: FunctionDiffContext<'_>,
|
||||||
selected_symbol: &SymbolRefByName,
|
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
_left: bool,
|
_column: usize,
|
||||||
) {
|
) {
|
||||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
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::Extend);
|
||||||
|
|
||||||
let symbol = obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
if let Some((_section, symbol)) =
|
||||||
|
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))
|
||||||
if let (Some(object), Some(symbol_ref)) = (obj, symbol) {
|
{
|
||||||
extab_text_ui(ui, object, symbol_ref, appearance);
|
extab_text_ui(ui, ctx, symbol, appearance);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extab_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
#[must_use]
|
||||||
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
pub fn extab_diff_ui(
|
||||||
else {
|
ui: &mut egui::Ui,
|
||||||
return;
|
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.map_or(false, |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.map_or(false, |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.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
|
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
|
{
|
||||||
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
|
}
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let column_width = available_width / 2.0;
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
ui.allocate_ui_with_layout(
|
if column == 0 {
|
||||||
Vec2 { x: available_width, y: 100.0 },
|
|
||||||
Layout::left_to_right(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
|
||||||
|
|
||||||
// Left column
|
// Left column
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() {
|
||||||
state.current_view = View::SymbolDiff;
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
!state.scratch_running
|
||||||
|
&& state.scratch_available
|
||||||
|
&& left_ctx.map_or(false, |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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let name = selected_symbol
|
if let Some((_section, symbol)) = left_ctx
|
||||||
.demangled_symbol_name
|
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
||||||
.as_deref()
|
|
||||||
.unwrap_or(&selected_symbol.symbol_name);
|
|
||||||
ui.scope(|ui| {
|
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
|
||||||
ui.colored_label(appearance.highlight_color, name);
|
|
||||||
ui.label("Diff target:");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right column
|
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui
|
|
||||||
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
|
||||||
.clicked()
|
|
||||||
{
|
{
|
||||||
state.queue_build = true;
|
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.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
@@ -145,57 +187,62 @@ pub fn extab_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &
|
|||||||
ui.colored_label(appearance.replace_color, "Building…");
|
ui.colored_label(appearance.replace_color, "Building…");
|
||||||
} else {
|
} else {
|
||||||
ui.label("Last built:");
|
ui.label("Last built:");
|
||||||
let format =
|
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
ui.label(
|
||||||
result
|
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
||||||
.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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.scope(|ui| {
|
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ctx.symbol_ref.map(|symbol_ref| {
|
||||||
if let Some(match_percent) = result
|
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
||||||
.second_obj
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|(obj, diff)| {
|
|
||||||
find_symbol(obj, selected_symbol).map(|sref| {
|
|
||||||
&diff.sections[sref.section_idx].symbols[sref.symbol_idx]
|
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.and_then(|symbol| symbol.match_percent)
|
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
||||||
{
|
ui.label(
|
||||||
ui.colored_label(
|
RichText::new(name)
|
||||||
match_color_for_symbol(match_percent, appearance),
|
.font(appearance.code_font.clone())
|
||||||
format!("{match_percent:.0}%"),
|
.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.colored_label(appearance.replace_color, "Missing");
|
|
||||||
}
|
}
|
||||||
ui.label("Diff base:");
|
} else {
|
||||||
|
ui.label(
|
||||||
|
RichText::new("Missing")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.replace_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
|
render_strips(ui, available_width, 2, |ui, column| {
|
||||||
strip.strip(|builder| {
|
if column == 0 {
|
||||||
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
if let Some(ctx) = left_ctx {
|
||||||
strip.cell(|ui| {
|
extab_ui(ui, ctx, appearance, column);
|
||||||
extab_ui(ui, result.first_obj.as_ref(), selected_symbol, appearance, true);
|
}
|
||||||
});
|
} else if column == 1 {
|
||||||
strip.cell(|ui| {
|
if let Some(ctx) = right_ctx {
|
||||||
extab_ui(ui, result.second_obj.as_ref(), selected_symbol, appearance, false);
|
extab_ui(ui, ctx, appearance, column);
|
||||||
});
|
}
|
||||||
});
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
||||||
use egui::{text::LayoutJob, Align, Label, Layout, Response, Sense, Vec2, Widget};
|
use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget};
|
||||||
use egui_extras::{Column, TableBuilder, TableRow};
|
use egui_extras::TableRow;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ObjArch,
|
|
||||||
diff::{
|
diff::{
|
||||||
display::{display_diff, DiffText, HighlightKind},
|
display::{display_diff, DiffText, HighlightKind},
|
||||||
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
ObjDiff, ObjInsDiff, ObjInsDiffKind,
|
||||||
},
|
},
|
||||||
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSymbol, SymbolRef},
|
obj::{
|
||||||
|
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSectionKind, ObjSymbol,
|
||||||
|
SymbolRef,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use time::format_description;
|
use time::format_description;
|
||||||
|
|
||||||
use crate::views::{
|
use crate::views::{
|
||||||
appearance::Appearance,
|
appearance::Appearance,
|
||||||
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolRefByName, View},
|
column_layout::{render_header, render_strips, render_table},
|
||||||
|
symbol_diff::{
|
||||||
|
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, DiffViewState,
|
||||||
|
SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
|
||||||
enum ColumnId {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FunctionViewState {
|
pub struct FunctionViewState {
|
||||||
left_highlight: HighlightKind,
|
left_highlight: HighlightKind,
|
||||||
@@ -30,16 +30,17 @@ pub struct FunctionViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FunctionViewState {
|
impl FunctionViewState {
|
||||||
fn highlight(&self, column: ColumnId) -> &HighlightKind {
|
pub fn highlight(&self, column: usize) -> &HighlightKind {
|
||||||
match column {
|
match column {
|
||||||
ColumnId::Left => &self.left_highlight,
|
0 => &self.left_highlight,
|
||||||
ColumnId::Right => &self.right_highlight,
|
1 => &self.right_highlight,
|
||||||
|
_ => &HighlightKind::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_highlight(&mut self, column: ColumnId, highlight: HighlightKind) {
|
pub fn set_highlight(&mut self, column: usize, highlight: HighlightKind) {
|
||||||
match column {
|
match column {
|
||||||
ColumnId::Left => {
|
0 => {
|
||||||
if highlight == self.left_highlight {
|
if highlight == self.left_highlight {
|
||||||
if highlight == self.right_highlight {
|
if highlight == self.right_highlight {
|
||||||
self.left_highlight = HighlightKind::None;
|
self.left_highlight = HighlightKind::None;
|
||||||
@@ -51,7 +52,7 @@ impl FunctionViewState {
|
|||||||
self.left_highlight = highlight;
|
self.left_highlight = highlight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColumnId::Right => {
|
1 => {
|
||||||
if highlight == self.right_highlight {
|
if highlight == self.right_highlight {
|
||||||
if highlight == self.left_highlight {
|
if highlight == self.left_highlight {
|
||||||
self.left_highlight = HighlightKind::None;
|
self.left_highlight = HighlightKind::None;
|
||||||
@@ -63,13 +64,19 @@ impl FunctionViewState {
|
|||||||
self.right_highlight = highlight;
|
self.right_highlight = highlight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_highlight(&mut self) {
|
||||||
|
self.left_highlight = HighlightKind::None;
|
||||||
|
self.right_highlight = HighlightKind::None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ins_hover_ui(
|
fn ins_hover_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
arch: &dyn ObjArch,
|
obj: &ObjInfo,
|
||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
ins: &ObjIns,
|
ins: &ObjIns,
|
||||||
symbol: &ObjSymbol,
|
symbol: &ObjSymbol,
|
||||||
@@ -112,10 +119,17 @@ fn ins_hover_ui(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(reloc) = &ins.reloc {
|
if let Some(reloc) = &ins.reloc {
|
||||||
ui.label(format!("Relocation type: {}", arch.display_reloc(reloc.flags)));
|
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
|
||||||
ui.colored_label(appearance.highlight_color, format!("Name: {}", reloc.target.name));
|
ui.colored_label(appearance.highlight_color, format!("Name: {}", reloc.target.name));
|
||||||
if let Some(section) = &reloc.target_section {
|
if let Some(orig_section_index) = reloc.target.orig_section_index {
|
||||||
ui.colored_label(appearance.highlight_color, format!("Section: {section}"));
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
ui.colored_label(
|
ui.colored_label(
|
||||||
appearance.highlight_color,
|
appearance.highlight_color,
|
||||||
format!("Address: {:x}", reloc.target.address),
|
format!("Address: {:x}", reloc.target.address),
|
||||||
@@ -124,9 +138,10 @@ fn ins_hover_ui(
|
|||||||
appearance.highlight_color,
|
appearance.highlight_color,
|
||||||
format!("Size: {:x}", reloc.target.size),
|
format!("Size: {:x}", reloc.target.size),
|
||||||
);
|
);
|
||||||
if let Some(s) = arch
|
if let Some(s) = obj
|
||||||
|
.arch
|
||||||
.guess_data_type(ins)
|
.guess_data_type(ins)
|
||||||
.and_then(|ty| arch.display_data_type(ty, &reloc.target.bytes))
|
.and_then(|ty| obj.arch.display_data_type(ty, &reloc.target.bytes))
|
||||||
{
|
{
|
||||||
ui.colored_label(appearance.highlight_color, s);
|
ui.colored_label(appearance.highlight_color, s);
|
||||||
}
|
}
|
||||||
@@ -218,17 +233,19 @@ fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<Symbo
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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<'_>,
|
text: DiffText<'_>,
|
||||||
ins_diff: &ObjInsDiff,
|
ins_diff: &ObjInsDiff,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &mut FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
column: ColumnId,
|
column: usize,
|
||||||
space_width: f32,
|
space_width: f32,
|
||||||
response_cb: impl Fn(Response) -> Response,
|
response_cb: impl Fn(Response) -> Response,
|
||||||
) {
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
let label_text;
|
let label_text;
|
||||||
let mut base_color = match ins_diff.kind {
|
let mut base_color = match ins_diff.kind {
|
||||||
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
|
||||||
@@ -282,7 +299,7 @@ fn diff_text_ui(
|
|||||||
}
|
}
|
||||||
DiffText::Spacing(n) => {
|
DiffText::Spacing(n) => {
|
||||||
ui.add_space(n as f32 * space_width);
|
ui.add_space(n as f32 * space_width);
|
||||||
return;
|
return ret;
|
||||||
}
|
}
|
||||||
DiffText::Eol => {
|
DiffText::Eol => {
|
||||||
label_text = "\n".to_string();
|
label_text = "\n".to_string();
|
||||||
@@ -299,22 +316,25 @@ fn diff_text_ui(
|
|||||||
.ui(ui);
|
.ui(ui);
|
||||||
response = response_cb(response);
|
response = response_cb(response);
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
ins_view_state.set_highlight(column, text.into());
|
ret = Some(DiffViewAction::SetDiffHighlight(column, text.into()));
|
||||||
}
|
}
|
||||||
if len < pad_to {
|
if len < pad_to {
|
||||||
ui.add_space((pad_to - len) as f32 * space_width);
|
ui.add_space((pad_to - len) as f32 * space_width);
|
||||||
}
|
}
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
fn asm_row_ui(
|
fn asm_row_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
ins_diff: &ObjInsDiff,
|
ins_diff: &ObjInsDiff,
|
||||||
symbol: &ObjSymbol,
|
symbol: &ObjSymbol,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &mut FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
column: ColumnId,
|
column: usize,
|
||||||
response_cb: impl Fn(Response) -> Response,
|
response_cb: impl Fn(Response) -> Response,
|
||||||
) {
|
) -> Option<DiffViewAction> {
|
||||||
|
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 != ObjInsDiffKind::None {
|
||||||
@@ -322,7 +342,7 @@ fn asm_row_ui(
|
|||||||
}
|
}
|
||||||
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_diff(ins_diff, symbol.address, |text| {
|
||||||
diff_text_ui(
|
if let Some(action) = diff_text_ui(
|
||||||
ui,
|
ui,
|
||||||
text,
|
text,
|
||||||
ins_diff,
|
ins_diff,
|
||||||
@@ -331,166 +351,385 @@ fn asm_row_ui(
|
|||||||
column,
|
column,
|
||||||
space_width,
|
space_width,
|
||||||
&response_cb,
|
&response_cb,
|
||||||
);
|
) {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
Ok::<_, ()>(())
|
Ok::<_, ()>(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
fn asm_col_ui(
|
fn asm_col_ui(
|
||||||
row: &mut TableRow<'_, '_>,
|
row: &mut TableRow<'_, '_>,
|
||||||
obj: &(ObjInfo, ObjDiff),
|
ctx: FunctionDiffContext<'_>,
|
||||||
symbol_ref: SymbolRef,
|
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &mut FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
column: ColumnId,
|
column: usize,
|
||||||
) {
|
) -> Option<DiffViewAction> {
|
||||||
let (section, symbol) = obj.0.section_symbol(symbol_ref);
|
let mut ret = None;
|
||||||
let section = section.unwrap();
|
let symbol_ref = ctx.symbol_ref?;
|
||||||
let ins_diff = &obj.1.symbol_diff(symbol_ref).instructions[row.index()];
|
let (section, symbol) = ctx.obj.section_symbol(symbol_ref);
|
||||||
|
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) = &ins_diff.ins {
|
||||||
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
|
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
|
||||||
response.on_hover_ui_at_pointer(|ui| {
|
response.on_hover_ui_at_pointer(|ui| {
|
||||||
ins_hover_ui(ui, obj.0.arch.as_ref(), section, ins, symbol, appearance)
|
ins_hover_ui(ui, ctx.obj, section, ins, symbol, appearance)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (_, response) = row.col(|ui| {
|
let (_, response) = row.col(|ui| {
|
||||||
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb);
|
if let Some(action) =
|
||||||
|
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb)
|
||||||
|
{
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
response_cb(response);
|
response_cb(response);
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_col_ui(row: &mut TableRow<'_, '_>) {
|
#[must_use]
|
||||||
row.col(|ui| {
|
|
||||||
ui.label("");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn asm_table_ui(
|
fn asm_table_ui(
|
||||||
table: TableBuilder<'_>,
|
ui: &mut egui::Ui,
|
||||||
left_obj: Option<&(ObjInfo, ObjDiff)>,
|
available_width: f32,
|
||||||
right_obj: Option<&(ObjInfo, ObjDiff)>,
|
left_ctx: Option<FunctionDiffContext<'_>>,
|
||||||
selected_symbol: &SymbolRefByName,
|
right_ctx: Option<FunctionDiffContext<'_>>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
ins_view_state: &mut FunctionViewState,
|
ins_view_state: &FunctionViewState,
|
||||||
) -> Option<()> {
|
symbol_state: &SymbolViewState,
|
||||||
let left_symbol = left_obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
) -> Option<DiffViewAction> {
|
||||||
let right_symbol = right_obj.and_then(|(obj, _)| find_symbol(obj, selected_symbol));
|
let mut ret = None;
|
||||||
let instructions_len = match (left_symbol, right_symbol) {
|
let left_len = left_ctx.and_then(|ctx| {
|
||||||
(Some(left_symbol_ref), Some(right_symbol_ref)) => {
|
ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len())
|
||||||
let left_len = left_obj.unwrap().1.symbol_diff(left_symbol_ref).instructions.len();
|
});
|
||||||
let right_len = right_obj.unwrap().1.symbol_diff(right_symbol_ref).instructions.len();
|
let right_len = right_ctx.and_then(|ctx| {
|
||||||
debug_assert_eq!(left_len, right_len);
|
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
|
left_len
|
||||||
}
|
}
|
||||||
(Some(left_symbol_ref), None) => {
|
(Some(left_len), None) => left_len,
|
||||||
left_obj.unwrap().1.symbol_diff(left_symbol_ref).instructions.len()
|
(None, Some(right_len)) => right_len,
|
||||||
|
(None, None) => {
|
||||||
|
ui.label("No symbol selected");
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
(None, Some(right_symbol_ref)) => {
|
|
||||||
right_obj.unwrap().1.symbol_diff(right_symbol_ref).instructions.len()
|
|
||||||
}
|
|
||||||
(None, None) => return None,
|
|
||||||
};
|
};
|
||||||
table.body(|body| {
|
if left_len.is_some() && right_len.is_some() {
|
||||||
body.rows(appearance.code_font.size, instructions_len, |mut row| {
|
// Joint view
|
||||||
if let (Some(left_obj), Some(left_symbol_ref)) = (left_obj, left_symbol) {
|
render_table(
|
||||||
asm_col_ui(
|
ui,
|
||||||
&mut row,
|
available_width,
|
||||||
left_obj,
|
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() {
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
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,
|
left_symbol_ref,
|
||||||
appearance,
|
SymbolRefByName::new(right_symbol, right_section),
|
||||||
ins_view_state,
|
));
|
||||||
ColumnId::Left,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
empty_col_ui(&mut row);
|
|
||||||
}
|
}
|
||||||
if let (Some(right_obj), Some(right_symbol_ref)) = (right_obj, right_symbol) {
|
DiffViewAction::SetSymbolHighlight(_, _) => {
|
||||||
asm_col_ui(
|
// Ignore
|
||||||
&mut row,
|
}
|
||||||
right_obj,
|
_ => {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ui.label("No left object");
|
||||||
|
}
|
||||||
|
} else if column == 1 {
|
||||||
|
if let Some(ctx) = right_ctx {
|
||||||
|
if ctx.has_symbol() {
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
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,
|
right_symbol_ref,
|
||||||
appearance,
|
));
|
||||||
ins_view_state,
|
}
|
||||||
ColumnId::Right,
|
DiffViewAction::SetSymbolHighlight(_, _) => {
|
||||||
);
|
// Ignore
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
empty_col_ui(&mut row);
|
ui.label("No right object");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
Some(())
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
#[derive(Clone, Copy)]
|
||||||
let (Some(result), Some(selected_symbol)) = (&state.build, &state.symbol_state.selected_symbol)
|
pub struct FunctionDiffContext<'a> {
|
||||||
else {
|
pub obj: &'a ObjInfo,
|
||||||
return;
|
pub diff: &'a ObjDiff,
|
||||||
|
pub symbol_ref: Option<SymbolRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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.map_or(false, |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.map_or(false, |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.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
|
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
|
||||||
|
{
|
||||||
|
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
|
}
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let column_width = available_width / 2.0;
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
ui.allocate_ui_with_layout(
|
if column == 0 {
|
||||||
Vec2 { x: available_width, y: 100.0 },
|
|
||||||
Layout::left_to_right(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
|
||||||
|
|
||||||
// Left column
|
// Left column
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("⏴ Back").clicked() {
|
if ui.button("⏴ Back").clicked() {
|
||||||
state.current_view = View::SymbolDiff;
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui
|
if ui
|
||||||
.add_enabled(
|
.add_enabled(
|
||||||
!state.scratch_running && state.scratch_available,
|
!state.scratch_running
|
||||||
|
&& state.scratch_available
|
||||||
|
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
|
||||||
egui::Button::new("📲 decomp.me"),
|
egui::Button::new("📲 decomp.me"),
|
||||||
)
|
)
|
||||||
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
|
||||||
.on_disabled_hover_text("Scratch configuration missing")
|
.on_disabled_hover_text("Scratch configuration missing")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
state.queue_scratch = true;
|
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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let name = selected_symbol
|
if let Some((_section, symbol)) = left_ctx
|
||||||
.demangled_symbol_name
|
.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)))
|
||||||
.as_deref()
|
{
|
||||||
.unwrap_or(&selected_symbol.symbol_name);
|
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
||||||
ui.scope(|ui| {
|
ui.label(
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
RichText::new(name)
|
||||||
ui.colored_label(appearance.highlight_color, name);
|
.font(appearance.code_font.clone())
|
||||||
ui.label("Diff target:");
|
.color(appearance.highlight_color),
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
if right_ctx.map_or(false, |m| m.has_symbol())
|
||||||
// Right column
|
&& ui
|
||||||
ui.allocate_ui_with_layout(
|
.button("Change target")
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if ui
|
|
||||||
.add_enabled(!state.build_running, egui::Button::new("Build"))
|
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
state.queue_build = true;
|
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.label(
|
||||||
|
RichText::new("Choose target symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_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.scope(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||||
@@ -498,74 +737,90 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
|
|||||||
ui.colored_label(appearance.replace_color, "Building…");
|
ui.colored_label(appearance.replace_color, "Building…");
|
||||||
} else {
|
} else {
|
||||||
ui.label("Last built:");
|
ui.label("Last built:");
|
||||||
let format =
|
let format = format_description::parse("[hour]:[minute]:[second]").unwrap();
|
||||||
format_description::parse("[hour]:[minute]:[second]").unwrap();
|
|
||||||
ui.label(
|
ui.label(
|
||||||
result
|
result.time.to_offset(appearance.utc_offset).format(&format).unwrap(),
|
||||||
.time
|
|
||||||
.to_offset(appearance.utc_offset)
|
|
||||||
.format(&format)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui
|
if ui
|
||||||
.add_enabled(
|
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
||||||
state.source_path_available,
|
|
||||||
egui::Button::new("🖹 Source file"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
state.queue_open_source_path = true;
|
ret = Some(DiffViewAction::OpenSourcePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.scope(|ui| {
|
if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
ctx.symbol_ref.map(|symbol_ref| {
|
||||||
if let Some(match_percent) = result
|
(ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref))
|
||||||
.second_obj
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|(obj, diff)| {
|
|
||||||
find_symbol(obj, selected_symbol).map(|sref| {
|
|
||||||
&diff.sections[sref.section_idx].symbols[sref.symbol_idx]
|
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.and_then(|symbol| symbol.match_percent)
|
let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name);
|
||||||
{
|
ui.label(
|
||||||
ui.colored_label(
|
RichText::new(name)
|
||||||
match_color_for_symbol(match_percent, appearance),
|
.font(appearance.code_font.clone())
|
||||||
format!("{match_percent:.0}%"),
|
.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)),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
|
||||||
}
|
}
|
||||||
ui.label("Diff base:");
|
if left_ctx.map_or(false, |m| m.has_symbol()) {
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.button("Change base")
|
||||||
|
.on_hover_text_at_pointer(
|
||||||
|
"Choose a different symbol to use as the base",
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
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.label(
|
||||||
|
RichText::new("Choose base symbol")
|
||||||
|
.font(appearance.code_font.clone())
|
||||||
|
.color(appearance.highlight_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
ui.style_mut().interaction.selectable_labels = false;
|
let id = Id::new(state.symbol_state.left_symbol.as_ref().map(|s| s.symbol_name.as_str()))
|
||||||
let available_height = ui.available_height();
|
.with(state.symbol_state.right_symbol.as_ref().map(|s| s.symbol_name.as_str()));
|
||||||
let table = TableBuilder::new(ui)
|
if let Some(action) = ui
|
||||||
.striped(false)
|
.push_id(id, |ui| {
|
||||||
.cell_layout(Layout::left_to_right(Align::Min))
|
|
||||||
.columns(Column::exact(column_width).clip(true), 2)
|
|
||||||
.resizable(false)
|
|
||||||
.auto_shrink([false, false])
|
|
||||||
.min_scrolled_height(available_height);
|
|
||||||
asm_table_ui(
|
asm_table_ui(
|
||||||
table,
|
ui,
|
||||||
result.first_obj.as_ref(),
|
available_width,
|
||||||
result.second_obj.as_ref(),
|
left_ctx,
|
||||||
selected_symbol,
|
right_ctx,
|
||||||
appearance,
|
appearance,
|
||||||
&mut state.function_state,
|
&state.function_state,
|
||||||
);
|
&state.symbol_state,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.inner
|
||||||
|
{
|
||||||
|
ret = Some(action);
|
||||||
|
}
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use egui::{text::LayoutJob, Color32, FontId, TextFormat};
|
use egui::{text::LayoutJob, Color32, FontId, TextFormat};
|
||||||
|
|
||||||
pub(crate) mod appearance;
|
pub(crate) mod appearance;
|
||||||
|
pub(crate) mod column_layout;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
pub(crate) mod data_diff;
|
pub(crate) mod data_diff;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
use std::mem::take;
|
use std::{collections::BTreeMap, mem::take};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
text::LayoutJob, Align, CollapsingHeader, Color32, Id, Layout, OpenUrl, ScrollArea,
|
text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, SelectableLabel, TextEdit,
|
||||||
SelectableLabel, TextEdit, Ui, Vec2, Widget,
|
Ui, Widget,
|
||||||
};
|
};
|
||||||
use egui_extras::{Size, StripBuilder};
|
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
arch::ObjArch,
|
arch::ObjArch,
|
||||||
diff::{ObjDiff, ObjSymbolDiff},
|
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
||||||
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
obj::{
|
||||||
|
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef, SECTION_COMMON,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
|
|
||||||
@@ -19,17 +20,28 @@ use crate::{
|
|||||||
objdiff::{BuildStatus, ObjDiffResult},
|
objdiff::{BuildStatus, ObjDiffResult},
|
||||||
Job, JobQueue, JobResult,
|
Job, JobQueue, JobResult,
|
||||||
},
|
},
|
||||||
views::{appearance::Appearance, function_diff::FunctionViewState, write_text},
|
views::{
|
||||||
|
appearance::Appearance,
|
||||||
|
column_layout::{render_header, render_strips},
|
||||||
|
function_diff::FunctionViewState,
|
||||||
|
write_text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct SymbolRefByName {
|
pub struct SymbolRefByName {
|
||||||
pub symbol_name: String,
|
pub symbol_name: String,
|
||||||
pub demangled_symbol_name: Option<String>,
|
pub section_name: Option<String>,
|
||||||
pub section_name: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
impl SymbolRefByName {
|
||||||
#[derive(Default, Eq, PartialEq, Copy, Clone)]
|
pub fn new(symbol: &ObjSymbol, section: Option<&ObjSection>) -> Self {
|
||||||
|
Self { symbol_name: symbol.name.clone(), section_name: section.map(|s| s.name.clone()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::enum_variant_names)]
|
||||||
|
#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash)]
|
||||||
pub enum View {
|
pub enum View {
|
||||||
#[default]
|
#[default]
|
||||||
SymbolDiff,
|
SymbolDiff,
|
||||||
@@ -38,6 +50,71 @@ pub enum View {
|
|||||||
ExtabDiff,
|
ExtabDiff,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DiffViewAction {
|
||||||
|
/// Queue a rebuild of the current object(s)
|
||||||
|
Build,
|
||||||
|
/// Navigate to a new diff view
|
||||||
|
Navigate(DiffViewNavigation),
|
||||||
|
/// Set the highlighted symbols in the symbols view
|
||||||
|
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>),
|
||||||
|
/// Set the symbols view search filter
|
||||||
|
SetSearch(String),
|
||||||
|
/// Submit the current function to decomp.me
|
||||||
|
CreateScratch(String),
|
||||||
|
/// Open the source path of the current object
|
||||||
|
OpenSourcePath,
|
||||||
|
/// Set the highlight for a diff column
|
||||||
|
SetDiffHighlight(usize, HighlightKind),
|
||||||
|
/// Clear the highlight for all diff columns
|
||||||
|
ClearDiffHighlight,
|
||||||
|
/// Start selecting a left symbol for mapping.
|
||||||
|
/// The symbol reference is the right symbol to map to.
|
||||||
|
SelectingLeft(SymbolRefByName),
|
||||||
|
/// Start selecting a right symbol for mapping.
|
||||||
|
/// The symbol reference is the left symbol to map to.
|
||||||
|
SelectingRight(SymbolRefByName),
|
||||||
|
/// Set a symbol mapping.
|
||||||
|
SetMapping(View, SymbolRefByName, SymbolRefByName),
|
||||||
|
/// Set the show_mapped_symbols flag
|
||||||
|
SetShowMappedSymbols(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct DiffViewNavigation {
|
||||||
|
pub view: Option<View>,
|
||||||
|
pub left_symbol: Option<SymbolRefByName>,
|
||||||
|
pub right_symbol: Option<SymbolRefByName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffViewNavigation {
|
||||||
|
pub fn symbol_diff() -> Self {
|
||||||
|
Self { view: Some(View::SymbolDiff), left_symbol: None, right_symbol: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_symbols(
|
||||||
|
view: View,
|
||||||
|
other_ctx: Option<SymbolDiffContext<'_>>,
|
||||||
|
symbol: &ObjSymbol,
|
||||||
|
section: &ObjSection,
|
||||||
|
symbol_diff: &ObjSymbolDiff,
|
||||||
|
column: usize,
|
||||||
|
) -> Self {
|
||||||
|
let symbol1 = Some(SymbolRefByName::new(symbol, Some(section)));
|
||||||
|
let symbol2 = symbol_diff.target_symbol.and_then(|symbol_ref| {
|
||||||
|
other_ctx.map(|ctx| {
|
||||||
|
let (section, symbol) = ctx.obj.section_symbol(symbol_ref);
|
||||||
|
SymbolRefByName::new(symbol, section)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
match column {
|
||||||
|
0 => Self { view: Some(view), left_symbol: symbol1, right_symbol: symbol2 },
|
||||||
|
1 => Self { view: Some(view), left_symbol: symbol2, right_symbol: symbol1 },
|
||||||
|
_ => unreachable!("Invalid column index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DiffViewState {
|
pub struct DiffViewState {
|
||||||
pub build: Option<Box<ObjDiffResult>>,
|
pub build: Option<Box<ObjDiffResult>>,
|
||||||
@@ -47,22 +124,23 @@ pub struct DiffViewState {
|
|||||||
pub function_state: FunctionViewState,
|
pub function_state: FunctionViewState,
|
||||||
pub search: String,
|
pub search: String,
|
||||||
pub search_regex: Option<Regex>,
|
pub search_regex: Option<Regex>,
|
||||||
pub queue_build: bool,
|
|
||||||
pub build_running: bool,
|
pub build_running: bool,
|
||||||
pub scratch_available: bool,
|
pub scratch_available: bool,
|
||||||
pub queue_scratch: bool,
|
|
||||||
pub scratch_running: bool,
|
pub scratch_running: bool,
|
||||||
pub source_path_available: bool,
|
pub source_path_available: bool,
|
||||||
pub queue_open_source_path: bool,
|
pub post_build_nav: Option<DiffViewNavigation>,
|
||||||
|
pub object_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SymbolViewState {
|
pub struct SymbolViewState {
|
||||||
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
|
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>),
|
||||||
pub selected_symbol: Option<SymbolRefByName>,
|
pub left_symbol: Option<SymbolRefByName>,
|
||||||
|
pub right_symbol: Option<SymbolRefByName>,
|
||||||
pub reverse_fn_order: bool,
|
pub reverse_fn_order: bool,
|
||||||
pub disable_reverse_fn_order: bool,
|
pub disable_reverse_fn_order: bool,
|
||||||
pub show_hidden_symbols: bool,
|
pub show_hidden_symbols: bool,
|
||||||
|
pub show_mapped_symbols: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiffViewState {
|
impl DiffViewState {
|
||||||
@@ -70,6 +148,16 @@ impl DiffViewState {
|
|||||||
jobs.results.retain_mut(|result| match result {
|
jobs.results.retain_mut(|result| match result {
|
||||||
JobResult::ObjDiff(result) => {
|
JobResult::ObjDiff(result) => {
|
||||||
self.build = take(result);
|
self.build = take(result);
|
||||||
|
|
||||||
|
// TODO: where should this go?
|
||||||
|
if let Some(result) = self.post_build_nav.take() {
|
||||||
|
if let Some(view) = result.view {
|
||||||
|
self.current_view = view;
|
||||||
|
}
|
||||||
|
self.symbol_state.left_symbol = result.left_symbol;
|
||||||
|
self.symbol_state.right_symbol = result.right_symbol;
|
||||||
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
JobResult::CreateScratch(result) => {
|
JobResult::CreateScratch(result) => {
|
||||||
@@ -93,44 +181,103 @@ impl DiffViewState {
|
|||||||
self.source_path_available = false;
|
self.source_path_available = false;
|
||||||
}
|
}
|
||||||
self.scratch_available = CreateScratchConfig::is_available(&state.config);
|
self.scratch_available = CreateScratchConfig::is_available(&state.config);
|
||||||
|
self.object_name =
|
||||||
|
state.config.selected_obj.as_ref().map(|o| o.name.clone()).unwrap_or_default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, state: &AppStateRef) {
|
pub fn post_update(
|
||||||
|
&mut self,
|
||||||
|
action: Option<DiffViewAction>,
|
||||||
|
ctx: &egui::Context,
|
||||||
|
jobs: &mut JobQueue,
|
||||||
|
state: &AppStateRef,
|
||||||
|
) {
|
||||||
if let Some(result) = take(&mut self.scratch) {
|
if let Some(result) = take(&mut self.scratch) {
|
||||||
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
|
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.queue_build {
|
let Some(action) = action else {
|
||||||
self.queue_build = false;
|
return;
|
||||||
|
};
|
||||||
|
match action {
|
||||||
|
DiffViewAction::Build => {
|
||||||
if let Ok(mut state) = state.write() {
|
if let Ok(mut state) = state.write() {
|
||||||
state.queue_build = true;
|
state.queue_build = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DiffViewAction::Navigate(nav) => {
|
||||||
if self.queue_scratch {
|
if self.post_build_nav.is_some() {
|
||||||
self.queue_scratch = false;
|
// Ignore action if we're already navigating
|
||||||
if let Some(function_name) =
|
return;
|
||||||
self.symbol_state.selected_symbol.as_ref().map(|sym| sym.symbol_name.clone())
|
}
|
||||||
|
self.symbol_state.highlighted_symbol = (None, None);
|
||||||
|
let Ok(mut state) = state.write() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (nav.left_symbol.is_some() && nav.right_symbol.is_some())
|
||||||
|
|| (nav.left_symbol.is_none() && nav.right_symbol.is_none())
|
||||||
|
|| nav.view != Some(View::FunctionDiff)
|
||||||
{
|
{
|
||||||
if let Ok(state) = state.read() {
|
// Regular navigation
|
||||||
|
if state.is_selecting_symbol() {
|
||||||
|
// Cancel selection and reload
|
||||||
|
state.clear_selection();
|
||||||
|
self.post_build_nav = Some(nav);
|
||||||
|
} else {
|
||||||
|
// Navigate immediately
|
||||||
|
if let Some(view) = nav.view {
|
||||||
|
self.current_view = view;
|
||||||
|
}
|
||||||
|
self.symbol_state.left_symbol = nav.left_symbol;
|
||||||
|
self.symbol_state.right_symbol = nav.right_symbol;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Enter selection mode
|
||||||
|
match (&nav.left_symbol, &nav.right_symbol) {
|
||||||
|
(Some(left_ref), None) => {
|
||||||
|
state.set_selecting_right(&left_ref.symbol_name);
|
||||||
|
}
|
||||||
|
(None, Some(right_ref)) => {
|
||||||
|
state.set_selecting_left(&right_ref.symbol_name);
|
||||||
|
}
|
||||||
|
(Some(_), Some(_)) => unreachable!(),
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
}
|
||||||
|
self.post_build_nav = Some(nav);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DiffViewAction::SetSymbolHighlight(left, right) => {
|
||||||
|
self.symbol_state.highlighted_symbol = (left, right);
|
||||||
|
}
|
||||||
|
DiffViewAction::SetSearch(search) => {
|
||||||
|
self.search_regex = if search.is_empty() {
|
||||||
|
None
|
||||||
|
} else if let Ok(regex) = RegexBuilder::new(&search).case_insensitive(true).build()
|
||||||
|
{
|
||||||
|
Some(regex)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.search = search;
|
||||||
|
}
|
||||||
|
DiffViewAction::CreateScratch(function_name) => {
|
||||||
|
let Ok(state) = state.read() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
match CreateScratchConfig::from_config(&state.config, function_name) {
|
match CreateScratchConfig::from_config(&state.config, function_name) {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
jobs.push_once(Job::CreateScratch, || {
|
jobs.push_once(Job::CreateScratch, || start_create_scratch(ctx, config));
|
||||||
start_create_scratch(ctx, config)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("Failed to create scratch config: {err}");
|
log::error!("Failed to create scratch config: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
DiffViewAction::OpenSourcePath => {
|
||||||
}
|
let Ok(state) = state.read() else {
|
||||||
|
return;
|
||||||
if self.queue_open_source_path {
|
};
|
||||||
self.queue_open_source_path = false;
|
|
||||||
if let Ok(state) = state.read() {
|
|
||||||
if let (Some(project_dir), Some(source_path)) = (
|
if let (Some(project_dir), Some(source_path)) = (
|
||||||
&state.config.project_dir,
|
&state.config.project_dir,
|
||||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
|
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
|
||||||
@@ -142,6 +289,67 @@ impl DiffViewState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DiffViewAction::SetDiffHighlight(column, kind) => {
|
||||||
|
self.function_state.set_highlight(column, kind);
|
||||||
|
}
|
||||||
|
DiffViewAction::ClearDiffHighlight => {
|
||||||
|
self.function_state.clear_highlight();
|
||||||
|
}
|
||||||
|
DiffViewAction::SelectingLeft(right_ref) => {
|
||||||
|
if self.post_build_nav.is_some() {
|
||||||
|
// Ignore action if we're already navigating
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(mut state) = state.write() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
state.set_selecting_left(&right_ref.symbol_name);
|
||||||
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
|
view: Some(View::FunctionDiff),
|
||||||
|
left_symbol: None,
|
||||||
|
right_symbol: Some(right_ref),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DiffViewAction::SelectingRight(left_ref) => {
|
||||||
|
if self.post_build_nav.is_some() {
|
||||||
|
// Ignore action if we're already navigating
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(mut state) = state.write() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
state.set_selecting_right(&left_ref.symbol_name);
|
||||||
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
|
view: Some(View::FunctionDiff),
|
||||||
|
left_symbol: Some(left_ref),
|
||||||
|
right_symbol: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DiffViewAction::SetMapping(view, left_ref, right_ref) => {
|
||||||
|
if self.post_build_nav.is_some() {
|
||||||
|
// Ignore action if we're already navigating
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(mut state) = state.write() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
state.set_symbol_mapping(
|
||||||
|
left_ref.symbol_name.clone(),
|
||||||
|
right_ref.symbol_name.clone(),
|
||||||
|
);
|
||||||
|
if view == View::SymbolDiff {
|
||||||
|
self.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
||||||
|
} else {
|
||||||
|
self.post_build_nav = Some(DiffViewNavigation {
|
||||||
|
view: Some(view),
|
||||||
|
left_symbol: Some(left_ref),
|
||||||
|
right_symbol: Some(right_ref),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DiffViewAction::SetShowMappedSymbols(value) => {
|
||||||
|
self.symbol_state.show_mapped_symbols = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,11 +366,13 @@ pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Co
|
|||||||
|
|
||||||
fn symbol_context_menu_ui(
|
fn symbol_context_menu_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
state: &mut SymbolViewState,
|
ctx: SymbolDiffContext<'_>,
|
||||||
arch: &dyn ObjArch,
|
other_ctx: Option<SymbolDiffContext<'_>>,
|
||||||
symbol: &ObjSymbol,
|
symbol: &ObjSymbol,
|
||||||
|
symbol_diff: &ObjSymbolDiff,
|
||||||
section: Option<&ObjSection>,
|
section: Option<&ObjSection>,
|
||||||
) -> Option<View> {
|
column: usize,
|
||||||
|
) -> Option<DiffViewNavigation> {
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
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);
|
||||||
@@ -185,14 +395,35 @@ fn symbol_context_menu_ui(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(section) = section {
|
if let Some(section) = section {
|
||||||
let has_extab = arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)).is_some();
|
let has_extab =
|
||||||
|
ctx.obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)).is_some();
|
||||||
if has_extab && ui.button("Decode exception table").clicked() {
|
if has_extab && ui.button("Decode exception table").clicked() {
|
||||||
state.selected_symbol = Some(SymbolRefByName {
|
ret = Some(DiffViewNavigation::with_symbols(
|
||||||
symbol_name: symbol.name.clone(),
|
View::ExtabDiff,
|
||||||
demangled_symbol_name: symbol.demangled_name.clone(),
|
other_ctx,
|
||||||
section_name: section.name.clone(),
|
symbol,
|
||||||
|
section,
|
||||||
|
symbol_diff,
|
||||||
|
column,
|
||||||
|
));
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Map symbol").clicked() {
|
||||||
|
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
|
||||||
|
if column == 0 {
|
||||||
|
ret = Some(DiffViewNavigation {
|
||||||
|
view: Some(View::FunctionDiff),
|
||||||
|
left_symbol: Some(symbol_ref),
|
||||||
|
right_symbol: None,
|
||||||
});
|
});
|
||||||
ret = Some(View::ExtabDiff);
|
} else {
|
||||||
|
ret = Some(DiffViewNavigation {
|
||||||
|
view: Some(View::FunctionDiff),
|
||||||
|
left_symbol: None,
|
||||||
|
right_symbol: Some(symbol_ref),
|
||||||
|
});
|
||||||
|
}
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,27 +463,28 @@ fn symbol_hover_ui(ui: &mut Ui, arch: &dyn ObjArch, symbol: &ObjSymbol, appearan
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[expect(clippy::too_many_arguments)]
|
||||||
fn symbol_ui(
|
fn symbol_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
arch: &dyn ObjArch,
|
ctx: SymbolDiffContext<'_>,
|
||||||
|
other_ctx: Option<SymbolDiffContext<'_>>,
|
||||||
symbol: &ObjSymbol,
|
symbol: &ObjSymbol,
|
||||||
symbol_diff: &ObjSymbolDiff,
|
symbol_diff: &ObjSymbolDiff,
|
||||||
section: Option<&ObjSection>,
|
section: Option<&ObjSection>,
|
||||||
state: &mut SymbolViewState,
|
state: &SymbolViewState,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
left: bool,
|
column: usize,
|
||||||
) -> Option<View> {
|
) -> Option<DiffViewAction> {
|
||||||
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) && !state.show_hidden_symbols {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
|
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) && !state.show_hidden_symbols {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
let mut job = LayoutJob::default();
|
let mut job = LayoutJob::default();
|
||||||
let name: &str =
|
let name: &str =
|
||||||
if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name };
|
if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name };
|
||||||
let mut selected = false;
|
let mut selected = false;
|
||||||
if let Some(sym_ref) =
|
if let Some(sym_ref) =
|
||||||
if left { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
|
if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
|
||||||
{
|
{
|
||||||
selected = symbol_diff.symbol_ref == sym_ref;
|
selected = symbol_diff.symbol_ref == sym_ref;
|
||||||
}
|
}
|
||||||
@@ -284,7 +516,7 @@ fn symbol_ui(
|
|||||||
if let Some(match_percent) = symbol_diff.match_percent {
|
if let Some(match_percent) = symbol_diff.match_percent {
|
||||||
write_text("(", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text("(", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
write_text(
|
write_text(
|
||||||
&format!("{match_percent:.0}%"),
|
&format!("{:.0}%", match_percent.floor()),
|
||||||
match_color_for_symbol(match_percent, appearance),
|
match_color_for_symbol(match_percent, appearance),
|
||||||
&mut job,
|
&mut job,
|
||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
@@ -292,90 +524,166 @@ fn symbol_ui(
|
|||||||
write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone());
|
write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||||
}
|
}
|
||||||
write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone());
|
write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone());
|
||||||
let response = SelectableLabel::new(selected, job)
|
let response = SelectableLabel::new(selected, job).ui(ui).on_hover_ui_at_pointer(|ui| {
|
||||||
.ui(ui)
|
symbol_hover_ui(ui, ctx.obj.arch.as_ref(), symbol, appearance)
|
||||||
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, arch, symbol, appearance));
|
});
|
||||||
response.context_menu(|ui| {
|
response.context_menu(|ui| {
|
||||||
ret = ret.or(symbol_context_menu_ui(ui, state, arch, symbol, section));
|
if let Some(result) =
|
||||||
|
symbol_context_menu_ui(ui, ctx, other_ctx, symbol, symbol_diff, section, column)
|
||||||
|
{
|
||||||
|
ret = Some(DiffViewAction::Navigate(result));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(section) = section {
|
if let Some(section) = section {
|
||||||
if section.kind == ObjSectionKind::Code {
|
match section.kind {
|
||||||
state.selected_symbol = Some(SymbolRefByName {
|
ObjSectionKind::Code => {
|
||||||
symbol_name: symbol.name.clone(),
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols(
|
||||||
demangled_symbol_name: symbol.demangled_name.clone(),
|
View::FunctionDiff,
|
||||||
section_name: section.name.clone(),
|
other_ctx,
|
||||||
});
|
symbol,
|
||||||
ret = Some(View::FunctionDiff);
|
section,
|
||||||
} else if section.kind == ObjSectionKind::Data {
|
symbol_diff,
|
||||||
state.selected_symbol = Some(SymbolRefByName {
|
column,
|
||||||
symbol_name: section.name.clone(),
|
)));
|
||||||
demangled_symbol_name: None,
|
}
|
||||||
section_name: section.name.clone(),
|
ObjSectionKind::Data => {
|
||||||
});
|
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols(
|
||||||
ret = Some(View::DataDiff);
|
View::DataDiff,
|
||||||
|
other_ctx,
|
||||||
|
symbol,
|
||||||
|
section,
|
||||||
|
symbol_diff,
|
||||||
|
column,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
ObjSectionKind::Bss => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if response.hovered() {
|
} else if response.hovered() {
|
||||||
state.highlighted_symbol = if let Some(diff_symbol) = symbol_diff.diff_symbol {
|
ret = Some(if let Some(target_symbol) = symbol_diff.target_symbol {
|
||||||
if left {
|
if column == 0 {
|
||||||
(Some(symbol_diff.symbol_ref), Some(diff_symbol))
|
DiffViewAction::SetSymbolHighlight(
|
||||||
|
Some(symbol_diff.symbol_ref),
|
||||||
|
Some(target_symbol),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(Some(diff_symbol), Some(symbol_diff.symbol_ref))
|
DiffViewAction::SetSymbolHighlight(
|
||||||
|
Some(target_symbol),
|
||||||
|
Some(symbol_diff.symbol_ref),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
DiffViewAction::SetSymbolHighlight(None, None)
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
fn symbol_matches_search(symbol: &ObjSymbol, search_regex: Option<&Regex>) -> bool {
|
fn symbol_matches_filter(
|
||||||
if let Some(search_regex) = search_regex {
|
symbol: &ObjSymbol,
|
||||||
search_regex.is_match(&symbol.name)
|
diff: &ObjSymbolDiff,
|
||||||
|| symbol.demangled_name.as_ref().map(|s| search_regex.is_match(s)).unwrap_or(false)
|
filter: SymbolFilter<'_>,
|
||||||
} else {
|
) -> bool {
|
||||||
true
|
match filter {
|
||||||
|
SymbolFilter::None => true,
|
||||||
|
SymbolFilter::Search(regex) => {
|
||||||
|
regex.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_ref().map(|s| regex.is_match(s)).unwrap_or(false)
|
||||||
|
}
|
||||||
|
SymbolFilter::Mapping(symbol_ref) => diff.target_symbol == Some(symbol_ref),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum SymbolFilter<'a> {
|
||||||
|
None,
|
||||||
|
Search(&'a Regex),
|
||||||
|
Mapping(SymbolRef),
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn symbol_list_ui(
|
pub fn symbol_list_ui(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
obj: &(ObjInfo, ObjDiff),
|
ctx: SymbolDiffContext<'_>,
|
||||||
state: &mut SymbolViewState,
|
other_ctx: Option<SymbolDiffContext<'_>>,
|
||||||
search_regex: Option<&Regex>,
|
state: &SymbolViewState,
|
||||||
|
filter: SymbolFilter<'_>,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
left: bool,
|
column: usize,
|
||||||
) -> Option<View> {
|
) -> Option<DiffViewAction> {
|
||||||
let mut ret = None;
|
let mut ret = None;
|
||||||
let arch = obj.0.arch.as_ref();
|
|
||||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||||
|
let mut mapping = BTreeMap::new();
|
||||||
|
if let SymbolFilter::Mapping(target_ref) = filter {
|
||||||
|
let mut show_mapped_symbols = state.show_mapped_symbols;
|
||||||
|
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
|
||||||
|
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
|
||||||
|
}
|
||||||
|
for mapping_diff in &ctx.diff.mapping_symbols {
|
||||||
|
if mapping_diff.target_symbol == Some(target_ref) {
|
||||||
|
if !show_mapped_symbols {
|
||||||
|
let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref);
|
||||||
|
if symbol_diff.target_symbol.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.insert(mapping_diff.symbol_ref, mapping_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (symbol, diff) in ctx.obj.common.iter().zip(&ctx.diff.common) {
|
||||||
|
if !symbol_matches_filter(symbol, diff, filter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mapping.insert(diff.symbol_ref, diff);
|
||||||
|
}
|
||||||
|
for (section, section_diff) in ctx.obj.sections.iter().zip(&ctx.diff.sections) {
|
||||||
|
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
||||||
|
if !symbol_matches_filter(symbol, symbol_diff, filter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mapping.insert(symbol_diff.symbol_ref, symbol_diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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::Extend);
|
||||||
|
|
||||||
if !obj.0.common.is_empty() {
|
// Skip sections with all symbols filtered out
|
||||||
|
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == SECTION_COMMON) {
|
||||||
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
|
||||||
for (symbol, symbol_diff) in obj.0.common.iter().zip(&obj.1.common) {
|
for (symbol_ref, symbol_diff) in mapping
|
||||||
if !symbol_matches_search(symbol, search_regex) {
|
.iter()
|
||||||
continue;
|
.filter(|(symbol_ref, _)| symbol_ref.section_idx == SECTION_COMMON)
|
||||||
}
|
{
|
||||||
ret = ret.or(symbol_ui(
|
let symbol = ctx.obj.section_symbol(*symbol_ref).1;
|
||||||
|
if let Some(result) = symbol_ui(
|
||||||
ui,
|
ui,
|
||||||
arch,
|
ctx,
|
||||||
|
other_ctx,
|
||||||
symbol,
|
symbol,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
None,
|
None,
|
||||||
state,
|
state,
|
||||||
appearance,
|
appearance,
|
||||||
left,
|
column,
|
||||||
));
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (section, section_diff) in obj.0.sections.iter().zip(&obj.1.sections) {
|
for ((section_index, section), section_diff) in
|
||||||
|
ctx.obj.sections.iter().enumerate().zip(&ctx.diff.sections)
|
||||||
|
{
|
||||||
|
// Skip sections with all symbols filtered out
|
||||||
|
if !mapping.keys().any(|symbol_ref| symbol_ref.section_idx == section_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut header = LayoutJob::simple_singleline(
|
let mut header = LayoutJob::simple_singleline(
|
||||||
format!("{} ({:x})", section.name, section.size),
|
format!("{} ({:x})", section.name, section.size),
|
||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
@@ -389,7 +697,7 @@ fn symbol_list_ui(
|
|||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
);
|
);
|
||||||
write_text(
|
write_text(
|
||||||
&format!("{match_percent:.0}%"),
|
&format!("{:.0}%", match_percent.floor()),
|
||||||
match_color_for_symbol(match_percent, appearance),
|
match_color_for_symbol(match_percent, appearance),
|
||||||
&mut header,
|
&mut header,
|
||||||
appearance.code_font.clone(),
|
appearance.code_font.clone(),
|
||||||
@@ -406,40 +714,45 @@ fn symbol_list_ui(
|
|||||||
.default_open(true)
|
.default_open(true)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
if section.kind == ObjSectionKind::Code && state.reverse_fn_order {
|
if section.kind == ObjSectionKind::Code && state.reverse_fn_order {
|
||||||
for (symbol, symbol_diff) in
|
for (symbol, symbol_diff) in mapping
|
||||||
section.symbols.iter().zip(§ion_diff.symbols).rev()
|
.iter()
|
||||||
|
.filter(|(symbol_ref, _)| symbol_ref.section_idx == section_index)
|
||||||
|
.rev()
|
||||||
{
|
{
|
||||||
if !symbol_matches_search(symbol, search_regex) {
|
let symbol = ctx.obj.section_symbol(*symbol).1;
|
||||||
continue;
|
if let Some(result) = symbol_ui(
|
||||||
}
|
|
||||||
ret = ret.or(symbol_ui(
|
|
||||||
ui,
|
ui,
|
||||||
arch,
|
ctx,
|
||||||
|
other_ctx,
|
||||||
symbol,
|
symbol,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
Some(section),
|
Some(section),
|
||||||
state,
|
state,
|
||||||
appearance,
|
appearance,
|
||||||
left,
|
column,
|
||||||
));
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (symbol, symbol_diff) in
|
for (symbol, symbol_diff) in mapping
|
||||||
section.symbols.iter().zip(§ion_diff.symbols)
|
.iter()
|
||||||
|
.filter(|(symbol_ref, _)| symbol_ref.section_idx == section_index)
|
||||||
{
|
{
|
||||||
if !symbol_matches_search(symbol, search_regex) {
|
let symbol = ctx.obj.section_symbol(*symbol).1;
|
||||||
continue;
|
if let Some(result) = symbol_ui(
|
||||||
}
|
|
||||||
ret = ret.or(symbol_ui(
|
|
||||||
ui,
|
ui,
|
||||||
arch,
|
ctx,
|
||||||
|
other_ctx,
|
||||||
symbol,
|
symbol,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
Some(section),
|
Some(section),
|
||||||
state,
|
state,
|
||||||
appearance,
|
appearance,
|
||||||
left,
|
column,
|
||||||
));
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -487,80 +800,62 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) {
|
#[derive(Copy, Clone)]
|
||||||
let DiffViewState { build, current_view, symbol_state, search, search_regex, .. } = state;
|
pub struct SymbolDiffContext<'a> {
|
||||||
let Some(result) = build else {
|
pub obj: &'a ObjInfo,
|
||||||
return;
|
pub diff: &'a ObjDiff,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn symbol_diff_ui(
|
||||||
|
ui: &mut Ui,
|
||||||
|
state: &mut DiffViewState,
|
||||||
|
appearance: &Appearance,
|
||||||
|
) -> Option<DiffViewAction> {
|
||||||
|
let mut ret = None;
|
||||||
|
let Some(result) = &state.build else {
|
||||||
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
let available_width = ui.available_width();
|
let available_width = ui.available_width();
|
||||||
let column_width = available_width / 2.0;
|
render_header(ui, available_width, 2, |ui, column| {
|
||||||
ui.allocate_ui_with_layout(
|
if column == 0 {
|
||||||
Vec2 { x: available_width, y: 100.0 },
|
|
||||||
Layout::left_to_right(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
|
|
||||||
|
|
||||||
// Left column
|
// Left column
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
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.label("Build target:");
|
ui.label("Target object");
|
||||||
if result.first_status.success {
|
if result.first_status.success {
|
||||||
if result.first_obj.is_none() {
|
if result.first_obj.is_none() {
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
ui.colored_label(appearance.replace_color, "Missing");
|
||||||
} else {
|
} else {
|
||||||
ui.label("OK");
|
ui.colored_label(appearance.highlight_color, state.object_name.clone());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ui.colored_label(appearance.delete_color, "Fail");
|
ui.colored_label(appearance.delete_color, "Fail");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if TextEdit::singleline(search).hint_text("Filter symbols").ui(ui).changed() {
|
let mut search = state.search.clone();
|
||||||
if search.is_empty() {
|
if TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui).changed() {
|
||||||
*search_regex = None;
|
ret = Some(DiffViewAction::SetSearch(search));
|
||||||
} else if let Ok(regex) =
|
|
||||||
RegexBuilder::new(search).case_insensitive(true).build()
|
|
||||||
{
|
|
||||||
*search_regex = Some(regex);
|
|
||||||
} else {
|
|
||||||
*search_regex = None;
|
|
||||||
}
|
}
|
||||||
}
|
} else if column == 1 {
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right column
|
// Right column
|
||||||
ui.allocate_ui_with_layout(
|
|
||||||
Vec2 { x: column_width, y: 100.0 },
|
|
||||||
Layout::top_down(Align::Min),
|
|
||||||
|ui| {
|
|
||||||
ui.set_width(column_width);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
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.label("Build base:");
|
ui.label("Base object");
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui
|
if ui
|
||||||
.add_enabled(
|
.add_enabled(state.source_path_available, egui::Button::new("🖹 Source file"))
|
||||||
state.source_path_available,
|
|
||||||
egui::Button::new("🖹 Source file"),
|
|
||||||
)
|
|
||||||
.on_hover_text_at_pointer("Open the source file in the default editor")
|
.on_hover_text_at_pointer("Open the source file in the default editor")
|
||||||
.on_disabled_hover_text("Source file metadata missing")
|
.on_disabled_hover_text("Source file metadata missing")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
state.queue_open_source_path = true;
|
ret = Some(DiffViewAction::OpenSourcePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -570,7 +865,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
if result.second_obj.is_none() {
|
if result.second_obj.is_none() {
|
||||||
ui.colored_label(appearance.replace_color, "Missing");
|
ui.colored_label(appearance.replace_color, "Missing");
|
||||||
} else {
|
} else {
|
||||||
ui.label("OK");
|
ui.colored_label(appearance.highlight_color, "OK");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ui.colored_label(appearance.delete_color, "Fail");
|
ui.colored_label(appearance.delete_color, "Fail");
|
||||||
@@ -578,64 +873,66 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
||||||
state.queue_build = true;
|
ret = Some(DiffViewAction::Build);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
let mut ret = None;
|
let filter = match &state.search_regex {
|
||||||
StripBuilder::new(ui).size(Size::remainder()).vertical(|mut strip| {
|
Some(regex) => SymbolFilter::Search(regex),
|
||||||
strip.strip(|builder| {
|
_ => SymbolFilter::None,
|
||||||
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
|
};
|
||||||
strip.cell(|ui| {
|
render_strips(ui, available_width, 2, |ui, column| {
|
||||||
ui.push_id("left", |ui| {
|
if column == 0 {
|
||||||
|
// Left column
|
||||||
if result.first_status.success {
|
if result.first_status.success {
|
||||||
if let Some(obj) = &result.first_obj {
|
if let Some((obj, diff)) = &result.first_obj {
|
||||||
ret = ret.or(symbol_list_ui(
|
if let Some(result) = symbol_list_ui(
|
||||||
ui,
|
ui,
|
||||||
obj,
|
SymbolDiffContext { obj, diff },
|
||||||
symbol_state,
|
result
|
||||||
search_regex.as_ref(),
|
.second_obj
|
||||||
|
.as_ref()
|
||||||
|
.map(|(obj, diff)| SymbolDiffContext { obj, diff }),
|
||||||
|
&state.symbol_state,
|
||||||
|
filter,
|
||||||
appearance,
|
appearance,
|
||||||
true,
|
column,
|
||||||
));
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
missing_obj_ui(ui, appearance);
|
missing_obj_ui(ui, appearance);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
build_log_ui(ui, &result.first_status, appearance);
|
build_log_ui(ui, &result.first_status, appearance);
|
||||||
}
|
}
|
||||||
});
|
} else if column == 1 {
|
||||||
});
|
// Right column
|
||||||
strip.cell(|ui| {
|
|
||||||
ui.push_id("right", |ui| {
|
|
||||||
if result.second_status.success {
|
if result.second_status.success {
|
||||||
if let Some(obj) = &result.second_obj {
|
if let Some((obj, diff)) = &result.second_obj {
|
||||||
ret = ret.or(symbol_list_ui(
|
if let Some(result) = symbol_list_ui(
|
||||||
ui,
|
ui,
|
||||||
obj,
|
SymbolDiffContext { obj, diff },
|
||||||
symbol_state,
|
result
|
||||||
search_regex.as_ref(),
|
.first_obj
|
||||||
|
.as_ref()
|
||||||
|
.map(|(obj, diff)| SymbolDiffContext { obj, diff }),
|
||||||
|
&state.symbol_state,
|
||||||
|
filter,
|
||||||
appearance,
|
appearance,
|
||||||
false,
|
column,
|
||||||
));
|
) {
|
||||||
|
ret = Some(result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
missing_obj_ui(ui, appearance);
|
missing_obj_ui(ui, appearance);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
build_log_ui(ui, &result.second_status, appearance);
|
build_log_ui(ui, &result.second_status, appearance);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(view) = ret {
|
|
||||||
*current_view = view;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user