mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bfa47fce9 | |||
| 1d9b9b6893 | |||
| 6b8e469261 | |||
| bf3ba48539 | |||
| 21cdf268f0 | |||
| 3970bc8acf | |||
| eaf0fabc2d | |||
| 91d11c83d6 | |||
| 94924047b7 | |||
| f5f6869029 | |||
| b02e32f2b7 | |||
| c7a326b160 | |||
| 100f8f8ac5 | |||
| 2f778932a4 | |||
| 42601b4750 | |||
| 636a8e00c5 | |||
| cd46be7726 | |||
| 019493f944 | |||
| 319b1c35c0 | |||
| 634e007cbc | |||
| 6ee11ca640 | |||
| 8278d5d207 | |||
| 09bbc534bd | |||
| fa28352e08 | |||
| 2ab519d361 | |||
| 3406c76973 | |||
| 6afc535fad | |||
| ec062bf5ca | |||
| 500965aacb | |||
| a8c2514377 | |||
| 4b58f69461 | |||
| cd01b6254c | |||
| bea0a0007d | |||
| ba74d63a99 | |||
| 20dcc50695 | |||
| c7b6ec83d7 |
@@ -20,7 +20,9 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt-get -y install libgtk-3-dev
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
@@ -58,7 +60,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: matrix.platform == 'ubuntu-latest'
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
run: sudo apt-get -y install libgtk-3-dev
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
@@ -89,7 +93,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: matrix.packages != ''
|
if: matrix.packages != ''
|
||||||
run: sudo apt-get -y install ${{ matrix.packages }}
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install ${{ matrix.packages }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
|
|||||||
Generated
+2017
-980
File diff suppressed because it is too large
Load Diff
+43
-25
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff"
|
name = "objdiff"
|
||||||
version = "0.2.3"
|
version = "0.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.62"
|
rust-version = "1.65"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
@@ -11,38 +11,56 @@ description = """
|
|||||||
A local diffing tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
"""
|
"""
|
||||||
publish = false
|
publish = false
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
wgpu = ["eframe/wgpu"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.71"
|
||||||
bytes = "1.3.0"
|
byteorder = "1.4.3"
|
||||||
|
bytes = "1.4.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
const_format = "0.2.30"
|
const_format = "0.2.31"
|
||||||
cwdemangle = { git = "https://github.com/encounter/cwdemangle", rev = "286f3d1d29ee2457db89043782725631845c3e4c" }
|
cwdemangle = "0.1.6"
|
||||||
eframe = { version = "0.19.0", features = ["persistence"] } # , "wgpu"
|
dirs = "5.0.1"
|
||||||
egui = "0.19.0"
|
eframe = { version = "0.22.0", features = ["persistence"] }
|
||||||
egui_extras = "0.19.0"
|
egui = "0.22.0"
|
||||||
|
egui_extras = "0.22.0"
|
||||||
flagset = "0.4.3"
|
flagset = "0.4.3"
|
||||||
log = "0.4.17"
|
globset = { version = "0.4.13", features = ["serde1"] }
|
||||||
memmap2 = "0.5.8"
|
log = "0.4.19"
|
||||||
notify = "5.0.0"
|
memmap2 = "0.7.1"
|
||||||
object = { version = "0.30.0", features = ["read_core", "std", "elf"], default-features = false }
|
notify = "6.0.1"
|
||||||
png = "0.17.7"
|
object = { version = "0.31.1", features = ["read_core", "std", "elf"], default-features = false }
|
||||||
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "aa631a33de7882c679afca89350898b87cb3ba3f" }
|
png = "0.17.9"
|
||||||
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
|
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
|
||||||
reqwest = "0.11.13"
|
rabbitizer = "1.7.4"
|
||||||
rfd = { version = "0.10.0" } # , default-features = false, features = ['xdg-portal']
|
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal']
|
||||||
self_update = "0.32.0"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
tempfile = "3.3.0"
|
serde_json = "1.0.104"
|
||||||
thiserror = "1.0.37"
|
serde_yaml = "0.9.25"
|
||||||
time = { version = "0.3.17", features = ["formatting", "local-offset"] }
|
tempfile = "3.6.0"
|
||||||
toml = "0.5.9"
|
thiserror = "1.0.41"
|
||||||
|
time = { version = "0.3.22", features = ["formatting", "local-offset"] }
|
||||||
|
toml = "0.7.6"
|
||||||
twox-hash = "1.6.3"
|
twox-hash = "1.6.3"
|
||||||
|
|
||||||
|
# For Linux static binaries, use rustls
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
reqwest = { version = "0.11.18", default-features = false, features = ["blocking", "json", "rustls"] }
|
||||||
|
self_update = { version = "0.37.0", default-features = false, features = ["rustls"] }
|
||||||
|
|
||||||
|
# For all other platforms, use native TLS
|
||||||
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
|
reqwest = "0.11.18"
|
||||||
|
self_update = "0.37.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
path-slash = "0.2.1"
|
path-slash = "0.2.1"
|
||||||
winapi = "0.3.9"
|
winapi = "0.3.9"
|
||||||
@@ -63,5 +81,5 @@ console_error_panic_hook = "0.1.7"
|
|||||||
tracing-wasm = "0.2"
|
tracing-wasm = "0.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.71"
|
||||||
vergen = { version = "7.4.3", features = ["build", "cargo", "git"], default-features = false }
|
vergen = { version = "8.2.4", features = ["build", "cargo", "git", "gitcl"] }
|
||||||
|
|||||||
@@ -5,14 +5,122 @@
|
|||||||
|
|
||||||
A local diffing tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
|
|
||||||
Currently supports:
|
Supports:
|
||||||
- PowerPC 750CL (GameCube & Wii)
|
- PowerPC 750CL (GameCube & Wii)
|
||||||
- MIPS (Nintendo 64)
|
- MIPS (Nintendo 64)
|
||||||
|
|
||||||
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
### License
|
## Usage
|
||||||
|
|
||||||
|
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path
|
||||||
|
from the "target" and "base" directories.
|
||||||
|
|
||||||
|
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual")
|
||||||
|
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
|
||||||
|
|
||||||
|
- Target build directory: `build/asm`
|
||||||
|
- Base build directory: `build/src`
|
||||||
|
- Object: `MetroTRK/mslsupp.o`
|
||||||
|
|
||||||
|
objdiff will then execute the build system from the project directory to build both objects:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
|
||||||
|
$ make build/src/MetroTRK/mslsupp.o
|
||||||
|
```
|
||||||
|
|
||||||
|
The objects will then be compared and the results will be displayed in the UI.
|
||||||
|
|
||||||
|
See [Configuration](#configuration) for more information.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` (or
|
||||||
|
`objdiff.yaml`, `objdiff.yml`) file to configure the tool automatically. The configuration file must be located in
|
||||||
|
the root project directory.
|
||||||
|
|
||||||
|
If your project has a generator script (e.g. `configure.py`), it's recommended to generate the objdiff configuration
|
||||||
|
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
// objdiff.json
|
||||||
|
{
|
||||||
|
"custom_make": "ninja",
|
||||||
|
|
||||||
|
// Only required if objects use "path" instead of "target_path" and "base_path".
|
||||||
|
"target_dir": "build/asm",
|
||||||
|
"base_dir": "build/src",
|
||||||
|
|
||||||
|
"build_target": true,
|
||||||
|
"watch_patterns": [
|
||||||
|
"*.c",
|
||||||
|
"*.cp",
|
||||||
|
"*.cpp",
|
||||||
|
"*.h",
|
||||||
|
"*.hpp",
|
||||||
|
"*.py"
|
||||||
|
],
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"name": "main/MetroTRK/mslsupp",
|
||||||
|
|
||||||
|
// Option 1: Relative to target_dir and base_dir
|
||||||
|
"path": "MetroTRK/mslsupp.o",
|
||||||
|
// Option 2: Explicit paths from project root
|
||||||
|
// Useful for more complex directory layouts
|
||||||
|
"target_path": "build/asm/MetroTRK/mslsupp.o",
|
||||||
|
"base_path": "build/src/MetroTRK/mslsupp.o",
|
||||||
|
|
||||||
|
"reverse_fn_order": false
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
|
||||||
|
If the project uses a different build system (e.g. `ninja`), specify it here.
|
||||||
|
|
||||||
|
`target_dir` _(optional)_: Relative from the root of the project, this where the "target" or "expected" objects are located.
|
||||||
|
These are the **intended result** of the match.
|
||||||
|
|
||||||
|
`base_dir` _(optional)_: Relative from the root of the project, this is where the "base" or "actual" objects are located.
|
||||||
|
These are objects built from the **current source code**.
|
||||||
|
|
||||||
|
`build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g.
|
||||||
|
`make path/to/target.o`).
|
||||||
|
This is useful if the target objects are not built by default or can change based on project configuration or edits
|
||||||
|
to assembly files.
|
||||||
|
Requires the build system to be configured properly.
|
||||||
|
|
||||||
|
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes.
|
||||||
|
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
|
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
||||||
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
|
`objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
|
|
||||||
|
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
|
>
|
||||||
|
> `path`: Relative path to the object from the `target_dir` and `base_dir`.
|
||||||
|
> Requires `target_dir` and `base_dir` to be specified.
|
||||||
|
>
|
||||||
|
> `target_path`: Path to the target object from the project root.
|
||||||
|
> Required if `path` is not specified.
|
||||||
|
>
|
||||||
|
> `base_path`: Path to the base object from the project root.
|
||||||
|
> Required if `path` is not specified.
|
||||||
|
>
|
||||||
|
> `reverse_fn_order` _(optional)_: Displays function symbols in reversed order.
|
||||||
|
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|
||||||
@@ -23,6 +131,5 @@ at your option.
|
|||||||
|
|
||||||
### Contribution
|
### Contribution
|
||||||
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as
|
||||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
|
defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
||||||
additional terms or conditions.
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 144 KiB |
@@ -1,10 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use vergen::{vergen, Config};
|
use vergen::EmitBuilder;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
winres::WindowsResource::new().set_icon("assets/icon.ico").compile()?;
|
winres::WindowsResource::new().set_icon("assets/icon.ico").compile()?;
|
||||||
}
|
}
|
||||||
vergen(Config::default())
|
EmitBuilder::builder().fail_on_error().all_build().all_cargo().all_git().emit()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ notice = "warn"
|
|||||||
# 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-0000-0000",
|
"RUSTSEC-2023-0022",
|
||||||
|
"RUSTSEC-2023-0023",
|
||||||
|
"RUSTSEC-2023-0024",
|
||||||
|
"RUSTSEC-2023-0034",
|
||||||
|
"RUSTSEC-2023-0044",
|
||||||
]
|
]
|
||||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||||
# lower than the range specified will be ignored. Note that ignored advisories
|
# lower than the range specified will be ignored. Note that ignored advisories
|
||||||
@@ -81,6 +85,10 @@ allow = [
|
|||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
|
"OFL-1.1",
|
||||||
|
"LicenseRef-UFL-1.0",
|
||||||
|
"OpenSSL",
|
||||||
|
"GPL-3.0",
|
||||||
]
|
]
|
||||||
# List of explictly disallowed licenses
|
# List of explictly disallowed licenses
|
||||||
# See https://spdx.org/licenses/ for list of possible licenses
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
@@ -118,22 +126,22 @@ exceptions = [
|
|||||||
# Some crates don't have (easily) machine readable licensing information,
|
# Some crates don't have (easily) machine readable licensing information,
|
||||||
# adding a clarification entry for it allows you to manually specify the
|
# adding a clarification entry for it allows you to manually specify the
|
||||||
# licensing information
|
# licensing information
|
||||||
#[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
# The name of the crate the clarification applies to
|
# The name of the crate the clarification applies to
|
||||||
#name = "ring"
|
name = "ring"
|
||||||
# The optional version constraint for the crate
|
# The optional version constraint for the crate
|
||||||
#version = "*"
|
version = "*"
|
||||||
# The SPDX expression for the license requirements of the crate
|
# The SPDX expression for the license requirements of the crate
|
||||||
#expression = "MIT AND ISC AND OpenSSL"
|
expression = "MIT AND ISC AND OpenSSL"
|
||||||
# One or more files in the crate's source used as the "source of truth" for
|
# One or more files in the crate's source used as the "source of truth" for
|
||||||
# the license expression. If the contents match, the clarification will be used
|
# the license expression. If the contents match, the clarification will be used
|
||||||
# when running the license check, otherwise the clarification will be ignored
|
# when running the license check, otherwise the clarification will be ignored
|
||||||
# and the crate will be checked normally, which may produce warnings or errors
|
# and the crate will be checked normally, which may produce warnings or errors
|
||||||
# depending on the rest of your configuration
|
# depending on the rest of your configuration
|
||||||
#license-files = [
|
license-files = [
|
||||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||||
#]
|
]
|
||||||
|
|
||||||
[licenses.private]
|
[licenses.private]
|
||||||
# If true, ignores workspace crates that aren't published, or are only
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
|
|||||||
+289
-409
File diff suppressed because it is too large
Load Diff
+170
@@ -0,0 +1,170 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
|
||||||
|
use crate::{app::AppConfig, views::config::DEFAULT_WATCH_PATTERNS};
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct ProjectConfig {
|
||||||
|
pub custom_make: Option<String>,
|
||||||
|
pub target_dir: Option<PathBuf>,
|
||||||
|
pub base_dir: Option<PathBuf>,
|
||||||
|
pub build_target: bool,
|
||||||
|
pub watch_patterns: Option<Vec<Glob>>,
|
||||||
|
#[serde(alias = "units")]
|
||||||
|
pub objects: Vec<ProjectObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, serde::Deserialize)]
|
||||||
|
pub struct ProjectObject {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
pub target_path: Option<PathBuf>,
|
||||||
|
pub base_path: Option<PathBuf>,
|
||||||
|
pub reverse_fn_order: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectObject {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
if let Some(name) = &self.name {
|
||||||
|
name
|
||||||
|
} else if let Some(path) = &self.path {
|
||||||
|
path.to_str().unwrap_or("[invalid path]")
|
||||||
|
} else {
|
||||||
|
"[unknown]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ProjectObjectNode {
|
||||||
|
File(String, ProjectObject),
|
||||||
|
Dir(String, Vec<ProjectObjectNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_dir<'a>(
|
||||||
|
name: &str,
|
||||||
|
nodes: &'a mut Vec<ProjectObjectNode>,
|
||||||
|
) -> &'a mut Vec<ProjectObjectNode> {
|
||||||
|
if let Some(index) = nodes
|
||||||
|
.iter()
|
||||||
|
.position(|node| matches!(node, ProjectObjectNode::Dir(dir_name, _) if dir_name == name))
|
||||||
|
{
|
||||||
|
if let ProjectObjectNode::Dir(_, children) = &mut nodes[index] {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodes.push(ProjectObjectNode::Dir(name.to_string(), vec![]));
|
||||||
|
if let Some(ProjectObjectNode::Dir(_, children)) = nodes.last_mut() {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_nodes(
|
||||||
|
objects: &[ProjectObject],
|
||||||
|
project_dir: &Path,
|
||||||
|
target_obj_dir: &Option<PathBuf>,
|
||||||
|
base_obj_dir: &Option<PathBuf>,
|
||||||
|
) -> Vec<ProjectObjectNode> {
|
||||||
|
let mut nodes = vec![];
|
||||||
|
for object in objects {
|
||||||
|
let mut out_nodes = &mut nodes;
|
||||||
|
let path = if let Some(name) = &object.name {
|
||||||
|
Path::new(name)
|
||||||
|
} else if let Some(path) = &object.path {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
for component in parent.components() {
|
||||||
|
if let Component::Normal(name) = component {
|
||||||
|
let name = name.to_str().unwrap();
|
||||||
|
out_nodes = find_dir(name, out_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut object = object.clone();
|
||||||
|
if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
object.target_path = Some(target_obj_dir.join(path));
|
||||||
|
} else if let Some(path) = &object.target_path {
|
||||||
|
object.target_path = Some(project_dir.join(path));
|
||||||
|
}
|
||||||
|
if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
object.base_path = Some(base_obj_dir.join(path));
|
||||||
|
} else if let Some(path) = &object.base_path {
|
||||||
|
object.base_path = Some(project_dir.join(path));
|
||||||
|
}
|
||||||
|
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
out_nodes.push(ProjectObjectNode::File(filename, object));
|
||||||
|
}
|
||||||
|
nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.yml", "objdiff.yaml", "objdiff.json"];
|
||||||
|
|
||||||
|
pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
|
||||||
|
let Some(project_dir) = &config.project_dir else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if let Some(result) = try_project_config(project_dir) {
|
||||||
|
let project_config = result?;
|
||||||
|
config.custom_make = project_config.custom_make;
|
||||||
|
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
|
||||||
|
config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
|
||||||
|
config.build_target = project_config.build_target;
|
||||||
|
config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| {
|
||||||
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
|
});
|
||||||
|
config.watcher_change = true;
|
||||||
|
config.objects = project_config.objects;
|
||||||
|
config.object_nodes =
|
||||||
|
build_nodes(&config.objects, project_dir, &config.target_obj_dir, &config.base_obj_dir);
|
||||||
|
config.project_config_loaded = true;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_project_config(dir: &Path) -> Option<Result<ProjectConfig>> {
|
||||||
|
for filename in CONFIG_FILENAMES.iter() {
|
||||||
|
let config_path = dir.join(filename);
|
||||||
|
if config_path.is_file() {
|
||||||
|
return match filename.contains("json") {
|
||||||
|
true => Some(read_json_config(&config_path)),
|
||||||
|
false => Some(read_yml_config(&config_path)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_yml_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||||
|
let mut reader = File::open(config_path)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||||
|
Ok(serde_yaml::from_reader(&mut reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_json_config(config_path: &Path) -> Result<ProjectConfig> {
|
||||||
|
let mut reader = File::open(config_path)
|
||||||
|
.with_context(|| format!("Failed to open config file '{}'", config_path.display()))?;
|
||||||
|
Ok(serde_json::from_reader(&mut reader)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
for glob in vec {
|
||||||
|
builder.add(glob.clone());
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
+98
-77
@@ -3,7 +3,6 @@ use std::{collections::BTreeMap, mem::take};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::DiffConfig,
|
|
||||||
editops::{editops_find, LevEditType},
|
editops::{editops_find, LevEditType},
|
||||||
obj::{
|
obj::{
|
||||||
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
||||||
@@ -17,14 +16,19 @@ fn no_diff_code(
|
|||||||
data: &[u8],
|
data: &[u8],
|
||||||
symbol: &mut ObjSymbol,
|
symbol: &mut ObjSymbol,
|
||||||
relocs: &[ObjReloc],
|
relocs: &[ObjReloc],
|
||||||
|
line_info: &Option<BTreeMap<u32, u32>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let code =
|
let code =
|
||||||
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
||||||
let (_, ins) = match arch {
|
let (_, ins) = match arch {
|
||||||
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs)?,
|
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?,
|
||||||
ObjArchitecture::Mips => {
|
ObjArchitecture::Mips => mips::process_code(
|
||||||
mips::process_code(code, symbol.address, symbol.address + symbol.size, relocs)?
|
code,
|
||||||
}
|
symbol.address,
|
||||||
|
symbol.address + symbol.size,
|
||||||
|
relocs,
|
||||||
|
line_info,
|
||||||
|
)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut diff = Vec::<ObjInsDiff>::new();
|
let mut diff = Vec::<ObjInsDiff>::new();
|
||||||
@@ -36,6 +40,7 @@ fn no_diff_code(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
arch: ObjArchitecture,
|
arch: ObjArchitecture,
|
||||||
left_data: &[u8],
|
left_data: &[u8],
|
||||||
@@ -44,6 +49,8 @@ pub fn diff_code(
|
|||||||
right_symbol: &mut ObjSymbol,
|
right_symbol: &mut ObjSymbol,
|
||||||
left_relocs: &[ObjReloc],
|
left_relocs: &[ObjReloc],
|
||||||
right_relocs: &[ObjReloc],
|
right_relocs: &[ObjReloc],
|
||||||
|
left_line_info: &Option<BTreeMap<u32, u32>>,
|
||||||
|
right_line_info: &Option<BTreeMap<u32, u32>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let left_code = &left_data[left_symbol.section_address as usize
|
let left_code = &left_data[left_symbol.section_address as usize
|
||||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
..(left_symbol.section_address + left_symbol.size) as usize];
|
||||||
@@ -51,8 +58,8 @@ pub fn diff_code(
|
|||||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
..(right_symbol.section_address + right_symbol.size) as usize];
|
||||||
let ((left_ops, left_insts), (right_ops, right_insts)) = match arch {
|
let ((left_ops, left_insts), (right_ops, right_insts)) = match arch {
|
||||||
ObjArchitecture::PowerPc => (
|
ObjArchitecture::PowerPc => (
|
||||||
ppc::process_code(left_code, left_symbol.address, left_relocs)?,
|
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
|
||||||
ppc::process_code(right_code, right_symbol.address, right_relocs)?,
|
ppc::process_code(right_code, right_symbol.address, right_relocs, right_line_info)?,
|
||||||
),
|
),
|
||||||
ObjArchitecture::Mips => (
|
ObjArchitecture::Mips => (
|
||||||
mips::process_code(
|
mips::process_code(
|
||||||
@@ -60,12 +67,14 @@ pub fn diff_code(
|
|||||||
left_symbol.address,
|
left_symbol.address,
|
||||||
left_symbol.address + left_symbol.size,
|
left_symbol.address + left_symbol.size,
|
||||||
left_relocs,
|
left_relocs,
|
||||||
|
left_line_info,
|
||||||
)?,
|
)?,
|
||||||
mips::process_code(
|
mips::process_code(
|
||||||
right_code,
|
right_code,
|
||||||
right_symbol.address,
|
right_symbol.address,
|
||||||
left_symbol.address + left_symbol.size,
|
left_symbol.address + left_symbol.size,
|
||||||
right_relocs,
|
right_relocs,
|
||||||
|
right_line_info,
|
||||||
)?,
|
)?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -123,7 +132,6 @@ pub fn diff_code(
|
|||||||
right_diff.push(ObjInsDiff::default());
|
right_diff.push(ObjInsDiff::default());
|
||||||
cur_left = left_iter.next();
|
cur_left = left_iter.next();
|
||||||
}
|
}
|
||||||
LevEditType::Keep => unreachable!(),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -211,25 +219,25 @@ fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reloc_eq(left_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>) -> bool {
|
fn reloc_eq(left_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>) -> bool {
|
||||||
if let (Some(left), Some(right)) = (left_reloc, right_reloc) {
|
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
||||||
if left.kind != right.kind {
|
return false;
|
||||||
return false;
|
};
|
||||||
|
if left.kind != right.kind {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_matches = left.target.name == right.target.name;
|
||||||
|
match (&left.target_section, &right.target_section) {
|
||||||
|
(Some(sl), Some(sr)) => {
|
||||||
|
// Match if section and name or address match
|
||||||
|
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
||||||
}
|
}
|
||||||
let name_matches = left.target.name == right.target.name;
|
(Some(_), None) => false,
|
||||||
match (&left.target_section, &right.target_section) {
|
(None, Some(_)) => {
|
||||||
(Some(sl), Some(sr)) => {
|
// Match if possibly stripped weak symbol
|
||||||
// Match if section and name or address match
|
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||||
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
|
||||||
}
|
|
||||||
(Some(_), None) => false,
|
|
||||||
(None, Some(_)) => {
|
|
||||||
// Match if possibly stripped weak symbol
|
|
||||||
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
|
||||||
}
|
|
||||||
(None, None) => name_matches,
|
|
||||||
}
|
}
|
||||||
} else {
|
(None, None) => name_matches,
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,8 +266,8 @@ fn arg_eq(
|
|||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ObjInsArg::MipsArg(ls) => {
|
ObjInsArg::MipsArg(ls) | ObjInsArg::MipsArgWithBase(ls) => {
|
||||||
matches!(right, ObjInsArg::MipsArg(rs) if ls == rs)
|
matches!(right, ObjInsArg::MipsArg(rs) | ObjInsArg::MipsArgWithBase(rs) if ls == rs)
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchOffset(_) => {
|
ObjInsArg::BranchOffset(_) => {
|
||||||
// Compare dest instruction idx after diffing
|
// Compare dest instruction idx after diffing
|
||||||
@@ -314,7 +322,7 @@ fn compare_ins(
|
|||||||
let a_str = match a {
|
let a_str = match a {
|
||||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||||
ObjInsArg::MipsArg(str) => str.clone(),
|
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||||
};
|
};
|
||||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
||||||
@@ -328,7 +336,7 @@ fn compare_ins(
|
|||||||
let b_str = match b {
|
let b_str = match b {
|
||||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||||
ObjInsArg::MipsArg(str) => str.clone(),
|
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||||
};
|
};
|
||||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
||||||
@@ -353,53 +361,56 @@ fn compare_ins(
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_section<'a>(obj: &'a mut ObjInfo, name: &str) -> Option<&'a mut ObjSection> {
|
fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)> {
|
||||||
obj.sections.iter_mut().find(|s| s.name == name)
|
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||||
|
let symbol_idx = match section.symbols.iter().position(|symbol| symbol.name == name) {
|
||||||
|
Some(symbol_idx) => symbol_idx,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
return Some((section_idx, symbol_idx));
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut ObjSymbol> {
|
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> {
|
||||||
symbols.iter_mut().find(|s| s.name == name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
|
|
||||||
for left_section in &mut left.sections {
|
for left_section in &mut left.sections {
|
||||||
if let Some(right_section) = find_section(right, &left_section.name) {
|
if left_section.kind == ObjSectionKind::Code {
|
||||||
if left_section.kind == ObjSectionKind::Code {
|
for left_symbol in &mut left_section.symbols {
|
||||||
for left_symbol in &mut left_section.symbols {
|
if let Some((right_section_idx, right_symbol_idx)) =
|
||||||
if let Some(right_symbol) =
|
find_section_and_symbol(right, &left_symbol.name)
|
||||||
find_symbol(&mut right_section.symbols, &left_symbol.name)
|
{
|
||||||
{
|
let right_section = &mut right.sections[right_section_idx];
|
||||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
let right_symbol = &mut right_section.symbols[right_symbol_idx];
|
||||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||||
diff_code(
|
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||||
left.architecture,
|
diff_code(
|
||||||
&left_section.data,
|
left.architecture,
|
||||||
&right_section.data,
|
&left_section.data,
|
||||||
left_symbol,
|
&right_section.data,
|
||||||
right_symbol,
|
left_symbol,
|
||||||
&left_section.relocations,
|
right_symbol,
|
||||||
&right_section.relocations,
|
&left_section.relocations,
|
||||||
)?;
|
&right_section.relocations,
|
||||||
} else {
|
&left.line_info,
|
||||||
no_diff_code(
|
&right.line_info,
|
||||||
left.architecture,
|
)?;
|
||||||
&left_section.data,
|
} else {
|
||||||
left_symbol,
|
no_diff_code(
|
||||||
&left_section.relocations,
|
left.architecture,
|
||||||
)?;
|
&left_section.data,
|
||||||
}
|
left_symbol,
|
||||||
|
&left_section.relocations,
|
||||||
|
&left.line_info,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
for right_symbol in &mut right_section.symbols {
|
}
|
||||||
if right_symbol.instructions.is_empty() {
|
} else {
|
||||||
no_diff_code(
|
let Some(right_section) =
|
||||||
left.architecture,
|
right.sections.iter_mut().find(|s| s.name == left_section.name)
|
||||||
&right_section.data,
|
else {
|
||||||
right_symbol,
|
continue;
|
||||||
&right_section.relocations,
|
};
|
||||||
)?;
|
if left_section.kind == ObjSectionKind::Data {
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if left_section.kind == ObjSectionKind::Data {
|
|
||||||
diff_data(left_section, right_section);
|
diff_data(left_section, right_section);
|
||||||
// diff_data_symbols(left_section, right_section)?;
|
// diff_data_symbols(left_section, right_section)?;
|
||||||
} else if left_section.kind == ObjSectionKind::Bss {
|
} else if left_section.kind == ObjSectionKind::Bss {
|
||||||
@@ -407,13 +418,26 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for right_section in right.sections.iter_mut().filter(|s| s.kind == ObjSectionKind::Code) {
|
||||||
|
for right_symbol in &mut right_section.symbols {
|
||||||
|
if right_symbol.instructions.is_empty() {
|
||||||
|
no_diff_code(
|
||||||
|
right.architecture,
|
||||||
|
&right_section.data,
|
||||||
|
right_symbol,
|
||||||
|
&right_section.relocations,
|
||||||
|
&right.line_info,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
diff_bss_symbols(&mut left.common, &mut right.common)?;
|
diff_bss_symbols(&mut left.common, &mut right.common)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_bss_symbols(left_symbols: &mut [ObjSymbol], right_symbols: &mut [ObjSymbol]) -> Result<()> {
|
fn diff_bss_symbols(left_symbols: &mut [ObjSymbol], right_symbols: &mut [ObjSymbol]) -> Result<()> {
|
||||||
for left_symbol in left_symbols {
|
for left_symbol in left_symbols {
|
||||||
if let Some(right_symbol) = find_symbol(right_symbols, &left_symbol.name) {
|
if let Some(right_symbol) = right_symbols.iter_mut().find(|s| s.name == left_symbol.name) {
|
||||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||||
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||||
@@ -500,13 +524,12 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
|||||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
let mut right_diff = Vec::<ObjDataDiff>::new();
|
||||||
let mut left_cur = 0usize;
|
let mut left_cur = 0usize;
|
||||||
let mut right_cur = 0usize;
|
let mut right_cur = 0usize;
|
||||||
let mut cur_op = LevEditType::Keep;
|
let mut cur_op = LevEditType::Replace;
|
||||||
let mut cur_left_data = Vec::<u8>::new();
|
let mut cur_left_data = Vec::<u8>::new();
|
||||||
let mut cur_right_data = Vec::<u8>::new();
|
let mut cur_right_data = Vec::<u8>::new();
|
||||||
for op in edit_ops {
|
for op in edit_ops {
|
||||||
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
|
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
|
||||||
match cur_op {
|
match cur_op {
|
||||||
LevEditType::Keep => {}
|
|
||||||
LevEditType::Replace => {
|
LevEditType::Replace => {
|
||||||
let left_data = take(&mut cur_left_data);
|
let left_data = take(&mut cur_left_data);
|
||||||
let right_data = take(&mut cur_right_data);
|
let right_data = take(&mut cur_right_data);
|
||||||
@@ -592,7 +615,6 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
|||||||
cur_left_data.push(left.data[left_cur]);
|
cur_left_data.push(left.data[left_cur]);
|
||||||
left_cur += 1;
|
left_cur += 1;
|
||||||
}
|
}
|
||||||
LevEditType::Keep => unreachable!(),
|
|
||||||
}
|
}
|
||||||
cur_op = op.op_type;
|
cur_op = op.op_type;
|
||||||
}
|
}
|
||||||
@@ -616,7 +638,6 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
|||||||
|
|
||||||
// TODO: merge with above
|
// TODO: merge with above
|
||||||
match cur_op {
|
match cur_op {
|
||||||
LevEditType::Keep => {}
|
|
||||||
LevEditType::Replace => {
|
LevEditType::Replace => {
|
||||||
let left_data = take(&mut cur_left_data);
|
let left_data = take(&mut cur_left_data);
|
||||||
let right_data = take(&mut cur_right_data);
|
let right_data = take(&mut cur_right_data);
|
||||||
|
|||||||
+57
-153
@@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
pub enum LevEditType {
|
pub enum LevEditType {
|
||||||
Keep,
|
|
||||||
Replace,
|
Replace,
|
||||||
Insert,
|
Insert,
|
||||||
Delete,
|
Delete,
|
||||||
@@ -40,25 +39,15 @@ pub struct LevEditOp {
|
|||||||
pub second_start: usize, /* destination position */
|
pub second_start: usize, /* destination position */
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct LevMatchingBlock {
|
|
||||||
pub first_start: usize,
|
|
||||||
pub second_start: usize,
|
|
||||||
pub len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
|
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
|
||||||
where T: PartialEq {
|
where T: PartialEq {
|
||||||
let string_affix = Affix::find(query, choice);
|
let Affix { prefix_len, suffix_len } = Affix::find(query, choice);
|
||||||
|
|
||||||
let first_string_len = string_affix.first_string_len;
|
let first_string = &query[prefix_len..query.len() - suffix_len];
|
||||||
let second_string_len = string_affix.second_string_len;
|
let second_string = &choice[prefix_len..choice.len() - suffix_len];
|
||||||
let prefix_len = string_affix.prefix_len;
|
|
||||||
let first_string = &query[prefix_len..prefix_len + first_string_len];
|
|
||||||
let second_string = &choice[prefix_len..prefix_len + second_string_len];
|
|
||||||
|
|
||||||
let matrix_columns = first_string_len + 1;
|
let matrix_columns = first_string.len() + 1;
|
||||||
let matrix_rows = second_string_len + 1;
|
let matrix_rows = second_string.len() + 1;
|
||||||
|
|
||||||
// TODO maybe use an actual matrix for readability
|
// TODO maybe use an actual matrix for readability
|
||||||
let mut cache_matrix: Vec<usize> = vec![0; matrix_rows * matrix_columns];
|
let mut cache_matrix: Vec<usize> = vec![0; matrix_rows * matrix_columns];
|
||||||
@@ -87,98 +76,66 @@ where T: PartialEq {
|
|||||||
cache_matrix[current + 1 + p] = x;
|
cache_matrix[current + 1 + p] = x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
editops_from_cost_matrix(
|
editops_from_cost_matrix(matrix_columns, matrix_rows, prefix_len, cache_matrix)
|
||||||
first_string,
|
|
||||||
second_string,
|
|
||||||
matrix_columns,
|
|
||||||
matrix_rows,
|
|
||||||
prefix_len,
|
|
||||||
cache_matrix,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editops_from_cost_matrix<T>(
|
fn editops_from_cost_matrix(
|
||||||
string1: &[T],
|
|
||||||
string2: &[T],
|
|
||||||
len1: usize,
|
len1: usize,
|
||||||
len2: usize,
|
len2: usize,
|
||||||
prefix_len: usize,
|
prefix_len: usize,
|
||||||
cache_matrix: Vec<usize>,
|
cache_matrix: Vec<usize>,
|
||||||
) -> Vec<LevEditOp>
|
) -> Vec<LevEditOp> {
|
||||||
where
|
let mut ops = Vec::with_capacity(cache_matrix[len1 * len2 - 1]);
|
||||||
T: PartialEq,
|
|
||||||
{
|
|
||||||
let mut dir = 0;
|
let mut dir = 0;
|
||||||
|
|
||||||
let mut ops: Vec<LevEditOp> = vec![];
|
|
||||||
ops.reserve(cache_matrix[len1 * len2 - 1]);
|
|
||||||
|
|
||||||
let mut i = len1 - 1;
|
let mut i = len1 - 1;
|
||||||
let mut j = len2 - 1;
|
let mut j = len2 - 1;
|
||||||
let mut p = len1 * len2 - 1;
|
let mut p = len1 * len2 - 1;
|
||||||
|
|
||||||
// let string1_chars: Vec<char> = string1.chars().collect();
|
|
||||||
// let string2_chars: Vec<char> = string2.chars().collect();
|
|
||||||
|
|
||||||
//TODO this is still pretty ugly
|
//TODO this is still pretty ugly
|
||||||
while i > 0 || j > 0 {
|
while i > 0 || j > 0 {
|
||||||
let current_value = cache_matrix[p];
|
let current_value = cache_matrix[p];
|
||||||
|
|
||||||
let op_type;
|
// More than one operation can be possible at a time. We use `dir` to
|
||||||
|
// decide when ambiguous.
|
||||||
|
let is_insert = j > 0 && current_value == cache_matrix[p - 1] + 1;
|
||||||
|
let is_delete = i > 0 && current_value == cache_matrix[p - len2] + 1;
|
||||||
|
let is_replace = i > 0 && j > 0 && current_value == cache_matrix[p - len2 - 1] + 1;
|
||||||
|
|
||||||
if dir == -1 && j > 0 && current_value == cache_matrix[p - 1] + 1 {
|
let (op_type, new_dir) = match (dir, is_insert, is_delete, is_replace) {
|
||||||
op_type = LevEditType::Insert;
|
(_, false, false, false) => (None, 0),
|
||||||
} else if dir == 1 && i > 0 && current_value == cache_matrix[p - len2] + 1 {
|
(-1, true, _, _) => (Some(LevEditType::Insert), -1),
|
||||||
op_type = LevEditType::Delete;
|
(1, _, true, _) => (Some(LevEditType::Delete), 1),
|
||||||
} else if i > 0
|
(_, _, _, true) => (Some(LevEditType::Replace), 0),
|
||||||
&& j > 0
|
(0, true, _, _) => (Some(LevEditType::Insert), -1),
|
||||||
&& current_value == cache_matrix[p - len2 - 1]
|
(0, _, true, _) => (Some(LevEditType::Delete), 1),
|
||||||
&& string1[i - 1] == string2[j - 1]
|
_ => panic!("something went terribly wrong"),
|
||||||
{
|
|
||||||
op_type = LevEditType::Keep;
|
|
||||||
} else if i > 0 && j > 0 && current_value == cache_matrix[p - len2 - 1] + 1 {
|
|
||||||
op_type = LevEditType::Replace;
|
|
||||||
}
|
|
||||||
/* we can't turn directly from -1 to 1, in this case it would be better
|
|
||||||
* to go diagonally, but check it (dir == 0) */
|
|
||||||
else if dir == 0 && j > 0 && current_value == cache_matrix[p - 1] + 1 {
|
|
||||||
op_type = LevEditType::Insert;
|
|
||||||
} else if dir == 0 && i > 0 && current_value == cache_matrix[p - len2] + 1 {
|
|
||||||
op_type = LevEditType::Delete;
|
|
||||||
} else {
|
|
||||||
panic!("something went terribly wrong");
|
|
||||||
}
|
|
||||||
|
|
||||||
match op_type {
|
|
||||||
LevEditType::Insert => {
|
|
||||||
j -= 1;
|
|
||||||
p -= 1;
|
|
||||||
dir = -1;
|
|
||||||
}
|
|
||||||
LevEditType::Delete => {
|
|
||||||
i -= 1;
|
|
||||||
p -= len2;
|
|
||||||
dir = 1;
|
|
||||||
}
|
|
||||||
LevEditType::Replace => {
|
|
||||||
i -= 1;
|
|
||||||
j -= 1;
|
|
||||||
p -= len2 + 1;
|
|
||||||
dir = 0;
|
|
||||||
}
|
|
||||||
LevEditType::Keep => {
|
|
||||||
i -= 1;
|
|
||||||
j -= 1;
|
|
||||||
p -= len2 + 1;
|
|
||||||
dir = 0;
|
|
||||||
/* LevEditKeep does not has to be stored */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let edit_op =
|
match new_dir {
|
||||||
LevEditOp { op_type, first_start: i + prefix_len, second_start: j + prefix_len };
|
-1 => {
|
||||||
ops.insert(0, edit_op);
|
j -= 1;
|
||||||
|
p -= 1;
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
i -= 1;
|
||||||
|
p -= len2;
|
||||||
|
}
|
||||||
|
0 => {
|
||||||
|
i -= 1;
|
||||||
|
j -= 1;
|
||||||
|
p -= len2 + 1;
|
||||||
|
}
|
||||||
|
_ => panic!("something went terribly wrong"),
|
||||||
|
};
|
||||||
|
dir = new_dir;
|
||||||
|
|
||||||
|
if let Some(op_type) = op_type {
|
||||||
|
ops.insert(0, LevEditOp {
|
||||||
|
op_type,
|
||||||
|
first_start: i + prefix_len,
|
||||||
|
second_start: j + prefix_len,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ops
|
ops
|
||||||
@@ -186,73 +143,20 @@ where
|
|||||||
|
|
||||||
pub struct Affix {
|
pub struct Affix {
|
||||||
pub prefix_len: usize,
|
pub prefix_len: usize,
|
||||||
pub first_string_len: usize,
|
pub suffix_len: usize,
|
||||||
pub second_string_len: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Affix {
|
impl Affix {
|
||||||
pub fn find<T>(first_string: &[T], second_string: &[T]) -> Affix
|
pub fn find<T>(s1: &[T], s2: &[T]) -> Affix
|
||||||
where T: PartialEq {
|
where T: PartialEq {
|
||||||
// remove common prefix and suffix (linear vs square runtime for levensthein)
|
let prefix_len = s1.iter().zip(s2.iter()).take_while(|t| t.0 == t.1).count();
|
||||||
let mut first_iter = first_string.iter();
|
let suffix_len = s1[prefix_len..]
|
||||||
let mut second_iter = second_string.iter();
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.zip(s2[prefix_len..].iter().rev())
|
||||||
|
.take_while(|t| t.0 == t.1)
|
||||||
|
.count();
|
||||||
|
|
||||||
let mut limit_start = 0;
|
Affix { prefix_len, suffix_len }
|
||||||
|
|
||||||
let mut first_iter_char = first_iter.next();
|
|
||||||
let mut second_iter_char = second_iter.next();
|
|
||||||
while first_iter_char.is_some() && first_iter_char == second_iter_char {
|
|
||||||
first_iter_char = first_iter.next();
|
|
||||||
second_iter_char = second_iter.next();
|
|
||||||
limit_start += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save char since the iterator was already consumed
|
|
||||||
let first_iter_cache = first_iter_char;
|
|
||||||
let second_iter_cache = second_iter_char;
|
|
||||||
|
|
||||||
if second_iter_char.is_some() && first_iter_char.is_some() {
|
|
||||||
first_iter_char = first_iter.next_back();
|
|
||||||
second_iter_char = second_iter.next_back();
|
|
||||||
while first_iter_char.is_some() && first_iter_char == second_iter_char {
|
|
||||||
first_iter_char = first_iter.next_back();
|
|
||||||
second_iter_char = second_iter.next_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match (first_iter_char, second_iter_char) {
|
|
||||||
(None, None) => {
|
|
||||||
// characters might not match even though they were consumed
|
|
||||||
let remaining_char = (first_iter_cache != second_iter_cache) as usize;
|
|
||||||
Affix {
|
|
||||||
prefix_len: limit_start,
|
|
||||||
first_string_len: remaining_char,
|
|
||||||
second_string_len: remaining_char,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, _) => {
|
|
||||||
let remaining_char =
|
|
||||||
(first_iter_cache.is_some() && first_iter_cache != second_iter_char) as usize;
|
|
||||||
Affix {
|
|
||||||
prefix_len: limit_start,
|
|
||||||
first_string_len: remaining_char,
|
|
||||||
second_string_len: second_iter.count() + 1 + remaining_char,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, None) => {
|
|
||||||
let remaining_char =
|
|
||||||
(second_iter_cache.is_some() && second_iter_cache != first_iter_char) as usize;
|
|
||||||
Affix {
|
|
||||||
prefix_len: limit_start,
|
|
||||||
first_string_len: first_iter.count() + 1 + remaining_char,
|
|
||||||
second_string_len: remaining_char,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Affix {
|
|
||||||
prefix_len: limit_start,
|
|
||||||
first_string_len: first_iter.count() + 2,
|
|
||||||
second_string_len: second_iter.count() + 2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
use std::sync::{mpsc::Receiver, Arc, RwLock};
|
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
app::{AppConfig, DiffConfig},
|
|
||||||
diff::diff_objs,
|
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
|
||||||
obj::{elf, ObjInfo},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BinDiffResult {
|
|
||||||
pub first_obj: ObjInfo,
|
|
||||||
pub second_obj: ObjInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_build(
|
|
||||||
status: &Status,
|
|
||||||
cancel: Receiver<()>,
|
|
||||||
config: Arc<RwLock<AppConfig>>,
|
|
||||||
) -> Result<Box<BinDiffResult>> {
|
|
||||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
|
||||||
let target_path =
|
|
||||||
config.left_obj.as_ref().ok_or_else(|| Error::msg("Missing target obj path"))?;
|
|
||||||
let base_path = config.right_obj.as_ref().ok_or_else(|| Error::msg("Missing base obj path"))?;
|
|
||||||
|
|
||||||
update_status(status, "Loading target obj".to_string(), 0, 3, &cancel)?;
|
|
||||||
let mut left_obj = elf::read(target_path)?;
|
|
||||||
|
|
||||||
update_status(status, "Loading base obj".to_string(), 1, 3, &cancel)?;
|
|
||||||
let mut right_obj = elf::read(base_path)?;
|
|
||||||
|
|
||||||
update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?;
|
|
||||||
diff_objs(&mut left_obj, &mut right_obj, &DiffConfig::default() /* TODO */)?;
|
|
||||||
|
|
||||||
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
|
|
||||||
Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
|
|
||||||
queue_job("Binary diff", Job::BinDiff, move |status, cancel| {
|
|
||||||
run_build(status, cancel, config).map(JobResult::BinDiff)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
|
|||||||
use self_update::{cargo_crate_version, update::Release};
|
use self_update::{cargo_crate_version, update::Release};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
update::{build_updater, BIN_NAME},
|
update::{build_updater, BIN_NAME},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ pub struct CheckUpdateResult {
|
|||||||
pub found_binary: bool,
|
pub found_binary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
fn run_check_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||||
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||||
let updater = build_updater().context("Failed to create release updater")?;
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
let latest_release = updater.get_latest_release()?;
|
let latest_release = updater.get_latest_release()?;
|
||||||
@@ -26,8 +26,8 @@ fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUp
|
|||||||
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_check_update() -> JobState {
|
pub fn start_check_update() -> JobState {
|
||||||
queue_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
start_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||||
run_check_update(status, cancel).map(JobResult::CheckUpdate)
|
run_check_update(status, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-18
@@ -9,12 +9,8 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::jobs::{
|
use crate::jobs::{check_update::CheckUpdateResult, objdiff::ObjDiffResult, update::UpdateResult};
|
||||||
bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult,
|
|
||||||
update::UpdateResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod bindiff;
|
|
||||||
pub mod check_update;
|
pub mod check_update;
|
||||||
pub mod objdiff;
|
pub mod objdiff;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
@@ -22,19 +18,88 @@ pub mod update;
|
|||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum Job {
|
pub enum Job {
|
||||||
ObjDiff,
|
ObjDiff,
|
||||||
BinDiff,
|
|
||||||
CheckUpdate,
|
CheckUpdate,
|
||||||
Update,
|
Update,
|
||||||
}
|
}
|
||||||
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct JobQueue {
|
||||||
|
pub jobs: Vec<JobState>,
|
||||||
|
pub results: Vec<JobResult>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JobQueue {
|
||||||
|
/// Adds a job to the queue.
|
||||||
|
#[inline]
|
||||||
|
pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
|
||||||
|
|
||||||
|
/// Adds a job to the queue if a job of the given kind is not already running.
|
||||||
|
#[inline]
|
||||||
|
pub fn push_once(&mut self, job: Job, func: impl FnOnce() -> JobState) {
|
||||||
|
if !self.is_running(job) {
|
||||||
|
self.push(func());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a job of the given kind is running.
|
||||||
|
pub fn is_running(&self, kind: Job) -> bool {
|
||||||
|
self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether any job is running.
|
||||||
|
pub fn any_running(&self) -> bool {
|
||||||
|
self.jobs.iter().any(|job| {
|
||||||
|
if let Some(handle) = &job.handle {
|
||||||
|
return !handle.is_finished();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates over all jobs mutably.
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut JobState> + '_ { self.jobs.iter_mut() }
|
||||||
|
|
||||||
|
/// Iterates over all finished jobs, returning the job state and the result.
|
||||||
|
pub fn iter_finished(
|
||||||
|
&mut self,
|
||||||
|
) -> impl Iterator<Item = (&mut JobState, std::thread::Result<JobResult>)> + '_ {
|
||||||
|
self.jobs.iter_mut().filter_map(|job| {
|
||||||
|
if let Some(handle) = &job.handle {
|
||||||
|
if !handle.is_finished() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let result = job.handle.take().unwrap().join();
|
||||||
|
return Some((job, result));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all finished jobs.
|
||||||
|
pub fn clear_finished(&mut self) {
|
||||||
|
self.jobs.retain(|job| {
|
||||||
|
!(job.should_remove
|
||||||
|
&& job.handle.is_none()
|
||||||
|
&& job.status.read().unwrap().error.is_none())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a job from the queue given its ID.
|
||||||
|
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type JobStatusRef = Arc<RwLock<JobStatus>>;
|
||||||
|
|
||||||
pub struct JobState {
|
pub struct JobState {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub job_type: Job,
|
pub kind: Job,
|
||||||
pub handle: Option<JoinHandle<JobResult>>,
|
pub handle: Option<JoinHandle<JobResult>>,
|
||||||
pub status: Arc<RwLock<JobStatus>>,
|
pub status: JobStatusRef,
|
||||||
pub cancel: Sender<()>,
|
pub cancel: Sender<()>,
|
||||||
pub should_remove: bool,
|
pub should_remove: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct JobStatus {
|
pub struct JobStatus {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
@@ -43,11 +108,11 @@ pub struct JobStatus {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
pub error: Option<anyhow::Error>,
|
pub error: Option<anyhow::Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum JobResult {
|
pub enum JobResult {
|
||||||
None,
|
None,
|
||||||
ObjDiff(Box<ObjDiffResult>),
|
ObjDiff(Option<Box<ObjDiffResult>>),
|
||||||
BinDiff(Box<BinDiffResult>),
|
CheckUpdate(Option<Box<CheckUpdateResult>>),
|
||||||
CheckUpdate(Box<CheckUpdateResult>),
|
|
||||||
Update(Box<UpdateResult>),
|
Update(Box<UpdateResult>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +123,10 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status = Arc<RwLock<JobStatus>>;
|
fn start_job(
|
||||||
|
|
||||||
fn queue_job(
|
|
||||||
title: &str,
|
title: &str,
|
||||||
job_type: Job,
|
kind: Job,
|
||||||
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
run: impl FnOnce(&JobStatusRef, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||||
) -> JobState {
|
) -> JobState {
|
||||||
let status = Arc::new(RwLock::new(JobStatus {
|
let status = Arc::new(RwLock::new(JobStatus {
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
@@ -89,7 +152,7 @@ fn queue_job(
|
|||||||
log::info!("Started job {}", id);
|
log::info!("Started job {}", id);
|
||||||
JobState {
|
JobState {
|
||||||
id,
|
id,
|
||||||
job_type,
|
kind,
|
||||||
handle: Some(handle),
|
handle: Some(handle),
|
||||||
status: status_clone,
|
status: status_clone,
|
||||||
cancel: tx,
|
cancel: tx,
|
||||||
@@ -98,7 +161,7 @@ fn queue_job(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_status(
|
fn update_status(
|
||||||
status: &Status,
|
status: &JobStatusRef,
|
||||||
str: String,
|
str: String,
|
||||||
count: u32,
|
count: u32,
|
||||||
total: u32,
|
total: u32,
|
||||||
|
|||||||
+35
-37
@@ -1,17 +1,12 @@
|
|||||||
use std::{
|
use std::{path::Path, process::Command, str::from_utf8, sync::mpsc::Receiver};
|
||||||
path::Path,
|
|
||||||
process::Command,
|
|
||||||
str::from_utf8,
|
|
||||||
sync::{mpsc::Receiver, Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{anyhow, Context, Error, Result};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, DiffConfig},
|
app::{AppConfig, AppConfigRef},
|
||||||
diff::diff_objs,
|
diff::diff_objs,
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
obj::{elf, ObjInfo},
|
obj::{elf, ObjInfo},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,68 +71,71 @@ fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_build(
|
fn run_build(
|
||||||
status: &Status,
|
status: &JobStatusRef,
|
||||||
cancel: Receiver<()>,
|
cancel: Receiver<()>,
|
||||||
config: Arc<RwLock<AppConfig>>,
|
config: AppConfigRef,
|
||||||
diff_config: DiffConfig,
|
|
||||||
) -> Result<Box<ObjDiffResult>> {
|
) -> Result<Box<ObjDiffResult>> {
|
||||||
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone();
|
||||||
let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
|
||||||
let project_dir =
|
let project_dir =
|
||||||
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
|
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||||
let mut target_path = config
|
let target_path_rel = obj_config.target_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
.target_obj_dir
|
anyhow!(
|
||||||
.as_ref()
|
"Target path '{}' doesn't begin with '{}'",
|
||||||
.ok_or_else(|| Error::msg("Missing target obj dir"))?
|
obj_config.target_path.display(),
|
||||||
.to_owned();
|
project_dir.display()
|
||||||
target_path.push(obj_path);
|
)
|
||||||
let mut base_path =
|
})?;
|
||||||
config.base_obj_dir.as_ref().ok_or_else(|| Error::msg("Missing base obj dir"))?.to_owned();
|
let base_path_rel = obj_config.base_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
base_path.push(obj_path);
|
anyhow!(
|
||||||
let target_path_rel = target_path
|
"Base path '{}' doesn't begin with '{}'",
|
||||||
.strip_prefix(project_dir)
|
obj_config.base_path.display(),
|
||||||
.context("Failed to create relative target obj path")?;
|
project_dir.display()
|
||||||
let base_path_rel =
|
)
|
||||||
base_path.strip_prefix(project_dir).context("Failed to create relative base obj path")?;
|
})?;
|
||||||
|
|
||||||
let total = if config.build_target { 5 } else { 4 };
|
let total = if config.build_target { 5 } else { 4 };
|
||||||
let first_status = if config.build_target {
|
let first_status = if config.build_target {
|
||||||
update_status(status, format!("Building target {obj_path}"), 0, total, &cancel)?;
|
update_status(status, format!("Building target {}", target_path_rel.display()), 0, total, &cancel)?;
|
||||||
run_make(project_dir, target_path_rel, &config)
|
run_make(project_dir, target_path_rel, &config)
|
||||||
} else {
|
} else {
|
||||||
BuildStatus { success: true, log: String::new() }
|
BuildStatus { success: true, log: String::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
update_status(status, format!("Building base {obj_path}"), 1, total, &cancel)?;
|
update_status(status, format!("Building base {}", base_path_rel.display()), 1, total, &cancel)?;
|
||||||
let second_status = run_make(project_dir, base_path_rel, &config);
|
let second_status = run_make(project_dir, base_path_rel, &config);
|
||||||
|
|
||||||
let time = OffsetDateTime::now_utc();
|
let time = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
let mut first_obj = if first_status.success {
|
let mut first_obj = if first_status.success {
|
||||||
update_status(status, format!("Loading target {obj_path}"), 2, total, &cancel)?;
|
update_status(status, format!("Loading target {}", target_path_rel.display()), 2, total, &cancel)?;
|
||||||
Some(elf::read(&target_path)?)
|
Some(elf::read(&obj_config.target_path).with_context(|| {
|
||||||
|
format!("Failed to read object '{}'", obj_config.target_path.display())
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut second_obj = if second_status.success {
|
let mut second_obj = if second_status.success {
|
||||||
update_status(status, format!("Loading base {obj_path}"), 3, total, &cancel)?;
|
update_status(status, format!("Loading base {}", base_path_rel.display()), 3, total, &cancel)?;
|
||||||
Some(elf::read(&base_path)?)
|
Some(elf::read(&obj_config.base_path).with_context(|| {
|
||||||
|
format!("Failed to read object '{}'", obj_config.base_path.display())
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) {
|
if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) {
|
||||||
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
|
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
|
||||||
diff_objs(first_obj, second_obj, &diff_config)?;
|
diff_objs(first_obj, second_obj)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
||||||
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
|
pub fn start_build(config: AppConfigRef) -> JobState {
|
||||||
queue_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
start_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||||
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
|
run_build(status, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
|||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
|
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||||
update::{build_updater, BIN_NAME},
|
update::{build_updater, BIN_NAME},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ pub struct UpdateResult {
|
|||||||
pub exe_path: PathBuf,
|
pub exe_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
fn run_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
||||||
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||||
let updater = build_updater().context("Failed to create release updater")?;
|
let updater = build_updater().context("Failed to create release updater")?;
|
||||||
let latest_release = updater.get_latest_release()?;
|
let latest_release = updater.get_latest_release()?;
|
||||||
@@ -53,8 +53,8 @@ fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>
|
|||||||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_update() -> JobState {
|
pub fn start_update() -> JobState {
|
||||||
queue_job("Update app", Job::Update, move |status, cancel| {
|
start_job("Update app", Job::Update, move |status, cancel| {
|
||||||
run_update(status, cancel).map(JobResult::Update)
|
run_update(status, cancel).map(JobResult::Update)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
pub use app::App;
|
pub use app::App;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
|
mod config;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod editops;
|
mod editops;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
|
|||||||
+10
-5
@@ -37,7 +37,8 @@ fn main() {
|
|||||||
|
|
||||||
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
|
||||||
let exec_path_clone = exec_path.clone();
|
let exec_path_clone = exec_path.clone();
|
||||||
let mut native_options = eframe::NativeOptions::default();
|
let mut native_options =
|
||||||
|
eframe::NativeOptions { follow_system_theme: false, ..Default::default() };
|
||||||
match load_icon() {
|
match load_icon() {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
native_options.icon_data = Some(data);
|
native_options.icon_data = Some(data);
|
||||||
@@ -46,12 +47,16 @@ fn main() {
|
|||||||
log::warn!("Failed to load application icon: {}", e);
|
log::warn!("Failed to load application icon: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// native_options.renderer = eframe::Renderer::Wgpu;
|
#[cfg(feature = "wgpu")]
|
||||||
|
{
|
||||||
|
native_options.renderer = eframe::Renderer::Wgpu;
|
||||||
|
}
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"objdiff",
|
"objdiff",
|
||||||
native_options,
|
native_options,
|
||||||
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset, exec_path_clone))),
|
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset, exec_path_clone))),
|
||||||
);
|
)
|
||||||
|
.expect("Failed to run eframe application");
|
||||||
|
|
||||||
// Attempt to relaunch application from the updated path
|
// Attempt to relaunch application from the updated path
|
||||||
if let Ok(mut guard) = exec_path.lock() {
|
if let Ok(mut guard) = exec_path.lock() {
|
||||||
@@ -61,7 +66,7 @@ fn main() {
|
|||||||
let result = exec::Command::new(path)
|
let result = exec::Command::new(path)
|
||||||
.args(&std::env::args().collect::<Vec<String>>())
|
.args(&std::env::args().collect::<Vec<String>>())
|
||||||
.exec();
|
.exec();
|
||||||
eprintln!("Failed to relaunch: {result:?}");
|
log::error!("Failed to relaunch: {result:?}");
|
||||||
} else {
|
} else {
|
||||||
let result = std::process::Command::new(path)
|
let result = std::process::Command::new(path)
|
||||||
.args(std::env::args())
|
.args(std::env::args())
|
||||||
@@ -69,7 +74,7 @@ fn main() {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.wait();
|
.wait();
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
eprintln!("Failed to relaunch: {:?}", e);
|
log::error!("Failed to relaunch: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-68
@@ -1,14 +1,11 @@
|
|||||||
use std::{fs, path::Path};
|
use std::{collections::BTreeMap, fs, io::Cursor, path::Path};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
use cwdemangle::demangle;
|
use cwdemangle::demangle;
|
||||||
use flagset::Flags;
|
use flagset::Flags;
|
||||||
use object::{
|
use object::{
|
||||||
elf::{
|
elf, Architecture, File, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget,
|
||||||
R_MIPS_26, R_MIPS_HI16, R_MIPS_LO16, R_PPC_ADDR16_HA, R_PPC_ADDR16_HI, R_PPC_ADDR16_LO,
|
|
||||||
R_PPC_EMB_SDA21, R_PPC_REL14, R_PPC_REL24,
|
|
||||||
},
|
|
||||||
Architecture, File, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget,
|
|
||||||
SectionIndex, SectionKind, Symbol, SymbolKind, SymbolSection,
|
SectionIndex, SectionKind, Symbol, SymbolKind, SymbolSection,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,19 +14,19 @@ use crate::obj::{
|
|||||||
ObjSymbolFlagSet, ObjSymbolFlags,
|
ObjSymbolFlagSet, ObjSymbolFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn to_obj_section_kind(kind: SectionKind) -> ObjSectionKind {
|
fn to_obj_section_kind(kind: SectionKind) -> Option<ObjSectionKind> {
|
||||||
match kind {
|
match kind {
|
||||||
SectionKind::Text => ObjSectionKind::Code,
|
SectionKind::Text => Some(ObjSectionKind::Code),
|
||||||
SectionKind::Data | SectionKind::ReadOnlyData => ObjSectionKind::Data,
|
SectionKind::Data | SectionKind::ReadOnlyData => Some(ObjSectionKind::Data),
|
||||||
SectionKind::UninitializedData => ObjSectionKind::Bss,
|
SectionKind::UninitializedData => Some(ObjSectionKind::Bss),
|
||||||
_ => panic!("Unhandled section kind {kind:?}"),
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> Result<ObjSymbol> {
|
fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> Result<ObjSymbol> {
|
||||||
let mut name = symbol.name().context("Failed to process symbol name")?;
|
let mut name = symbol.name().context("Failed to process symbol name")?;
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
println!("Found empty sym: {symbol:?}");
|
log::warn!("Found empty sym: {symbol:?}");
|
||||||
name = "?";
|
name = "?";
|
||||||
}
|
}
|
||||||
let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::none());
|
let mut flags = ObjSymbolFlagSet(ObjSymbolFlags::none());
|
||||||
@@ -73,18 +70,14 @@ fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
|
|||||||
if section.size() == 0 {
|
if section.size() == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if section.kind() != SectionKind::Text
|
let Some(kind) = to_obj_section_kind(section.kind()) else {
|
||||||
&& section.kind() != SectionKind::Data
|
|
||||||
&& section.kind() != SectionKind::ReadOnlyData
|
|
||||||
&& section.kind() != SectionKind::UninitializedData
|
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
let name = section.name().context("Failed to process section name")?;
|
let name = section.name().context("Failed to process section name")?;
|
||||||
let data = section.uncompressed_data().context("Failed to read section data")?;
|
let data = section.uncompressed_data().context("Failed to read section data")?;
|
||||||
result.push(ObjSection {
|
result.push(ObjSection {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
kind: to_obj_section_kind(section.kind()),
|
kind,
|
||||||
address: section.address(),
|
address: section.address(),
|
||||||
size: section.size(),
|
size: section.size(),
|
||||||
data: data.to_vec(),
|
data: data.to_vec(),
|
||||||
@@ -133,13 +126,11 @@ fn symbols_by_section(obj_file: &File<'_>, section: &ObjSection) -> Result<Vec<O
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn common_symbols(obj_file: &File<'_>) -> Result<Vec<ObjSymbol>> {
|
fn common_symbols(obj_file: &File<'_>) -> Result<Vec<ObjSymbol>> {
|
||||||
let mut result = Vec::<ObjSymbol>::new();
|
obj_file
|
||||||
for symbol in obj_file.symbols() {
|
.symbols()
|
||||||
if symbol.is_common() {
|
.filter(Symbol::is_common)
|
||||||
result.push(to_obj_symbol(obj_file, &symbol, 0)?);
|
.map(|symbol| to_obj_symbol(obj_file, &symbol, 0))
|
||||||
}
|
.collect::<Result<Vec<ObjSymbol>>>()
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_section_symbol(
|
fn find_section_symbol(
|
||||||
@@ -190,7 +181,7 @@ fn find_section_symbol(
|
|||||||
fn relocations_by_section(
|
fn relocations_by_section(
|
||||||
arch: ObjArchitecture,
|
arch: ObjArchitecture,
|
||||||
obj_file: &File<'_>,
|
obj_file: &File<'_>,
|
||||||
section: &mut ObjSection,
|
section: &ObjSection,
|
||||||
) -> Result<Vec<ObjReloc>> {
|
) -> Result<Vec<ObjReloc>> {
|
||||||
let obj_section = obj_file.section_by_index(SectionIndex(section.index))?;
|
let obj_section = obj_file.section_by_index(SectionIndex(section.index))?;
|
||||||
let mut relocations = Vec::<ObjReloc>::new();
|
let mut relocations = Vec::<ObjReloc>::new();
|
||||||
@@ -210,12 +201,12 @@ fn relocations_by_section(
|
|||||||
RelocationKind::Absolute => ObjRelocKind::Absolute,
|
RelocationKind::Absolute => ObjRelocKind::Absolute,
|
||||||
RelocationKind::Elf(kind) => match arch {
|
RelocationKind::Elf(kind) => match arch {
|
||||||
ObjArchitecture::PowerPc => match kind {
|
ObjArchitecture::PowerPc => match kind {
|
||||||
R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
|
elf::R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
|
||||||
R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
|
elf::R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
|
||||||
R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha,
|
elf::R_PPC_ADDR16_HA => ObjRelocKind::PpcAddr16Ha,
|
||||||
R_PPC_REL24 => ObjRelocKind::PpcRel24,
|
elf::R_PPC_REL24 => ObjRelocKind::PpcRel24,
|
||||||
R_PPC_REL14 => ObjRelocKind::PpcRel14,
|
elf::R_PPC_REL14 => ObjRelocKind::PpcRel14,
|
||||||
R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
|
elf::R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(anyhow::Error::msg(format!(
|
return Err(anyhow::Error::msg(format!(
|
||||||
"Unhandled PPC relocation type: {kind}"
|
"Unhandled PPC relocation type: {kind}"
|
||||||
@@ -223,14 +214,14 @@ fn relocations_by_section(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ObjArchitecture::Mips => match kind {
|
ObjArchitecture::Mips => match kind {
|
||||||
R_MIPS_26 => ObjRelocKind::Mips26,
|
elf::R_MIPS_26 => ObjRelocKind::Mips26,
|
||||||
R_MIPS_HI16 => ObjRelocKind::MipsHi16,
|
elf::R_MIPS_HI16 => ObjRelocKind::MipsHi16,
|
||||||
R_MIPS_LO16 => ObjRelocKind::MipsLo16,
|
elf::R_MIPS_LO16 => ObjRelocKind::MipsLo16,
|
||||||
_ => {
|
elf::R_MIPS_GOT16 => ObjRelocKind::MipsGot16,
|
||||||
return Err(anyhow::Error::msg(format!(
|
elf::R_MIPS_CALL16 => ObjRelocKind::MipsCall16,
|
||||||
"Unhandled MIPS relocation type: {kind}"
|
elf::R_MIPS_GPREL16 => ObjRelocKind::MipsGpRel16,
|
||||||
)))
|
elf::R_MIPS_GPREL32 => ObjRelocKind::MipsGpRel32,
|
||||||
}
|
_ => bail!("Unhandled MIPS relocation type: {kind}"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
@@ -247,43 +238,68 @@ fn relocations_by_section(
|
|||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
// println!("Reloc: {:?}, symbol: {:?}", reloc, symbol);
|
let addend = if reloc.has_implicit_addend() {
|
||||||
|
let addend = u32::from_be_bytes(
|
||||||
|
section.data[address as usize..address as usize + 4].try_into()?,
|
||||||
|
);
|
||||||
|
match kind {
|
||||||
|
ObjRelocKind::Absolute => addend as i64,
|
||||||
|
ObjRelocKind::MipsHi16 => ((addend & 0x0000FFFF) << 16) as i32 as i64,
|
||||||
|
ObjRelocKind::MipsLo16
|
||||||
|
| ObjRelocKind::MipsGot16
|
||||||
|
| ObjRelocKind::MipsCall16
|
||||||
|
| ObjRelocKind::MipsGpRel16 => (addend & 0x0000FFFF) as i16 as i64,
|
||||||
|
ObjRelocKind::MipsGpRel32 => addend as i32 as i64,
|
||||||
|
ObjRelocKind::Mips26 => ((addend & 0x03FFFFFF) << 2) as i64,
|
||||||
|
_ => bail!("Unsupported implicit relocation {kind:?}"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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::Unknown => {
|
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
|
||||||
to_obj_symbol(obj_file, &symbol, reloc.addend())
|
to_obj_symbol(obj_file, &symbol, addend)
|
||||||
}
|
}
|
||||||
SymbolKind::Section => {
|
SymbolKind::Section => {
|
||||||
let addend = if reloc.has_implicit_addend() {
|
if addend < 0 {
|
||||||
let addend = u32::from_be_bytes(
|
return Err(anyhow::Error::msg(format!("Negative addend in reloc: {addend}")));
|
||||||
section.data[address as usize..address as usize + 4].try_into()?,
|
}
|
||||||
);
|
|
||||||
match kind {
|
|
||||||
ObjRelocKind::Absolute => addend,
|
|
||||||
ObjRelocKind::MipsHi16 | ObjRelocKind::MipsLo16 => addend & 0x0000FFFF,
|
|
||||||
ObjRelocKind::Mips26 => (addend & 0x03FFFFFF) * 4,
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let addend = reloc.addend();
|
|
||||||
if addend < 0 {
|
|
||||||
return Err(anyhow::Error::msg(format!(
|
|
||||||
"Negative addend in section reloc: {addend}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
addend as u32
|
|
||||||
};
|
|
||||||
find_section_symbol(obj_file, &symbol, addend as u64)
|
find_section_symbol(obj_file, &symbol, addend as u64)
|
||||||
}
|
}
|
||||||
_ => Err(anyhow::Error::msg(format!(
|
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
|
||||||
"Unhandled relocation symbol type {:?}",
|
|
||||||
symbol.kind()
|
|
||||||
))),
|
|
||||||
}?;
|
}?;
|
||||||
relocations.push(ObjReloc { kind, address, target, target_section });
|
relocations.push(ObjReloc { kind, address, target, target_section });
|
||||||
}
|
}
|
||||||
Ok(relocations)
|
Ok(relocations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u32, u32>>> {
|
||||||
|
if let Some(section) = obj_file.section_by_name(".line") {
|
||||||
|
if section.size() == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let data = section.uncompressed_data()?;
|
||||||
|
let mut reader = Cursor::new(data.as_ref());
|
||||||
|
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
let size = reader.read_u32::<BigEndian>()?;
|
||||||
|
let base_address = reader.read_u32::<BigEndian>()?;
|
||||||
|
while reader.position() < size as u64 {
|
||||||
|
let line_number = reader.read_u32::<BigEndian>()?;
|
||||||
|
let statement_pos = reader.read_u16::<BigEndian>()?;
|
||||||
|
if statement_pos != 0xFFFF {
|
||||||
|
log::warn!("Unhandled statement pos {}", statement_pos);
|
||||||
|
}
|
||||||
|
let address_delta = reader.read_u32::<BigEndian>()?;
|
||||||
|
map.insert(base_address + address_delta, line_number);
|
||||||
|
}
|
||||||
|
log::debug!("Line info: {map:#X?}");
|
||||||
|
return Ok(Some(map));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
||||||
let data = {
|
let data = {
|
||||||
let file = fs::File::open(obj_path)?;
|
let file = fs::File::open(obj_path)?;
|
||||||
@@ -305,6 +321,7 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
|
|||||||
path: obj_path.to_owned(),
|
path: obj_path.to_owned(),
|
||||||
sections: filter_sections(&obj_file)?,
|
sections: filter_sections(&obj_file)?,
|
||||||
common: common_symbols(&obj_file)?,
|
common: common_symbols(&obj_file)?,
|
||||||
|
line_info: line_info(&obj_file)?,
|
||||||
};
|
};
|
||||||
for section in &mut result.sections {
|
for section in &mut result.sections {
|
||||||
section.symbols = symbols_by_section(&obj_file, section)?;
|
section.symbols = symbols_by_section(&obj_file, section)?;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user