mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83de98b5ee | |||
| c1ba4e91d1 | |||
| 575900024d | |||
| cbe299e859 | |||
| 741d93e211 | |||
| 603dbd6882 | |||
| 6fb0a63de2 | |||
| ab2e84a2c6 | |||
| 9596051cb4 |
@@ -142,7 +142,11 @@ 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:
|
||||||
|
|||||||
Generated
+13
-3
@@ -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"
|
||||||
@@ -2852,7 +2861,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objdiff-cli"
|
name = "objdiff-cli"
|
||||||
version = "2.2.2"
|
version = "2.3.1"
|
||||||
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.1"
|
||||||
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.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.2.2"
|
version = "2.3.1"
|
||||||
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+33
-21
@@ -102,26 +102,32 @@ 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
|
||||||
if obj.name.as_deref() == Some(u) {
|
.units
|
||||||
|
.as_deref_mut()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter_mut()
|
||||||
|
.find_map(|obj| {
|
||||||
|
if obj.name.as_deref() == Some(u) {
|
||||||
|
resolve_paths(obj);
|
||||||
|
return Some(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let up = unit_path.as_deref()?;
|
||||||
|
|
||||||
resolve_paths(obj);
|
resolve_paths(obj);
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
let up = unit_path.as_deref()?;
|
if [&obj.base_path, &obj.target_path]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
||||||
|
.any(|p| p == up)
|
||||||
|
{
|
||||||
|
return Some(obj);
|
||||||
|
}
|
||||||
|
|
||||||
resolve_paths(obj);
|
None
|
||||||
|
})
|
||||||
if [&obj.base_path, &obj.target_path]
|
else {
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
|
||||||
.any(|p| p == up)
|
|
||||||
{
|
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}) 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,8 +17,8 @@ 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 = ["bimap"] # Implicit, used to check if any arch is enabled
|
||||||
config = ["globset", "semver", "serde_json", "serde_yaml"]
|
config = ["bimap", "globset", "semver", "serde_json", "serde_yaml"]
|
||||||
dwarf = ["gimli"]
|
dwarf = ["gimli"]
|
||||||
mips = ["any-arch", "rabbitizer"]
|
mips = ["any-arch", "rabbitizer"]
|
||||||
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
|
||||||
@@ -32,6 +32,7 @@ features = ["all"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||||
byteorder = "1.5"
|
byteorder = "1.5"
|
||||||
filetime = "0.2"
|
filetime = "0.2"
|
||||||
flagset = "0.4"
|
flagset = "0.4"
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ 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(
|
||||||
@@ -67,7 +67,7 @@ pub fn diff_code(
|
|||||||
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 +77,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),
|
||||||
},
|
},
|
||||||
@@ -211,7 +211,7 @@ fn arg_eq(
|
|||||||
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,
|
||||||
@@ -236,7 +236,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)]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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::{
|
||||||
@@ -161,6 +162,8 @@ pub struct DiffObjConfig {
|
|||||||
#[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 +185,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 +227,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 +300,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 +314,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,
|
||||||
});
|
});
|
||||||
@@ -328,7 +340,7 @@ 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: obj.sections.len(), symbol_idx },
|
||||||
diff_symbol: None,
|
target_symbol: None,
|
||||||
instructions: vec![],
|
instructions: vec![],
|
||||||
match_percent: None,
|
match_percent: None,
|
||||||
});
|
});
|
||||||
@@ -378,7 +390,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)));
|
||||||
@@ -529,6 +541,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 +559,63 @@ 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_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 +631,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 +751,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: left.sections.len(), 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,
|
||||||
|
|||||||
@@ -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,6 +129,7 @@ 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 addend: i64,
|
||||||
/// Original virtual address (from .note.split section)
|
/// Original virtual address (from .note.split section)
|
||||||
|
|||||||
+102
-40
@@ -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},
|
||||||
};
|
};
|
||||||
@@ -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,6 +109,7 @@ 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,
|
addend,
|
||||||
virtual_address,
|
virtual_address,
|
||||||
@@ -152,26 +161,23 @@ 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 symbol.is_local() && section.kind == ObjSectionKind::Code {
|
||||||
if index.0 == section.orig_index {
|
// TODO strip local syms in diff?
|
||||||
if symbol.is_local() && section.kind == ObjSectionKind::Code {
|
let name = symbol.name().context("Failed to process symbol name")?;
|
||||||
// TODO strip local syms in diff?
|
if symbol.size() == 0 || name.starts_with("lbl_") {
|
||||||
let name = symbol.name().context("Failed to process symbol name")?;
|
continue;
|
||||||
if symbol.size() == 0 || name.starts_with("lbl_") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.push(to_obj_symbol(arch, obj_file, &symbol, 0, split_meta)?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
result.push(to_obj_symbol(arch, obj_file, symbol, 0, 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,6 +212,10 @@ 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,
|
addend: 0,
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
@@ -221,47 +238,72 @@ fn common_symbols(
|
|||||||
.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 closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
|
||||||
|
Ok(index) => Some(index),
|
||||||
|
Err(index) => index.checked_sub(1),
|
||||||
|
}?;
|
||||||
|
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<'_>,
|
||||||
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
target: &Symbol<'_, '_>,
|
target: &Symbol<'_, '_>,
|
||||||
address: u64,
|
address: u64,
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
) -> Result<ObjSymbol> {
|
) -> Result<ObjSymbol> {
|
||||||
|
if let Some(symbol) = best_symbol(section_symbols, address) {
|
||||||
|
return to_obj_symbol(
|
||||||
|
arch,
|
||||||
|
obj_file,
|
||||||
|
symbol,
|
||||||
|
address as i64 - symbol.address() as i64,
|
||||||
|
split_meta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Fallback to section symbol
|
||||||
let section_index =
|
let section_index =
|
||||||
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
|
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
|
||||||
let section = obj_file.section_by_index(section_index)?;
|
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 {
|
|
||||||
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,
|
addend: address as i64 - section.address() as i64,
|
||||||
virtual_address: None,
|
virtual_address: None,
|
||||||
original_index: None,
|
original_index: None,
|
||||||
bytes: Vec::new(),
|
bytes: Vec::new(),
|
||||||
@@ -272,6 +314,7 @@ fn relocations_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>,
|
||||||
) -> 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))?;
|
||||||
@@ -315,7 +358,14 @@ fn relocations_by_section(
|
|||||||
}
|
}
|
||||||
SymbolKind::Section => {
|
SymbolKind::Section => {
|
||||||
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
|
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
|
||||||
find_section_symbol(arch, obj_file, &symbol, addend as u64, split_meta)
|
find_section_symbol(
|
||||||
|
arch,
|
||||||
|
obj_file,
|
||||||
|
section_symbols,
|
||||||
|
&symbol,
|
||||||
|
addend as u64,
|
||||||
|
split_meta,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
|
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
|
||||||
}?;
|
}?;
|
||||||
@@ -539,6 +589,7 @@ 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,
|
addend: symbol.addend,
|
||||||
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
|
||||||
@@ -650,15 +701,26 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
|||||||
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 name_counts: HashMap<String, u32> = HashMap::new();
|
||||||
for section in &mut sections {
|
for section in &mut sections {
|
||||||
|
let mut symbols = obj_file
|
||||||
|
.symbols()
|
||||||
|
.filter(|s| s.section_index() == Some(SectionIndex(section.orig_index)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
symbols.sort_by_key(|s| s.address());
|
||||||
section.symbols = symbols_by_section(
|
section.symbols = symbols_by_section(
|
||||||
arch.as_ref(),
|
arch.as_ref(),
|
||||||
&obj_file,
|
&obj_file,
|
||||||
section,
|
section,
|
||||||
|
&symbols,
|
||||||
split_meta.as_ref(),
|
split_meta.as_ref(),
|
||||||
&mut name_counts,
|
&mut name_counts,
|
||||||
)?;
|
)?;
|
||||||
section.relocations =
|
section.relocations = relocations_by_section(
|
||||||
relocations_by_section(arch.as_ref(), &obj_file, section, split_meta.as_ref())?;
|
arch.as_ref(),
|
||||||
|
&obj_file,
|
||||||
|
section,
|
||||||
|
&symbols,
|
||||||
|
split_meta.as_ref(),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
if config.combine_data_sections {
|
if config.combine_data_sections {
|
||||||
combine_data_sections(&mut sections)?;
|
combine_data_sections(&mut sections)?;
|
||||||
|
|||||||
+239
-38
@@ -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.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.obj_change = true;
|
||||||
self.queue_build = false;
|
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;
|
||||||
|
|
||||||
if let Some(info) = &state.project_config_info {
|
let mut mod_check = false;
|
||||||
if file_modified(&info.path, info.timestamp) {
|
if state.last_mod_check.elapsed().as_millis() >= 500 {
|
||||||
state.config_change = true;
|
state.last_mod_check = Instant::now();
|
||||||
|
mod_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod_check {
|
||||||
|
if let Some(info) = &state.project_config_info {
|
||||||
|
if let Some(last_ts) = info.timestamp {
|
||||||
|
if file_modified(&info.path, last_ts) {
|
||||||
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-18
@@ -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
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user