Compare commits

...

8 Commits

Author SHA1 Message Date
bb9ff4b928 Update all dependencies 2023-10-05 23:55:01 -04:00
57392daaeb Implement click-to-highlight
Highlights registers, instructions, arguments, symbols or addresses on click.

Resolves #7
2023-10-05 23:40:45 -04:00
2dd3dd60a8 Update webpki (advisory fix) 2023-10-05 00:01:09 -04:00
f4757b8d92 Version 0.4.4
Add `#[serde(default)]` to new AppConfig field
2023-10-04 23:52:00 -04:00
52f8c5d4f9 Add "Recent Projects" to file menu 2023-10-03 13:52:16 -04:00
711f40b591 I forgot to bump the Cargo.toml version, oops 2023-09-10 00:24:53 -04:00
26932b2e44 Support min_version field in objdiff.json 2023-09-09 23:54:25 -04:00
192a06bc0b Project configuration improvements
- Support `completed` field for objects in project config. In object tree, displays red for incomplete, green for complete.
- Add support for one-sided diffs. A project can include objects without an associated source file for viewing.
- Add versioning to AppConfig, supporting upgrades without losing user configuration.
2023-09-09 23:43:12 -04:00
15 changed files with 1320 additions and 666 deletions

866
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[package] [package]
name = "objdiff" name = "objdiff"
version = "0.4.1" version = "0.5.0"
edition = "2021" edition = "2021"
rust-version = "1.65" rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff" repository = "https://github.com/encounter/objdiff"
@@ -22,44 +22,46 @@ default = []
wgpu = ["eframe/wgpu"] wgpu = ["eframe/wgpu"]
[dependencies] [dependencies]
anyhow = "1.0.71" anyhow = "1.0.75"
byteorder = "1.4.3" byteorder = "1.5.0"
bytes = "1.4.0" bytes = "1.5.0"
cfg-if = "1.0.0" cfg-if = "1.0.0"
const_format = "0.2.31" const_format = "0.2.31"
cwdemangle = "0.1.6" cwdemangle = "0.1.6"
dirs = "5.0.1" dirs = "5.0.1"
eframe = { version = "0.22.0", features = ["persistence"] } eframe = { version = "0.23.0", features = ["persistence"] }
egui = "0.22.0" egui = "0.23.0"
egui_extras = "0.22.0" egui_extras = "0.23.0"
flagset = "0.4.3" flagset = "0.4.4"
globset = { version = "0.4.13", features = ["serde1"] } globset = { version = "0.4.13", features = ["serde1"] }
log = "0.4.19" log = "0.4.20"
memmap2 = "0.7.1" memmap2 = "0.9.0"
notify = "6.0.1" notify = "6.1.1"
object = { version = "0.31.1", features = ["read_core", "std", "elf"], default-features = false } object = { version = "0.32.1", features = ["read_core", "std", "elf"], default-features = false }
png = "0.17.9" png = "0.17.10"
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" } ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
rabbitizer = "1.7.4" rabbitizer = "1.7.10"
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal'] rfd = { version = "0.12.0" } #, default-features = false, features = ['xdg-portal']
ron = "0.8.1"
semver = "1.0.19"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0.104" serde_json = "1.0.107"
serde_yaml = "0.9.25" serde_yaml = "0.9.25"
tempfile = "3.6.0" tempfile = "3.8.0"
thiserror = "1.0.41" thiserror = "1.0.49"
time = { version = "0.3.22", features = ["formatting", "local-offset"] } time = { version = "0.3.29", features = ["formatting", "local-offset"] }
toml = "0.7.6" toml = "0.8.2"
twox-hash = "1.6.3" twox-hash = "1.6.3"
# For Linux static binaries, use rustls # For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.18", default-features = false, features = ["blocking", "json", "rustls"] } reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "json", "rustls"] }
self_update = { version = "0.37.0", default-features = false, features = ["rustls"] } self_update = { version = "0.38.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS # For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = "0.11.18" reqwest = "0.11.22"
self_update = "0.37.0" self_update = "0.38.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
path-slash = "0.2.1" path-slash = "0.2.1"
@@ -81,5 +83,5 @@ console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2" tracing-wasm = "0.2"
[build-dependencies] [build-dependencies]
anyhow = "1.0.71" anyhow = "1.0.75"
vergen = { version = "8.2.4", features = ["build", "cargo", "git", "gitcl"] } vergen = { version = "8.2.5", features = ["build", "cargo", "git", "gitcl"] }

View File

@@ -14,13 +14,14 @@ use notify::{RecursiveMode, Watcher};
use time::UtcOffset; use time::UtcOffset;
use crate::{ use crate::{
app_config::{deserialize_config, AppConfigVersion},
config::{ config::{
build_globset, load_project_config, ProjectObject, ProjectObjectNode, CONFIG_FILENAMES, build_globset, load_project_config, ProjectObject, ProjectObjectNode, CONFIG_FILENAMES,
}, },
jobs::{objdiff::start_build, Job, JobQueue, JobResult, JobStatus}, jobs::{objdiff::start_build, Job, JobQueue, JobResult, JobStatus},
views::{ views::{
appearance::{appearance_window, Appearance}, appearance::{appearance_window, Appearance},
config::{config_ui, project_window, ConfigViewState}, config::{config_ui, project_window, ConfigViewState, DEFAULT_WATCH_PATTERNS},
data_diff::data_diff_ui, data_diff::data_diff_ui,
demangle::{demangle_window, DemangleViewState}, demangle::{demangle_window, DemangleViewState},
function_diff::function_diff_ui, function_diff::function_diff_ui,
@@ -44,27 +45,48 @@ pub struct ViewState {
#[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ObjectConfig { pub struct ObjectConfig {
pub name: String, pub name: String,
pub target_path: PathBuf, pub target_path: Option<PathBuf>,
pub base_path: PathBuf, pub base_path: Option<PathBuf>,
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
pub complete: Option<bool>,
} }
#[inline] #[inline]
fn bool_true() -> bool { true } fn bool_true() -> bool { true }
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] #[inline]
fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct AppConfig { pub struct AppConfig {
// TODO: https://github.com/ron-rs/ron/pull/455
// #[serde(flatten)]
// pub version: AppConfigVersion,
pub version: u32,
#[serde(default)]
pub custom_make: Option<String>, pub custom_make: Option<String>,
#[serde(default)]
pub selected_wsl_distro: Option<String>, pub selected_wsl_distro: Option<String>,
#[serde(default)]
pub project_dir: Option<PathBuf>, pub project_dir: Option<PathBuf>,
#[serde(default)]
pub target_obj_dir: Option<PathBuf>, pub target_obj_dir: Option<PathBuf>,
#[serde(default)]
pub base_obj_dir: Option<PathBuf>, pub base_obj_dir: Option<PathBuf>,
#[serde(default)]
pub selected_obj: Option<ObjectConfig>, pub selected_obj: Option<ObjectConfig>,
#[serde(default)]
pub build_target: bool, pub build_target: bool,
#[serde(default = "bool_true")] #[serde(default = "bool_true")]
pub rebuild_on_changes: bool, pub rebuild_on_changes: bool,
#[serde(default)]
pub auto_update_check: bool, pub auto_update_check: bool,
#[serde(default = "default_watch_patterns")]
pub watch_patterns: Vec<Glob>, pub watch_patterns: Vec<Glob>,
#[serde(default)]
pub recent_projects: Vec<PathBuf>,
#[serde(skip)] #[serde(skip)]
pub objects: Vec<ProjectObject>, pub objects: Vec<ProjectObject>,
@@ -82,8 +104,39 @@ pub struct AppConfig {
pub project_config_loaded: bool, pub project_config_loaded: bool,
} }
impl Default for AppConfig {
fn default() -> Self {
Self {
version: AppConfigVersion::default().version,
custom_make: None,
selected_wsl_distro: None,
project_dir: None,
target_obj_dir: None,
base_obj_dir: None,
selected_obj: None,
build_target: false,
rebuild_on_changes: true,
auto_update_check: true,
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
recent_projects: vec![],
objects: vec![],
object_nodes: vec![],
watcher_change: false,
config_change: false,
obj_change: false,
queue_build: false,
project_config_loaded: false,
}
}
}
impl AppConfig { impl AppConfig {
pub fn set_project_dir(&mut self, path: PathBuf) { pub fn set_project_dir(&mut self, path: PathBuf) {
self.recent_projects.retain(|p| p != &path);
if self.recent_projects.len() > 9 {
self.recent_projects.truncate(9);
}
self.recent_projects.insert(0, path.clone());
self.project_dir = Some(path); self.project_dir = Some(path);
self.target_obj_dir = None; self.target_obj_dir = None;
self.base_obj_dir = None; self.base_obj_dir = None;
@@ -133,8 +186,8 @@ pub struct App {
should_relaunch: bool, should_relaunch: bool,
} }
const APPEARANCE_KEY: &str = "appearance"; pub const APPEARANCE_KEY: &str = "appearance";
const CONFIG_KEY: &str = "app_config"; pub const CONFIG_KEY: &str = "app_config";
impl App { impl App {
/// Called once before the first frame. /// Called once before the first frame.
@@ -153,7 +206,7 @@ impl App {
if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) { if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) {
app.appearance = appearance; app.appearance = appearance;
} }
if let Some(mut config) = eframe::get_value::<AppConfig>(storage, CONFIG_KEY) { if let Some(mut config) = deserialize_config(storage) {
if config.project_dir.is_some() { if config.project_dir.is_some() {
config.config_change = true; config.config_change = true;
config.watcher_change = true; config.watcher_change = true;
@@ -320,6 +373,23 @@ impl eframe::App for App {
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| {
ui.menu_button("File", |ui| { ui.menu_button("File", |ui| {
let recent_projects = if let Ok(guard) = config.read() {
guard.recent_projects.clone()
} else {
vec![]
};
if recent_projects.is_empty() {
ui.add_enabled(false, egui::Button::new("Recent Projects…"));
} else {
ui.menu_button("Recent Projects…", |ui| {
for path in recent_projects {
if ui.button(format!("{}", path.display())).clicked() {
config.write().unwrap().set_project_dir(path);
ui.close_menu();
}
}
});
}
if ui.button("Appearance…").clicked() { if ui.button("Appearance…").clicked() {
*show_appearance_config = !*show_appearance_config; *show_appearance_config = !*show_appearance_config;
ui.close_menu(); ui.close_menu();

96
src/app_config.rs Normal file
View File

@@ -0,0 +1,96 @@
use std::path::PathBuf;
use eframe::Storage;
use globset::Glob;
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct AppConfigVersion {
pub version: u32,
}
impl Default for AppConfigVersion {
fn default() -> Self { Self { version: 1 } }
}
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
pub fn deserialize_config(storage: &dyn Storage) -> Option<AppConfig> {
let str = storage.get_string(CONFIG_KEY)?;
match ron::from_str::<AppConfigVersion>(&str) {
Ok(version) => match version.version {
1 => from_str::<AppConfig>(&str),
_ => {
log::warn!("Unknown config version: {}", version.version);
None
}
},
Err(e) => {
log::warn!("Failed to decode config version: {e}");
// Try to decode as v0
from_str::<AppConfigV0>(&str).map(|c| c.into_config())
}
}
}
fn from_str<T>(str: &str) -> Option<T>
where T: serde::de::DeserializeOwned {
match ron::from_str(str) {
Ok(config) => Some(config),
Err(err) => {
log::warn!("Failed to decode config: {err}");
None
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ObjectConfigV0 {
pub name: String,
pub target_path: PathBuf,
pub base_path: PathBuf,
pub reverse_fn_order: Option<bool>,
}
impl ObjectConfigV0 {
fn into_config(self) -> ObjectConfig {
ObjectConfig {
name: self.name,
target_path: Some(self.target_path),
base_path: Some(self.base_path),
reverse_fn_order: self.reverse_fn_order,
complete: None,
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct AppConfigV0 {
pub custom_make: Option<String>,
pub selected_wsl_distro: Option<String>,
pub project_dir: Option<PathBuf>,
pub target_obj_dir: Option<PathBuf>,
pub base_obj_dir: Option<PathBuf>,
pub selected_obj: Option<ObjectConfigV0>,
pub build_target: bool,
pub auto_update_check: bool,
pub watch_patterns: Vec<Glob>,
}
impl AppConfigV0 {
fn into_config(self) -> AppConfig {
log::info!("Upgrading configuration from v0");
AppConfig {
custom_make: self.custom_make,
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_target: self.build_target,
auto_update_check: self.auto_update_check,
watch_patterns: self.watch_patterns,
..Default::default()
}
}
}

View File

@@ -3,7 +3,7 @@ use std::{
path::{Component, Path, PathBuf}, path::{Component, Path, PathBuf},
}; };
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use globset::{Glob, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobSet, GlobSetBuilder};
use crate::{app::AppConfig, views::config::DEFAULT_WATCH_PATTERNS}; use crate::{app::AppConfig, views::config::DEFAULT_WATCH_PATTERNS};
@@ -11,6 +11,7 @@ use crate::{app::AppConfig, views::config::DEFAULT_WATCH_PATTERNS};
#[derive(Default, Clone, serde::Deserialize)] #[derive(Default, Clone, serde::Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProjectConfig { pub struct ProjectConfig {
pub min_version: Option<String>,
pub custom_make: Option<String>, pub custom_make: Option<String>,
pub target_dir: Option<PathBuf>, pub target_dir: Option<PathBuf>,
pub base_dir: Option<PathBuf>, pub base_dir: Option<PathBuf>,
@@ -27,6 +28,7 @@ pub struct ProjectObject {
pub target_path: Option<PathBuf>, pub target_path: Option<PathBuf>,
pub base_path: Option<PathBuf>, pub base_path: Option<PathBuf>,
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
pub complete: Option<bool>,
} }
impl ProjectObject { impl ProjectObject {
@@ -120,6 +122,14 @@ pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
}; };
if let Some(result) = try_project_config(project_dir) { if let Some(result) = try_project_config(project_dir) {
let project_config = result?; let project_config = result?;
if let Some(min_version) = &project_config.min_version {
let version_str = env!("CARGO_PKG_VERSION");
let version = semver::Version::parse(version_str).unwrap();
let version_req = semver::VersionReq::parse(&format!(">={min_version}"))?;
if !version_req.matches(&version) {
bail!("Project requires objdiff version {} or higher", min_version);
}
}
config.custom_make = project_config.custom_make; config.custom_make = project_config.custom_make;
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p)); config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));
config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p)); config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));

View File

@@ -372,66 +372,80 @@ fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)>
None None
} }
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> { pub fn diff_objs(mut left: Option<&mut ObjInfo>, mut right: Option<&mut ObjInfo>) -> Result<()> {
for left_section in &mut left.sections { if let Some(left) = left.as_mut() {
if left_section.kind == ObjSectionKind::Code { for left_section in &mut left.sections {
for left_symbol in &mut left_section.symbols { if left_section.kind == ObjSectionKind::Code {
if let Some((right_section_idx, right_symbol_idx)) = for left_symbol in &mut left_section.symbols {
find_section_and_symbol(right, &left_symbol.name) if let Some((right, (right_section_idx, right_symbol_idx))) =
{ right.as_mut().and_then(|obj| {
let right_section = &mut right.sections[right_section_idx]; find_section_and_symbol(obj, &left_symbol.name).map(|s| (obj, s))
let right_symbol = &mut right_section.symbols[right_symbol_idx]; })
left_symbol.diff_symbol = Some(right_symbol.name.clone()); {
right_symbol.diff_symbol = Some(left_symbol.name.clone()); let right_section = &mut right.sections[right_section_idx];
diff_code( let right_symbol = &mut right_section.symbols[right_symbol_idx];
left.architecture, left_symbol.diff_symbol = Some(right_symbol.name.clone());
&left_section.data, right_symbol.diff_symbol = Some(left_symbol.name.clone());
&right_section.data, diff_code(
left_symbol, left.architecture,
right_symbol, &left_section.data,
&left_section.relocations, &right_section.data,
&right_section.relocations, left_symbol,
&left.line_info, right_symbol,
&right.line_info, &left_section.relocations,
)?; &right_section.relocations,
} else { &left.line_info,
no_diff_code( &right.line_info,
left.architecture, )?;
&left_section.data, } else {
left_symbol, no_diff_code(
&left_section.relocations, left.architecture,
&left.line_info, &left_section.data,
)?; left_symbol,
&left_section.relocations,
&left.line_info,
)?;
}
} }
} } else if let Some(right_section) = right
} else { .as_mut()
let Some(right_section) = .and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name))
right.sections.iter_mut().find(|s| s.name == left_section.name) {
else { if left_section.kind == ObjSectionKind::Data {
continue; diff_data(left_section, right_section);
}; // diff_data_symbols(left_section, right_section)?;
if left_section.kind == ObjSectionKind::Data { } else if left_section.kind == ObjSectionKind::Bss {
diff_data(left_section, right_section); diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
// diff_data_symbols(left_section, right_section)?; }
} else if left_section.kind == ObjSectionKind::Bss { } else if left_section.kind == ObjSectionKind::Data {
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?; no_diff_data(left_section);
} }
} }
} }
for right_section in right.sections.iter_mut().filter(|s| s.kind == ObjSectionKind::Code) { if let Some(right) = right.as_mut() {
for right_symbol in &mut right_section.symbols { for right_section in right.sections.iter_mut() {
if right_symbol.instructions.is_empty() { if right_section.kind == ObjSectionKind::Code {
no_diff_code( for right_symbol in &mut right_section.symbols {
right.architecture, if right_symbol.instructions.is_empty() {
&right_section.data, no_diff_code(
right_symbol, right.architecture,
&right_section.relocations, &right_section.data,
&right.line_info, right_symbol,
)?; &right_section.relocations,
&right.line_info,
)?;
}
}
} else if right_section.kind == ObjSectionKind::Data
&& right_section.data_diff.is_empty()
{
no_diff_data(right_section);
} }
} }
} }
diff_bss_symbols(&mut left.common, &mut right.common)?; if let (Some(left), Some(right)) = (left, right) {
diff_bss_symbols(&mut left.common, &mut right.common)?;
}
Ok(()) Ok(())
} }
@@ -710,3 +724,12 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
left.data_diff = left_diff; left.data_diff = left_diff;
right.data_diff = right_diff; right.data_diff = right_diff;
} }
fn no_diff_data(section: &mut ObjSection) {
section.data_diff = vec![ObjDataDiff {
data: section.data.clone(),
kind: ObjDataDiffKind::None,
len: section.data.len(),
symbol: String::new(),
}];
}

View File

@@ -79,56 +79,101 @@ fn run_build(
let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?; let obj_config = config.selected_obj.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?;
let project_dir = let project_dir =
config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?; config.project_dir.as_ref().ok_or_else(|| Error::msg("Missing project dir"))?;
let target_path_rel = obj_config.target_path.strip_prefix(project_dir).map_err(|_| { let target_path_rel = if let Some(target_path) = &obj_config.target_path {
anyhow!( Some(target_path.strip_prefix(project_dir).map_err(|_| {
"Target path '{}' doesn't begin with '{}'", anyhow!(
obj_config.target_path.display(), "Target path '{}' doesn't begin with '{}'",
project_dir.display() target_path.display(),
) project_dir.display()
})?; )
let base_path_rel = obj_config.base_path.strip_prefix(project_dir).map_err(|_| { })?)
anyhow!( } else {
"Base path '{}' doesn't begin with '{}'", None
obj_config.base_path.display(), };
project_dir.display() let base_path_rel = if let Some(base_path) = &obj_config.base_path {
) Some(base_path.strip_prefix(project_dir).map_err(|_| {
})?; anyhow!(
"Base path '{}' doesn't begin with '{}'",
base_path.display(),
project_dir.display()
)
})?)
} else {
None
};
let total = if config.build_target { 5 } else { 4 }; let mut total = 3;
let first_status = if config.build_target { if config.build_target && target_path_rel.is_some() {
update_status(status, format!("Building target {}", target_path_rel.display()), 0, total, &cancel)?; total += 1;
run_make(project_dir, target_path_rel, &config) }
if base_path_rel.is_some() {
total += 1;
}
let first_status = match target_path_rel {
Some(target_path_rel) if config.build_target => {
update_status(
status,
format!("Building target {}", target_path_rel.display()),
0,
total,
&cancel,
)?;
run_make(project_dir, target_path_rel, &config)
}
_ => BuildStatus { success: true, log: String::new() },
};
let second_status = if let Some(base_path_rel) = base_path_rel {
update_status(
status,
format!("Building base {}", base_path_rel.display()),
1,
total,
&cancel,
)?;
run_make(project_dir, base_path_rel, &config)
} else { } else {
BuildStatus { success: true, log: String::new() } BuildStatus { success: true, log: String::new() }
}; };
update_status(status, format!("Building base {}", base_path_rel.display()), 1, total, &cancel)?;
let second_status = run_make(project_dir, base_path_rel, &config);
let time = OffsetDateTime::now_utc(); let time = OffsetDateTime::now_utc();
let mut first_obj = if first_status.success { let mut first_obj =
update_status(status, format!("Loading target {}", target_path_rel.display()), 2, total, &cancel)?; match &obj_config.target_path {
Some(elf::read(&obj_config.target_path).with_context(|| { Some(target_path) if first_status.success => {
format!("Failed to read object '{}'", obj_config.target_path.display()) update_status(
})?) status,
} else { format!("Loading target {}", target_path_rel.unwrap().display()),
None 2,
total,
&cancel,
)?;
Some(elf::read(target_path).with_context(|| {
format!("Failed to read object '{}'", target_path.display())
})?)
}
_ => None,
};
let mut second_obj = match &obj_config.base_path {
Some(base_path) if second_status.success => {
update_status(
status,
format!("Loading base {}", base_path_rel.unwrap().display()),
3,
total,
&cancel,
)?;
Some(
elf::read(base_path)
.with_context(|| format!("Failed to read object '{}'", base_path.display()))?,
)
}
_ => None,
}; };
let mut second_obj = if second_status.success { update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
update_status(status, format!("Loading base {}", base_path_rel.display()), 3, total, &cancel)?; diff_objs(first_obj.as_mut(), second_obj.as_mut())?;
Some(elf::read(&obj_config.base_path).with_context(|| {
format!("Failed to read object '{}'", obj_config.base_path.display())
})?)
} else {
None
};
if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) {
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
diff_objs(first_obj, second_obj)?;
}
update_status(status, "Complete".to_string(), total, total, &cancel)?; update_status(status, "Complete".to_string(), total, total, &cancel)?;
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time })) Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))

View File

@@ -3,6 +3,7 @@
pub use app::App; pub use app::App;
mod app; mod app;
mod app_config;
mod config; mod config;
mod diff; mod diff;
mod editops; mod editops;

View File

@@ -91,6 +91,7 @@ pub fn process_code(
reloc: reloc.cloned(), reloc: reloc.cloned(),
branch_dest, branch_dest,
line, line,
orig: None,
}); });
cur_addr += 4; cur_addr += 4;
} }

View File

@@ -37,6 +37,7 @@ pub struct ObjSection {
pub data_diff: Vec<ObjDataDiff>, pub data_diff: Vec<ObjDataDiff>,
pub match_percent: f32, pub match_percent: f32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ObjInsArg { pub enum ObjInsArg {
PpcArg(ppc750cl::Argument), PpcArg(ppc750cl::Argument),
@@ -46,6 +47,40 @@ pub enum ObjInsArg {
RelocWithBase, RelocWithBase,
BranchOffset(i32), BranchOffset(i32),
} }
// TODO derive PartialEq on ppc750cl::Argument so this isn't necessary
impl PartialEq for ObjInsArg {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ObjInsArg::PpcArg(a), ObjInsArg::PpcArg(b)) => {
use ppc750cl::Argument;
match (a, b) {
(Argument::GPR(a), Argument::GPR(b)) => a == b,
(Argument::FPR(a), Argument::FPR(b)) => a == b,
(Argument::SR(a), Argument::SR(b)) => a == b,
(Argument::SPR(a), Argument::SPR(b)) => a == b,
(Argument::CRField(a), Argument::CRField(b)) => a == b,
(Argument::CRBit(a), Argument::CRBit(b)) => a == b,
(Argument::GQR(a), Argument::GQR(b)) => a == b,
(Argument::Uimm(a), Argument::Uimm(b)) => a == b,
(Argument::Simm(a), Argument::Simm(b)) => a == b,
(Argument::Offset(a), Argument::Offset(b)) => a == b,
(Argument::BranchDest(a), Argument::BranchDest(b)) => a == b,
(Argument::Bit(a), Argument::Bit(b)) => a == b,
(Argument::OpaqueU(a), Argument::OpaqueU(b)) => a == b,
(_, _) => false,
}
}
(ObjInsArg::MipsArg(a), ObjInsArg::MipsArg(b)) => a == b,
(ObjInsArg::MipsArgWithBase(a), ObjInsArg::MipsArgWithBase(b)) => a == b,
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true,
(ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b,
(_, _) => false,
}
}
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct ObjInsArgDiff { pub struct ObjInsArgDiff {
/// Incrementing index for coloring /// Incrementing index for coloring
@@ -86,6 +121,8 @@ pub struct ObjIns {
pub branch_dest: Option<u32>, pub branch_dest: Option<u32>,
/// Line info /// Line info
pub line: Option<u32>, pub line: Option<u32>,
/// Original (unsimplified) instruction
pub orig: Option<String>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjInsDiff { pub struct ObjInsDiff {

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use anyhow::Result; use anyhow::Result;
use ppc750cl::{disasm_iter, Argument}; use ppc750cl::{disasm_iter, Argument, Ins, SimplifiedIns};
use crate::obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind}; use crate::obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind};
@@ -40,7 +40,7 @@ pub fn process_code(
_ => ins.code, _ => ins.code,
}; };
} }
let simplified = ins.simplified(); let simplified = ins.clone().simplified();
let mut args: Vec<ObjInsArg> = simplified let mut args: Vec<ObjInsArg> = simplified
.args .args
.iter() .iter()
@@ -86,10 +86,21 @@ pub fn process_code(
mnemonic: format!("{}{}", simplified.mnemonic, simplified.suffix), mnemonic: format!("{}{}", simplified.mnemonic, simplified.suffix),
args, args,
reloc: reloc.cloned(), reloc: reloc.cloned(),
op: 0, op: ins.op as u8,
branch_dest: None, branch_dest: None,
line, line,
orig: Some(format!("{}", basic_form(ins))),
}); });
} }
Ok((ops, insts)) Ok((ops, insts))
} }
// TODO make public in ppc750cl
fn basic_form(ins: Ins) -> SimplifiedIns {
SimplifiedIns {
mnemonic: ins.op.mnemonic(),
suffix: ins.suffix(),
args: ins.fields().iter().flat_map(|field| field.argument()).collect(),
ins,
}
}

View File

@@ -40,6 +40,7 @@ pub struct ConfigViewState {
pub watch_pattern_text: String, pub watch_pattern_text: String,
pub load_error: Option<String>, pub load_error: Option<String>,
pub object_search: String, pub object_search: String,
pub filter_diffable: bool,
#[cfg(windows)] #[cfg(windows)]
pub available_wsl_distros: Option<Vec<String>>, pub available_wsl_distros: Option<Vec<String>>,
} }
@@ -218,17 +219,19 @@ pub fn config_ui(
let target_path = target_dir.join(obj_path); let target_path = target_dir.join(obj_path);
new_selected_obj = Some(ObjectConfig { new_selected_obj = Some(ObjectConfig {
name: obj_path.display().to_string(), name: obj_path.display().to_string(),
target_path, target_path: Some(target_path),
base_path: path, base_path: Some(path),
reverse_fn_order: None, reverse_fn_order: None,
complete: None,
}); });
} else if let Ok(obj_path) = path.strip_prefix(&target_dir) { } else if let Ok(obj_path) = path.strip_prefix(&target_dir) {
let base_path = base_dir.join(obj_path); let base_path = base_dir.join(obj_path);
new_selected_obj = Some(ObjectConfig { new_selected_obj = Some(ObjectConfig {
name: obj_path.display().to_string(), name: obj_path.display().to_string(),
target_path: path, target_path: Some(path),
base_path, base_path: Some(base_path),
reverse_fn_order: None, reverse_fn_order: None,
complete: None,
}); });
} }
} }
@@ -266,6 +269,13 @@ pub fn config_ui(
root_open = Some(true); root_open = Some(true);
node_open = NodeOpen::Object; node_open = NodeOpen::Object;
} }
if ui
.selectable_label(state.filter_diffable, "Diffable")
.on_hover_text_at_pointer("Only show objects with a source file")
.clicked()
{
state.filter_diffable = !state.filter_diffable;
}
}); });
if state.object_search.is_empty() { if state.object_search.is_empty() {
if had_search { if had_search {
@@ -285,10 +295,13 @@ pub fn config_ui(
.default_open(true) .default_open(true)
.show(ui, |ui| { .show(ui, |ui| {
let mut nodes = Cow::Borrowed(object_nodes); let mut nodes = Cow::Borrowed(object_nodes);
if !state.object_search.is_empty() { if !state.object_search.is_empty() || state.filter_diffable {
let search = state.object_search.to_ascii_lowercase(); let search = state.object_search.to_ascii_lowercase();
nodes = Cow::Owned( nodes = Cow::Owned(
object_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(), object_nodes
.iter()
.filter_map(|node| filter_node(node, &search, state.filter_diffable))
.collect(),
); );
} }
@@ -322,8 +335,18 @@ fn display_object(
) { ) {
let object_name = object.name(); let object_name = object.name();
let selected = matches!(selected_obj, Some(obj) if obj.name == object_name); let selected = matches!(selected_obj, Some(obj) if obj.name == object_name);
let color = if selected { appearance.emphasized_text_color } else { appearance.text_color }; let color = if selected {
if SelectableLabel::new( appearance.emphasized_text_color
} else if let Some(complete) = object.complete {
if complete {
appearance.insert_color
} else {
appearance.delete_color
}
} else {
appearance.text_color
};
let clicked = SelectableLabel::new(
selected, selected,
RichText::new(name) RichText::new(name)
.font(FontId { .font(FontId {
@@ -333,13 +356,16 @@ fn display_object(
.color(color), .color(color),
) )
.ui(ui) .ui(ui)
.clicked() .clicked();
{ // Always recreate ObjectConfig if selected, in case the project config changed.
// ObjectConfig is compared using equality, so this won't unnecessarily trigger a rebuild.
if selected || clicked {
*selected_obj = Some(ObjectConfig { *selected_obj = Some(ObjectConfig {
name: object_name.to_string(), name: object_name.to_string(),
target_path: object.target_path.clone().unwrap_or_default(), target_path: object.target_path.clone(),
base_path: object.base_path.clone().unwrap_or_default(), base_path: object.base_path.clone(),
reverse_fn_order: object.reverse_fn_order, reverse_fn_order: object.reverse_fn_order,
complete: object.complete,
}); });
} }
} }
@@ -404,21 +430,30 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: &ObjectConfig) -> bool
} }
} }
fn filter_node(node: &ProjectObjectNode, search: &str) -> Option<ProjectObjectNode> { fn filter_node(
node: &ProjectObjectNode,
search: &str,
filter_diffable: bool,
) -> Option<ProjectObjectNode> {
match node { match node {
ProjectObjectNode::File(name, _) => { ProjectObjectNode::File(name, object) => {
if name.to_ascii_lowercase().contains(search) { if (search.is_empty() || name.to_ascii_lowercase().contains(search))
&& (!filter_diffable || object.base_path.is_some())
{
Some(node.clone()) Some(node.clone())
} else { } else {
None None
} }
} }
ProjectObjectNode::Dir(name, children) => { ProjectObjectNode::Dir(name, children) => {
if name.to_ascii_lowercase().contains(search) { if (search.is_empty() || name.to_ascii_lowercase().contains(search)) && !filter_diffable
{
return Some(node.clone()); return Some(node.clone());
} }
let new_children = let new_children = children
children.iter().filter_map(|child| filter_node(child, search)).collect::<Vec<_>>(); .iter()
.filter_map(|child| filter_node(child, search, filter_diffable))
.collect::<Vec<_>>();
if !new_children.is_empty() { if !new_children.is_empty() {
Some(ProjectObjectNode::Dir(name.clone(), new_children)) Some(ProjectObjectNode::Dir(name.clone(), new_children))
} else { } else {

View File

@@ -132,31 +132,39 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
fn data_table_ui( fn data_table_ui(
table: TableBuilder<'_>, table: TableBuilder<'_>,
left_obj: &ObjInfo, left_obj: Option<&ObjInfo>,
right_obj: &ObjInfo, right_obj: Option<&ObjInfo>,
selected_symbol: &SymbolReference, selected_symbol: &SymbolReference,
config: &Appearance, config: &Appearance,
) -> Option<()> { ) -> Option<()> {
let left_section = find_section(left_obj, selected_symbol)?; let left_section = left_obj.and_then(|obj| find_section(obj, selected_symbol));
let right_section = find_section(right_obj, selected_symbol)?; let right_section = right_obj.and_then(|obj| find_section(obj, selected_symbol));
let total_bytes = left_section.data_diff.iter().fold(0usize, |accum, item| accum + item.len); let total_bytes = left_section
.or(right_section)?
.data_diff
.iter()
.fold(0usize, |accum, item| accum + item.len);
if total_bytes == 0 { if total_bytes == 0 {
return None; return None;
} }
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1; let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
let left_diffs = split_diffs(&left_section.data_diff); let left_diffs = left_section.map(|section| split_diffs(&section.data_diff));
let right_diffs = split_diffs(&right_section.data_diff); let right_diffs = right_section.map(|section| split_diffs(&section.data_diff));
table.body(|body| { table.body(|body| {
body.rows(config.code_font.size, total_rows, |row_index, mut row| { body.rows(config.code_font.size, total_rows, |row_index, mut row| {
let address = row_index * BYTES_PER_ROW; let address = row_index * BYTES_PER_ROW;
row.col(|ui| { row.col(|ui| {
data_row_ui(ui, address, &left_diffs[row_index], config); if let Some(left_diffs) = &left_diffs {
data_row_ui(ui, address, &left_diffs[row_index], config);
}
}); });
row.col(|ui| { row.col(|ui| {
data_row_ui(ui, address, &right_diffs[row_index], config); if let Some(right_diffs) = &right_diffs {
data_row_ui(ui, address, &right_diffs[row_index], config);
}
}); });
}); });
}); });
@@ -243,15 +251,19 @@ pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &A
ui.separator(); ui.separator();
// Table // Table
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) { let available_height = ui.available_height();
let available_height = ui.available_height(); let table = TableBuilder::new(ui)
let table = TableBuilder::new(ui) .striped(false)
.striped(false) .cell_layout(Layout::left_to_right(Align::Min))
.cell_layout(Layout::left_to_right(Align::Min)) .columns(Column::exact(column_width).clip(true), 2)
.columns(Column::exact(column_width).clip(true), 2) .resizable(false)
.resizable(false) .auto_shrink([false, false])
.auto_shrink([false, false]) .min_scrolled_height(available_height);
.min_scrolled_height(available_height); data_table_ui(
data_table_ui(table, left_obj, right_obj, selected_symbol, appearance); table,
} result.first_obj.as_ref(),
result.second_obj.as_ref(),
selected_symbol,
appearance,
);
} }

View File

@@ -1,9 +1,12 @@
use std::{cmp::Ordering, default::Default}; use std::{
cmp::{max, Ordering},
default::Default,
};
use cwdemangle::demangle; use cwdemangle::demangle;
use eframe::emath::Align; use eframe::emath::Align;
use egui::{text::LayoutJob, Color32, FontId, Label, Layout, Sense, Vec2}; use egui::{text::LayoutJob, Color32, Label, Layout, RichText, Sense, TextFormat, Vec2};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder, TableRow};
use ppc750cl::Argument; use ppc750cl::Argument;
use time::format_description; use time::format_description;
@@ -19,21 +22,49 @@ use crate::{
}, },
}; };
#[derive(Default)]
pub enum HighlightKind {
#[default]
None,
Opcode(u8),
Arg(ObjInsArg),
Symbol(String),
Address(u32),
}
#[derive(Default)]
pub struct FunctionViewState {
pub highlight: HighlightKind,
}
fn write_reloc_name( fn write_reloc_name(
reloc: &ObjReloc, reloc: &ObjReloc,
color: Color32, color: Color32,
background_color: Color32,
job: &mut LayoutJob, job: &mut LayoutJob,
font_id: FontId,
appearance: &Appearance, appearance: &Appearance,
) { ) {
let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name); let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name);
write_text(name, appearance.emphasized_text_color, job, font_id.clone()); job.append(name, 0.0, TextFormat {
font_id: appearance.code_font.clone(),
color: appearance.emphasized_text_color,
background: background_color,
..Default::default()
});
match reloc.target.addend.cmp(&0i64) { match reloc.target.addend.cmp(&0i64) {
Ordering::Greater => { Ordering::Greater => write_text(
write_text(&format!("+{:#X}", reloc.target.addend), color, job, font_id) &format!("+{:#X}", reloc.target.addend),
} color,
job,
appearance.code_font.clone(),
),
Ordering::Less => { Ordering::Less => {
write_text(&format!("-{:#X}", -reloc.target.addend), color, job, font_id); write_text(
&format!("-{:#X}", -reloc.target.addend),
color,
job,
appearance.code_font.clone(),
);
} }
_ => {} _ => {}
} }
@@ -42,57 +73,57 @@ fn write_reloc_name(
fn write_reloc( fn write_reloc(
reloc: &ObjReloc, reloc: &ObjReloc,
color: Color32, color: Color32,
background_color: Color32,
job: &mut LayoutJob, job: &mut LayoutJob,
font_id: FontId,
appearance: &Appearance, appearance: &Appearance,
) { ) {
match reloc.kind { match reloc.kind {
ObjRelocKind::PpcAddr16Lo => { ObjRelocKind::PpcAddr16Lo => {
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text("@l", color, job, font_id); write_text("@l", color, job, appearance.code_font.clone());
} }
ObjRelocKind::PpcAddr16Hi => { ObjRelocKind::PpcAddr16Hi => {
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text("@h", color, job, font_id); write_text("@h", color, job, appearance.code_font.clone());
} }
ObjRelocKind::PpcAddr16Ha => { ObjRelocKind::PpcAddr16Ha => {
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text("@ha", color, job, font_id); write_text("@ha", color, job, appearance.code_font.clone());
} }
ObjRelocKind::PpcEmbSda21 => { ObjRelocKind::PpcEmbSda21 => {
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text("@sda21", color, job, font_id); write_text("@sda21", color, job, appearance.code_font.clone());
} }
ObjRelocKind::MipsHi16 => { ObjRelocKind::MipsHi16 => {
write_text("%hi(", color, job, font_id.clone()); write_text("%hi(", color, job, appearance.code_font.clone());
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text(")", color, job, font_id); write_text(")", color, job, appearance.code_font.clone());
} }
ObjRelocKind::MipsLo16 => { ObjRelocKind::MipsLo16 => {
write_text("%lo(", color, job, font_id.clone()); write_text("%lo(", color, job, appearance.code_font.clone());
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text(")", color, job, font_id); write_text(")", color, job, appearance.code_font.clone());
} }
ObjRelocKind::MipsGot16 => { ObjRelocKind::MipsGot16 => {
write_text("%got(", color, job, font_id.clone()); write_text("%got(", color, job, appearance.code_font.clone());
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text(")", color, job, font_id); write_text(")", color, job, appearance.code_font.clone());
} }
ObjRelocKind::MipsCall16 => { ObjRelocKind::MipsCall16 => {
write_text("%call16(", color, job, font_id.clone()); write_text("%call16(", color, job, appearance.code_font.clone());
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text(")", color, job, font_id); write_text(")", color, job, appearance.code_font.clone());
} }
ObjRelocKind::MipsGpRel16 => { ObjRelocKind::MipsGpRel16 => {
write_text("%gp_rel(", color, job, font_id.clone()); write_text("%gp_rel(", color, job, appearance.code_font.clone());
write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_reloc_name(reloc, color, background_color, job, appearance);
write_text(")", color, job, font_id); write_text(")", color, job, appearance.code_font.clone());
} }
ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 | ObjRelocKind::Mips26 => { ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 | ObjRelocKind::Mips26 => {
write_reloc_name(reloc, color, job, font_id, appearance); write_reloc_name(reloc, color, background_color, job, appearance);
} }
ObjRelocKind::Absolute | ObjRelocKind::MipsGpRel32 => { ObjRelocKind::Absolute | ObjRelocKind::MipsGpRel32 => {
write_text("[INVALID]", color, job, font_id); write_text("[INVALID]", color, job, appearance.code_font.clone());
} }
}; };
} }
@@ -102,8 +133,9 @@ fn write_ins(
diff_kind: &ObjInsDiffKind, diff_kind: &ObjInsDiffKind,
args: &[Option<ObjInsArgDiff>], args: &[Option<ObjInsArgDiff>],
base_addr: u32, base_addr: u32,
job: &mut LayoutJob, ui: &mut egui::Ui,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
) { ) {
let base_color = match diff_kind { let base_color = match diff_kind {
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
@@ -113,49 +145,92 @@ fn write_ins(
ObjInsDiffKind::Delete => appearance.delete_color, ObjInsDiffKind::Delete => appearance.delete_color,
ObjInsDiffKind::Insert => appearance.insert_color, ObjInsDiffKind::Insert => appearance.insert_color,
}; };
write_text(
&format!("{:<11}", ins.mnemonic), let highlighted_op =
match diff_kind { matches!(ins_view_state.highlight, HighlightKind::Opcode(op) if op == ins.op);
ObjInsDiffKind::OpMismatch => appearance.replace_color, let op_label = RichText::new(ins.mnemonic.clone())
_ => base_color, .font(appearance.code_font.clone())
}, .color(if highlighted_op {
job, appearance.emphasized_text_color
appearance.code_font.clone(), } else {
); match diff_kind {
ObjInsDiffKind::OpMismatch => appearance.replace_color,
_ => base_color,
}
})
.background_color(if highlighted_op {
appearance.deemphasized_text_color
} else {
Color32::TRANSPARENT
});
if ui.add(Label::new(op_label).sense(Sense::click())).clicked() {
if highlighted_op {
ins_view_state.highlight = HighlightKind::None;
} else {
ins_view_state.highlight = HighlightKind::Opcode(ins.op);
}
}
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
ui.add_space(space_width * (max(11, ins.mnemonic.len()) - ins.mnemonic.len()) as f32);
let mut writing_offset = false; let mut writing_offset = false;
for (i, arg) in ins.args.iter().enumerate() { for (i, arg) in ins.args.iter().enumerate() {
let mut job = LayoutJob::default();
if i == 0 { if i == 0 {
write_text(" ", base_color, job, appearance.code_font.clone()); write_text(" ", base_color, &mut job, appearance.code_font.clone());
} }
if i > 0 && !writing_offset { if i > 0 && !writing_offset {
write_text(", ", base_color, job, appearance.code_font.clone()); write_text(", ", base_color, &mut job, appearance.code_font.clone());
} }
let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) { let highlighted_arg = match &ins_view_state.highlight {
HighlightKind::Symbol(v) => {
matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase)
&& matches!(&ins.reloc, Some(reloc) if &reloc.target.name == v)
}
HighlightKind::Address(v) => {
matches!(arg, ObjInsArg::BranchOffset(offset) if (offset + ins.address as i32 - base_addr as i32) as u32 == *v)
}
HighlightKind::Arg(v) => v == arg,
_ => false,
};
let color = if highlighted_arg {
appearance.emphasized_text_color
} else if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) {
appearance.diff_colors[diff.idx % appearance.diff_colors.len()] appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} else { } else {
base_color base_color
}; };
let text_format = TextFormat {
font_id: appearance.code_font.clone(),
color,
background: if highlighted_arg {
appearance.deemphasized_text_color
} else {
Color32::TRANSPARENT
},
..Default::default()
};
let mut new_writing_offset = false;
match arg { match arg {
ObjInsArg::PpcArg(arg) => match arg { ObjInsArg::PpcArg(arg) => match arg {
Argument::Offset(val) => { Argument::Offset(val) => {
write_text(&format!("{val}"), color, job, appearance.code_font.clone()); job.append(&format!("{val}"), 0.0, text_format);
write_text("(", base_color, job, appearance.code_font.clone()); write_text("(", base_color, &mut job, appearance.code_font.clone());
writing_offset = true; new_writing_offset = true;
continue;
} }
Argument::Uimm(_) | Argument::Simm(_) => { Argument::Uimm(_) | Argument::Simm(_) => {
write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); job.append(&format!("{arg}"), 0.0, text_format);
} }
_ => { _ => {
write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); job.append(&format!("{arg}"), 0.0, text_format);
} }
}, },
ObjInsArg::Reloc => { ObjInsArg::Reloc => {
write_reloc( write_reloc(
ins.reloc.as_ref().unwrap(), ins.reloc.as_ref().unwrap(),
base_color, base_color,
job, text_format.background,
appearance.code_font.clone(), &mut job,
appearance, appearance,
); );
} }
@@ -163,41 +238,42 @@ fn write_ins(
write_reloc( write_reloc(
ins.reloc.as_ref().unwrap(), ins.reloc.as_ref().unwrap(),
base_color, base_color,
job, text_format.background,
appearance.code_font.clone(), &mut job,
appearance, appearance,
); );
write_text("(", base_color, job, appearance.code_font.clone()); write_text("(", base_color, &mut job, appearance.code_font.clone());
writing_offset = true; new_writing_offset = true;
continue;
} }
ObjInsArg::MipsArg(str) => { ObjInsArg::MipsArg(str) => {
write_text( job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format);
str.strip_prefix('$').unwrap_or(str),
color,
job,
appearance.code_font.clone(),
);
} }
ObjInsArg::MipsArgWithBase(str) => { ObjInsArg::MipsArgWithBase(str) => {
write_text( job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format);
str.strip_prefix('$').unwrap_or(str), write_text("(", base_color, &mut job, appearance.code_font.clone());
color, new_writing_offset = true;
job,
appearance.code_font.clone(),
);
write_text("(", base_color, job, appearance.code_font.clone());
writing_offset = true;
continue;
} }
ObjInsArg::BranchOffset(offset) => { ObjInsArg::BranchOffset(offset) => {
let addr = offset + ins.address as i32 - base_addr as i32; let addr = offset + ins.address as i32 - base_addr as i32;
write_text(&format!("{addr:x}"), color, job, appearance.code_font.clone()); job.append(&format!("{addr:x}"), 0.0, text_format);
} }
} }
if writing_offset { if writing_offset {
write_text(")", base_color, job, appearance.code_font.clone()); write_text(")", base_color, &mut job, appearance.code_font.clone());
writing_offset = false; }
writing_offset = new_writing_offset;
if ui.add(Label::new(job).sense(Sense::click())).clicked() {
if highlighted_arg {
ins_view_state.highlight = HighlightKind::None;
} else if matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase) {
ins_view_state.highlight =
HighlightKind::Symbol(ins.reloc.as_ref().unwrap().target.name.clone());
} else if let ObjInsArg::BranchOffset(offset) = arg {
ins_view_state.highlight =
HighlightKind::Address((offset + ins.address as i32 - base_addr as i32) as u32);
} else {
ins_view_state.highlight = HighlightKind::Arg(arg.clone());
}
} }
} }
} }
@@ -209,6 +285,10 @@ fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, appearance: &Appearance) {
ui.label(format!("{:02X?}", ins.code.to_be_bytes())); ui.label(format!("{:02X?}", ins.code.to_be_bytes()));
if let Some(orig) = &ins.orig {
ui.label(format!("Original: {}", orig));
}
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::PpcArg(arg) = arg { if let ObjInsArg::PpcArg(arg) = arg {
match arg { match arg {
@@ -316,7 +396,9 @@ fn asm_row_ui(
ins_diff: &ObjInsDiff, ins_diff: &ObjInsDiff,
symbol: &ObjSymbol, symbol: &ObjSymbol,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
) { ) {
ui.spacing_mut().item_spacing.x = 0.0;
if ins_diff.kind != ObjInsDiffKind::None { if ins_diff.kind != ObjInsDiffKind::None {
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
} }
@@ -345,58 +427,128 @@ fn asm_row_ui(
); );
pad = 12 - line_str.len(); pad = 12 - line_str.len();
} }
write_text( let base_addr = symbol.address as u32;
&format!("{:<1$}", format!("{:x}: ", ins.address - symbol.address as u32), pad), let addr_highlight = matches!(
base_color, &ins_view_state.highlight,
&mut job, HighlightKind::Address(v) if *v == (ins.address - base_addr)
appearance.code_font.clone(),
); );
if let Some(branch) = &ins_diff.branch_from { let addr_string = format!("{:x}", ins.address - symbol.address as u32);
write_text( pad -= addr_string.len();
"~> ", job.append(&addr_string, 0.0, TextFormat {
appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], font_id: appearance.code_font.clone(),
&mut job, color: if addr_highlight { appearance.emphasized_text_color } else { base_color },
appearance.code_font.clone(), background: if addr_highlight {
); appearance.deemphasized_text_color
} else { } else {
write_text(" ", base_color, &mut job, appearance.code_font.clone()); Color32::TRANSPARENT
},
..Default::default()
});
if ui.add(Label::new(job).sense(Sense::click())).clicked() {
if addr_highlight {
ins_view_state.highlight = HighlightKind::None;
} else {
ins_view_state.highlight = HighlightKind::Address(ins.address - base_addr);
}
} }
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, appearance);
let mut job = LayoutJob::default();
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
let spacing = space_width * pad as f32;
job.append(": ", 0.0, TextFormat {
font_id: appearance.code_font.clone(),
color: base_color,
..Default::default()
});
if let Some(branch) = &ins_diff.branch_from {
job.append("~> ", spacing, TextFormat {
font_id: appearance.code_font.clone(),
color: appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()],
..Default::default()
});
} else {
job.append(" ", spacing, TextFormat {
font_id: appearance.code_font.clone(),
color: base_color,
..Default::default()
});
}
ui.add(Label::new(job));
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, base_addr, ui, appearance, ins_view_state);
if let Some(branch) = &ins_diff.branch_to { if let Some(branch) = &ins_diff.branch_to {
let mut job = LayoutJob::default();
write_text( write_text(
" ~>", " ~>",
appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()],
&mut job, &mut job,
appearance.code_font.clone(), appearance.code_font.clone(),
); );
ui.add(Label::new(job));
} }
ui.add(Label::new(job).sense(Sense::click())) }
.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, appearance))
.context_menu(|ui| ins_context_menu(ui, ins)); fn asm_col_ui(
row: &mut TableRow<'_, '_>,
ins_diff: &ObjInsDiff,
symbol: &ObjSymbol,
appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
) {
let (_, response) = row.col(|ui| {
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state);
});
if let Some(ins) = &ins_diff.ins {
response
.on_hover_ui_at_pointer(|ui| {
ins_hover_ui(ui, ins, appearance);
})
.context_menu(|ui| {
ins_context_menu(ui, ins);
});
}
}
fn empty_col_ui(row: &mut TableRow<'_, '_>) {
row.col(|ui| {
ui.label("");
});
} }
fn asm_table_ui( fn asm_table_ui(
table: TableBuilder<'_>, table: TableBuilder<'_>,
left_obj: &ObjInfo, left_obj: Option<&ObjInfo>,
right_obj: &ObjInfo, right_obj: Option<&ObjInfo>,
selected_symbol: &SymbolReference, selected_symbol: &SymbolReference,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &mut FunctionViewState,
) -> Option<()> { ) -> Option<()> {
let left_symbol = find_symbol(left_obj, selected_symbol); let left_symbol = left_obj.and_then(|obj| find_symbol(obj, selected_symbol));
let right_symbol = find_symbol(right_obj, selected_symbol); let right_symbol = right_obj.and_then(|obj| find_symbol(obj, selected_symbol));
let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?; let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?;
table.body(|body| { table.body(|body| {
body.rows(appearance.code_font.size, instructions_len, |row_index, mut row| { body.rows(appearance.code_font.size, instructions_len, |row_index, mut row| {
row.col(|ui| { if let Some(symbol) = left_symbol {
if let Some(symbol) = left_symbol { asm_col_ui(
asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); &mut row,
} &symbol.instructions[row_index],
}); symbol,
row.col(|ui| { appearance,
if let Some(symbol) = right_symbol { ins_view_state,
asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); );
} } else {
}); empty_col_ui(&mut row);
}
if let Some(symbol) = right_symbol {
asm_col_ui(
&mut row,
&symbol.instructions[row_index],
symbol,
appearance,
ins_view_state,
);
} else {
empty_col_ui(&mut row);
}
}); });
}); });
Some(()) Some(())
@@ -492,7 +644,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
&format!("{match_percent:.0}%"), &format!("{match_percent:.0}%"),
); );
} else { } else {
ui.label(""); ui.colored_label(appearance.replace_color, "Missing");
} }
ui.label("Diff base:"); ui.label("Diff base:");
}); });
@@ -503,15 +655,20 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
ui.separator(); ui.separator();
// Table // Table
if let (Some(left_obj), Some(right_obj)) = (&result.first_obj, &result.second_obj) { let available_height = ui.available_height();
let available_height = ui.available_height(); let table = TableBuilder::new(ui)
let table = TableBuilder::new(ui) .striped(false)
.striped(false) .cell_layout(Layout::left_to_right(Align::Min))
.cell_layout(Layout::left_to_right(Align::Min)) .columns(Column::exact(column_width).clip(true), 2)
.columns(Column::exact(column_width).clip(true), 2) .resizable(false)
.resizable(false) .auto_shrink([false, false])
.auto_shrink([false, false]) .min_scrolled_height(available_height);
.min_scrolled_height(available_height); asm_table_ui(
asm_table_ui(table, left_obj, right_obj, selected_symbol, appearance); table,
} result.first_obj.as_ref(),
result.second_obj.as_ref(),
selected_symbol,
appearance,
&mut state.function_state,
);
} }

View File

@@ -1,7 +1,7 @@
use std::mem::take; use std::mem::take;
use egui::{ use egui::{
text::LayoutJob, Align, CollapsingHeader, Color32, Layout, Rgba, ScrollArea, SelectableLabel, text::LayoutJob, Align, CollapsingHeader, Color32, Layout, ScrollArea, SelectableLabel,
TextEdit, Ui, Vec2, Widget, TextEdit, Ui, Vec2, Widget,
}; };
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
@@ -13,7 +13,7 @@ use crate::{
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
}, },
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags}, obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
views::{appearance::Appearance, write_text}, views::{appearance::Appearance, function_diff::FunctionViewState, write_text},
}; };
pub struct SymbolReference { pub struct SymbolReference {
@@ -35,6 +35,7 @@ pub struct DiffViewState {
pub build: Option<Box<ObjDiffResult>>, pub build: Option<Box<ObjDiffResult>>,
pub current_view: View, pub current_view: View,
pub symbol_state: SymbolViewState, pub symbol_state: SymbolViewState,
pub function_state: FunctionViewState,
pub search: String, pub search: String,
pub queue_build: bool, pub queue_build: bool,
pub build_running: bool, pub build_running: bool,
@@ -263,6 +264,15 @@ fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) {
}); });
} }
fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.colored_label(appearance.replace_color, "No object configured");
});
}
pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) { pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) {
let DiffViewState { build, current_view, symbol_state, search, .. } = state; let DiffViewState { build, current_view, symbol_state, search, .. } = state;
let Some(result) = build else { let Some(result) = build else {
@@ -289,9 +299,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.label("Build target:"); ui.label("Build target:");
if result.first_status.success { if result.first_status.success {
ui.label("OK"); if result.first_obj.is_none() {
ui.colored_label(appearance.replace_color, "Missing");
} else {
ui.label("OK");
}
} else { } else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); ui.colored_label(appearance.delete_color, "Fail");
} }
}); });
@@ -312,9 +326,13 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
ui.label("Build base:"); ui.label("Build base:");
if result.second_status.success { if result.second_status.success {
ui.label("OK"); if result.second_obj.is_none() {
ui.colored_label(appearance.replace_color, "Missing");
} else {
ui.label("OK");
}
} else { } else {
ui.colored_label(Rgba::from_rgb(1.0, 0.0, 0.0), "Fail"); ui.colored_label(appearance.delete_color, "Fail");
} }
}); });
@@ -348,6 +366,8 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
&lower_search, &lower_search,
appearance, appearance,
)); ));
} else {
missing_obj_ui(ui, appearance);
} }
} else { } else {
build_log_ui(ui, &result.first_status, appearance); build_log_ui(ui, &result.first_status, appearance);
@@ -365,6 +385,8 @@ pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appea
&lower_search, &lower_search,
appearance, appearance,
)); ));
} else {
missing_obj_ui(ui, appearance);
} }
} else { } else {
build_log_ui(ui, &result.second_status, appearance); build_log_ui(ui, &result.second_status, appearance);