Version 0.2.0

- Update checker & auto-updater
- Configure font sizes and diff colors
- Data diffing bug fixes & improvements
- Bug fix for low match percent
- Improvements to Jobs UI (cancel, dismiss errors)
- "Demangle" tool

Closes #6, #13, #17, #19
This commit is contained in:
2022-12-06 17:53:32 -05:00
parent 2f2efb4711
commit 771a141110
26 changed files with 2244 additions and 485 deletions

View File

@@ -2,22 +2,23 @@ use std::{
default::Default,
ffi::OsStr,
path::{Path, PathBuf},
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Arc, RwLock,
Arc, Mutex, RwLock,
},
time::Duration,
};
use eframe::Frame;
use egui::Widget;
use egui::{Color32, FontFamily, FontId, TextStyle};
use notify::{RecursiveMode, Watcher};
use time::{OffsetDateTime, UtcOffset};
use crate::{
jobs::{
check_update::{queue_check_update, CheckUpdateResult},
objdiff::{queue_build, BuildStatus, ObjDiffResult},
Job, JobResult, JobState,
Job, JobResult, JobState, JobStatus,
},
views::{
config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui,
@@ -48,6 +49,36 @@ pub struct DiffConfig {
// pub mapped_symbols: HashMap<String, String>,
}
const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
Color32::from_rgb(255, 0, 255),
Color32::from_rgb(0, 255, 255),
Color32::from_rgb(0, 128, 0),
Color32::from_rgb(255, 0, 0),
Color32::from_rgb(255, 255, 0),
Color32::from_rgb(255, 192, 203),
Color32::from_rgb(0, 0, 255),
Color32::from_rgb(0, 255, 0),
Color32::from_rgb(128, 128, 128),
];
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct ViewConfig {
pub ui_font: FontId,
pub code_font: FontId,
pub diff_colors: Vec<Color32>,
}
impl Default for ViewConfig {
fn default() -> Self {
Self {
ui_font: FontId { size: 14.0, family: FontFamily::Proportional },
code_font: FontId { size: 14.0, family: FontFamily::Monospace },
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct ViewState {
@@ -64,14 +95,21 @@ pub struct ViewState {
#[serde(skip)]
pub show_config: bool,
#[serde(skip)]
pub show_demangle: bool,
#[serde(skip)]
pub demangle_text: String,
#[serde(skip)]
pub diff_config: DiffConfig,
#[serde(skip)]
pub search: String,
#[serde(skip)]
pub utc_offset: UtcOffset,
#[serde(skip)]
pub check_update: Option<Box<CheckUpdateResult>>,
// Config
pub diff_kind: DiffKind,
pub reverse_fn_order: bool,
pub view_config: ViewConfig,
}
impl Default for ViewState {
@@ -83,11 +121,15 @@ impl Default for ViewState {
selected_symbol: None,
current_view: Default::default(),
show_config: false,
show_demangle: false,
demangle_text: String::new(),
diff_config: Default::default(),
search: Default::default(),
utc_offset: UtcOffset::UTC,
check_update: None,
diff_kind: Default::default(),
reverse_fn_order: false,
view_config: Default::default(),
}
}
}
@@ -95,7 +137,7 @@ impl Default for ViewState {
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct AppConfig {
pub custom_make: String,
pub custom_make: Option<String>,
// WSL2 settings
#[serde(skip)]
pub available_wsl_distros: Option<Vec<String>>,
@@ -111,6 +153,19 @@ pub struct AppConfig {
pub right_obj: Option<PathBuf>,
#[serde(skip)]
pub project_dir_change: bool,
#[serde(skip)]
pub queue_update_check: bool,
pub auto_update_check: bool,
}
#[derive(Default, Clone, serde::Deserialize)]
#[serde(default)]
pub struct ProjectConfig {
pub custom_make: Option<String>,
pub project_dir: Option<PathBuf>,
pub target_obj_dir: Option<PathBuf>,
pub base_obj_dir: Option<PathBuf>,
pub build_target: bool,
}
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
@@ -124,6 +179,10 @@ pub struct App {
modified: Arc<AtomicBool>,
#[serde(skip)]
watcher: Option<notify::RecommendedWatcher>,
#[serde(skip)]
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
#[serde(skip)]
should_relaunch: bool,
}
impl Default for App {
@@ -133,6 +192,8 @@ impl Default for App {
config: Arc::new(Default::default()),
modified: Arc::new(Default::default()),
watcher: None,
relaunch_path: Default::default(),
should_relaunch: false,
}
}
}
@@ -141,7 +202,11 @@ const CONFIG_KEY: &str = "app_config";
impl App {
/// Called once before the first frame.
pub fn new(cc: &eframe::CreationContext<'_>, utc_offset: UtcOffset) -> Self {
pub fn new(
cc: &eframe::CreationContext<'_>,
utc_offset: UtcOffset,
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
) -> Self {
// This is also where you can customized the look at feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
@@ -153,11 +218,16 @@ impl App {
if config.project_dir.is_some() {
config.project_dir_change = true;
}
config.queue_update_check = config.auto_update_check;
app.config = Arc::new(RwLock::new(config));
app.view_state.utc_offset = utc_offset;
app.relaunch_path = relaunch_path;
app
} else {
Self::default()
let mut app = Self::default();
app.view_state.utc_offset = utc_offset;
app.relaunch_path = relaunch_path;
app
}
}
}
@@ -165,17 +235,44 @@ impl App {
impl eframe::App for App {
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
if self.should_relaunch {
frame.close();
return;
}
let Self { config, view_state, .. } = self;
{
let config = &view_state.view_config;
let mut style = (*ctx.style()).clone();
style.text_styles.insert(TextStyle::Body, FontId {
size: (config.ui_font.size * 0.75).floor(),
family: config.ui_font.family.clone(),
});
style.text_styles.insert(TextStyle::Body, config.ui_font.clone());
style.text_styles.insert(TextStyle::Button, config.ui_font.clone());
style.text_styles.insert(TextStyle::Heading, FontId {
size: (config.ui_font.size * 1.5).floor(),
family: config.ui_font.family.clone(),
});
style.text_styles.insert(TextStyle::Monospace, config.code_font.clone());
ctx.set_style(style);
}
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Show config").clicked() {
view_state.show_config = !view_state.show_config;
}
if ui.button("Quit").clicked() {
frame.close();
}
if ui.button("Show config").clicked() {
view_state.show_config = !view_state.show_config;
});
ui.menu_button("Tools", |ui| {
if ui.button("Demangle").clicked() {
view_state.show_demangle = !view_state.show_demangle;
}
});
});
@@ -221,31 +318,55 @@ impl eframe::App for App {
}
egui::Window::new("Config").open(&mut view_state.show_config).show(ctx, |ui| {
ui.label("Diff type:");
if egui::RadioButton::new(
view_state.diff_kind == DiffKind::SplitObj,
"Split object diff",
)
.ui(ui)
.on_hover_text("Compare individual object files")
.clicked()
{
view_state.diff_kind = DiffKind::SplitObj;
}
if egui::RadioButton::new(
view_state.diff_kind == DiffKind::WholeBinary,
"Whole binary diff",
)
.ui(ui)
.on_hover_text("Compare two full binaries")
.clicked()
{
view_state.diff_kind = DiffKind::WholeBinary;
}
ui.label("UI font:");
egui::introspection::font_id_ui(ui, &mut view_state.view_config.ui_font);
ui.separator();
ui.label("Code font:");
egui::introspection::font_id_ui(ui, &mut view_state.view_config.code_font);
ui.separator();
ui.label("Diff colors:");
if ui.button("Reset").clicked() {
view_state.view_config.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
}
let mut remove_at: Option<usize> = None;
let num_colors = view_state.view_config.diff_colors.len();
for (idx, color) in view_state.view_config.diff_colors.iter_mut().enumerate() {
ui.horizontal(|ui| {
ui.color_edit_button_srgba(color);
if num_colors > 1 {
if ui.small_button("-").clicked() {
remove_at = Some(idx);
}
}
});
}
if let Some(idx) = remove_at {
view_state.view_config.diff_colors.remove(idx);
}
if ui.small_button("+").clicked() {
view_state.view_config.diff_colors.push(Color32::BLACK);
}
});
egui::Window::new("Demangle").open(&mut view_state.show_demangle).show(ctx, |ui| {
ui.text_edit_singleline(&mut view_state.demangle_text);
ui.add_space(10.0);
if let Some(demangled) =
cwdemangle::demangle(&view_state.demangle_text, &Default::default())
{
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
ui.colored_label(Color32::LIGHT_BLUE, &demangled);
});
if ui.button("Copy").clicked() {
ui.output().copied_text = demangled;
}
} else {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
ui.colored_label(Color32::LIGHT_RED, "[invalid]");
});
}
});
// Windows + request_repaint_after breaks dialogs:
@@ -272,7 +393,7 @@ impl eframe::App for App {
eframe::set_value(storage, eframe::APP_KEY, self);
}
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &eframe::Frame) {
for job in &mut self.view_state.jobs {
if let Some(handle) = &job.handle {
if !handle.is_finished() {
@@ -305,10 +426,38 @@ impl eframe::App for App {
time: OffsetDateTime::now_utc(),
}));
}
JobResult::CheckUpdate(state) => {
self.view_state.check_update = Some(state);
}
JobResult::Update(state) => {
if let Ok(mut guard) = self.relaunch_path.lock() {
*guard = Some(state.exe_path);
}
self.should_relaunch = true;
}
}
}
Err(e) => {
log::error!("Failed to join job handle: {:?}", e);
Err(err) => {
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
anyhow::Error::msg(*msg)
} else if let Some(msg) = err.downcast_ref::<String>() {
anyhow::Error::msg(msg.clone())
} else {
anyhow::Error::msg("Thread panicked")
};
let result = job.status.write();
if let Ok(mut guard) = result {
guard.error = Some(err);
} else {
drop(result);
job.status = Arc::new(RwLock::new(JobStatus {
title: "Error".to_string(),
progress_percent: 0.0,
progress_items: None,
status: "".to_string(),
error: Some(err),
}));
}
}
}
}
@@ -355,6 +504,11 @@ impl eframe::App for App {
}
self.modified.store(false, Ordering::Relaxed);
}
if config.queue_update_check {
self.view_state.jobs.push(queue_check_update());
config.queue_update_check = false;
}
}
}
}

View File

@@ -12,6 +12,30 @@ use crate::{
},
};
fn no_diff_code(
arch: ObjArchitecture,
data: &Vec<u8>,
symbol: &mut ObjSymbol,
relocs: &Vec<ObjReloc>,
) -> Result<()> {
let code =
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let (_, ins) = match arch {
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs)?,
ObjArchitecture::Mips => {
mips::process_code(code, symbol.address, symbol.address + symbol.size, relocs)?
}
};
let mut diff = Vec::<ObjInsDiff>::new();
for i in ins {
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
}
resolve_branches(&mut diff);
symbol.instructions = diff;
Ok(())
}
pub fn diff_code(
arch: ObjArchitecture,
left_data: &[u8],
@@ -133,8 +157,8 @@ pub fn diff_code(
} else {
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
};
left_symbol.match_percent = percent;
right_symbol.match_percent = percent;
left_symbol.match_percent = Some(percent);
right_symbol.match_percent = Some(percent);
left_symbol.instructions = left_diff;
right_symbol.instructions = right_diff;
@@ -356,13 +380,101 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
&left_section.relocations,
&right_section.relocations,
)?;
} else {
no_diff_code(
left.architecture,
&left_section.data,
left_symbol,
&left_section.relocations,
)?;
}
}
for right_symbol in &mut right_section.symbols {
if right_symbol.instructions.is_empty() {
no_diff_code(
left.architecture,
&right_section.data,
right_symbol,
&right_section.relocations,
)?;
}
}
} else if left_section.kind == ObjSectionKind::Data {
diff_data(left_section, right_section);
// diff_data_symbols(left_section, right_section)?;
} else if left_section.kind == ObjSectionKind::Bss {
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
}
}
}
diff_bss_symbols(&mut left.common, &mut right.common)?;
Ok(())
}
fn diff_bss_symbols(left_symbols: &mut [ObjSymbol], right_symbols: &mut [ObjSymbol]) -> Result<()> {
for left_symbol in left_symbols {
if let Some(right_symbol) = find_symbol(right_symbols, &left_symbol.name) {
left_symbol.diff_symbol = Some(right_symbol.name.clone());
right_symbol.diff_symbol = Some(left_symbol.name.clone());
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
left_symbol.match_percent = Some(percent);
right_symbol.match_percent = Some(percent);
}
}
Ok(())
}
// WIP diff-by-symbol
#[allow(dead_code)]
fn diff_data_symbols(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
let mut left_ops = Vec::<u32>::with_capacity(left.symbols.len());
let mut right_ops = Vec::<u32>::with_capacity(right.symbols.len());
for left_symbol in &left.symbols {
let data = &left.data
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
let hash = twox_hash::xxh3::hash64(data);
left_ops.push(hash as u32);
}
for symbol in &right.symbols {
let data = &right.data[symbol.address as usize..(symbol.address + symbol.size) as usize];
let hash = twox_hash::xxh3::hash64(data);
right_ops.push(hash as u32);
}
let edit_ops = editops_find(&left_ops, &right_ops);
if edit_ops.is_empty() && !left.data.is_empty() {
let mut left_iter = left.symbols.iter_mut();
let mut right_iter = right.symbols.iter_mut();
loop {
let (left_symbol, right_symbol) = match (left_iter.next(), right_iter.next()) {
(Some(l), Some(r)) => (l, r),
(None, None) => break,
_ => return Err(anyhow::Error::msg("L/R mismatch in diff_data_symbols")),
};
let left_data = &left.data
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
let right_data = &right.data[right_symbol.address as usize
..(right_symbol.address + right_symbol.size) as usize];
left.data_diff.push(ObjDataDiff {
data: left_data.to_vec(),
kind: ObjDataDiffKind::None,
len: left_symbol.size as usize,
symbol: left_symbol.name.clone(),
});
right.data_diff.push(ObjDataDiff {
data: right_data.to_vec(),
kind: ObjDataDiffKind::None,
len: right_symbol.size as usize,
symbol: right_symbol.name.clone(),
});
left_symbol.diff_symbol = Some(right_symbol.name.clone());
left_symbol.match_percent = Some(100.0);
right_symbol.diff_symbol = Some(left_symbol.name.clone());
right_symbol.match_percent = Some(100.0);
}
return Ok(());
}
Ok(())
}
@@ -373,11 +485,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: left.data.clone(),
kind: ObjDataDiffKind::None,
len: left.data.len(),
symbol: String::new(),
}];
right.data_diff = vec![ObjDataDiff {
data: right.data.clone(),
kind: ObjDataDiffKind::None,
len: right.data.len(),
symbol: String::new(),
}];
return;
}
@@ -390,23 +504,7 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
let mut cur_left_data = Vec::<u8>::new();
let mut cur_right_data = Vec::<u8>::new();
for op in edit_ops {
if left_cur < op.first_start {
left_diff.push(ObjDataDiff {
data: left.data[left_cur..op.first_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.first_start - left_cur,
});
left_cur = op.first_start;
}
if right_cur < op.second_start {
right_diff.push(ObjDataDiff {
data: right.data[right_cur..op.second_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.second_start - right_cur,
});
right_cur = op.second_start;
}
if cur_op != op.op_type {
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
match cur_op {
LevEditType::Keep => {}
LevEditType::Replace => {
@@ -418,11 +516,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Insert => {
@@ -432,11 +532,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Delete => {
@@ -446,15 +548,35 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
}
}
}
if left_cur < op.first_start {
left_diff.push(ObjDataDiff {
data: left.data[left_cur..op.first_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.first_start - left_cur,
symbol: String::new(),
});
left_cur = op.first_start;
}
if right_cur < op.second_start {
right_diff.push(ObjDataDiff {
data: right.data[right_cur..op.second_start].to_vec(),
kind: ObjDataDiffKind::None,
len: op.second_start - right_cur,
symbol: String::new(),
});
right_cur = op.second_start;
}
match op.op_type {
LevEditType::Replace => {
cur_left_data.push(left.data[left_cur]);
@@ -504,11 +626,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Insert => {
@@ -518,11 +642,13 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
symbol: String::new(),
});
}
LevEditType::Delete => {
@@ -532,15 +658,34 @@ fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
symbol: String::new(),
});
}
}
if left_cur < left.data.len() {
left_diff.push(ObjDataDiff {
data: left.data[left_cur..].to_vec(),
kind: ObjDataDiffKind::None,
len: left.data.len() - left_cur,
symbol: String::new(),
});
}
if right_cur < right.data.len() {
right_diff.push(ObjDataDiff {
data: right.data[right_cur..].to_vec(),
kind: ObjDataDiffKind::None,
len: right.data.len() - right_cur,
symbol: String::new(),
});
}
left.data_diff = left_diff;
right.data_diff = right_diff;
}

View File

@@ -47,7 +47,8 @@ pub struct LevMatchingBlock {
pub len: usize,
}
pub fn editops_find(query: &[u8], choice: &[u8]) -> Vec<LevEditOp> {
pub fn editops_find<T>(query: &[T], choice: &[T]) -> Vec<LevEditOp>
where T: PartialEq {
let string_affix = Affix::find(query, choice);
let first_string_len = string_affix.first_string_len;
@@ -96,14 +97,17 @@ pub fn editops_find(query: &[u8], choice: &[u8]) -> Vec<LevEditOp> {
)
}
fn editops_from_cost_matrix(
string1: &[u8],
string2: &[u8],
fn editops_from_cost_matrix<T>(
string1: &[T],
string2: &[T],
len1: usize,
len2: usize,
prefix_len: usize,
cache_matrix: Vec<usize>,
) -> Vec<LevEditOp> {
) -> Vec<LevEditOp>
where
T: PartialEq,
{
let mut dir = 0;
let mut ops: Vec<LevEditOp> = vec![];
@@ -187,7 +191,8 @@ pub struct Affix {
}
impl Affix {
pub fn find(first_string: &[u8], second_string: &[u8]) -> Affix {
pub fn find<T>(first_string: &[T], second_string: &[T]) -> Affix
where T: PartialEq {
// remove common prefix and suffix (linear vs square runtime for levensthein)
let mut first_iter = first_string.iter();
let mut second_iter = second_string.iter();

View File

@@ -38,7 +38,7 @@ fn run_build(
}
pub fn queue_bindiff(config: Arc<RwLock<AppConfig>>) -> JobState {
queue_job(Job::BinDiff, move |status, cancel| {
queue_job("Binary diff", Job::BinDiff, move |status, cancel| {
run_build(status, cancel, config).map(JobResult::BinDiff)
})
}

33
src/jobs/check_update.rs Normal file
View File

@@ -0,0 +1,33 @@
use std::sync::mpsc::Receiver;
use anyhow::{Context, Result};
use self_update::{cargo_crate_version, update::Release};
use crate::{
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
update::{build_updater, BIN_NAME},
};
pub struct CheckUpdateResult {
pub update_available: bool,
pub latest_release: Release,
pub found_binary: bool,
}
fn run_check_update(status: &Status, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?;
let update_available =
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
let found_binary = latest_release.assets.iter().any(|a| a.name == BIN_NAME);
update_status(status, "Complete".to_string(), 1, 1, &cancel)?;
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
}
pub fn queue_check_update() -> JobState {
queue_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
run_check_update(status, cancel).map(JobResult::CheckUpdate)
})
}

View File

@@ -9,15 +9,22 @@ use std::{
use anyhow::Result;
use crate::jobs::{bindiff::BinDiffResult, objdiff::ObjDiffResult};
use crate::jobs::{
bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult,
update::UpdateResult,
};
pub mod bindiff;
pub mod check_update;
pub mod objdiff;
pub mod update;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Job {
ObjDiff,
BinDiff,
CheckUpdate,
Update,
}
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
pub struct JobState {
@@ -40,6 +47,8 @@ pub enum JobResult {
None,
ObjDiff(Box<ObjDiffResult>),
BinDiff(Box<BinDiffResult>),
CheckUpdate(Box<CheckUpdateResult>),
Update(Box<UpdateResult>),
}
fn should_cancel(rx: &Receiver<()>) -> bool {
@@ -52,14 +61,15 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
type Status = Arc<RwLock<JobStatus>>;
fn queue_job(
title: &str,
job_type: Job,
run: impl FnOnce(&Status, Receiver<()>) -> Result<JobResult> + Send + 'static,
) -> JobState {
let status = Arc::new(RwLock::new(JobStatus {
title: String::new(),
title: title.to_string(),
progress_percent: 0.0,
progress_items: None,
status: "".to_string(),
status: String::new(),
error: None,
}));
let status_clone = status.clone();

View File

@@ -19,6 +19,7 @@ pub struct BuildStatus {
pub success: bool,
pub log: String,
}
pub struct ObjDiffResult {
pub first_status: BuildStatus,
pub second_status: BuildStatus,
@@ -29,7 +30,7 @@ pub struct ObjDiffResult {
fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
match (|| -> Result<BuildStatus> {
let make = if config.custom_make.is_empty() { "make" } else { &config.custom_make };
let make = config.custom_make.as_deref().unwrap_or("make");
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new(make);
@@ -136,7 +137,7 @@ fn run_build(
}
pub fn queue_build(config: Arc<RwLock<AppConfig>>, diff_config: DiffConfig) -> JobState {
queue_job(Job::ObjDiff, move |status, cancel| {
queue_job("Object diff", Job::ObjDiff, move |status, cancel| {
run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff)
})
}

61
src/jobs/update.rs Normal file
View File

@@ -0,0 +1,61 @@
use std::{
env::{current_dir, current_exe},
fs,
fs::File,
path::PathBuf,
sync::mpsc::Receiver,
};
use anyhow::{Context, Result};
use const_format::formatcp;
use crate::{
jobs::{queue_job, update_status, Job, JobResult, JobState, Status},
update::{build_updater, BIN_NAME},
};
pub struct UpdateResult {
pub exe_path: PathBuf,
}
fn run_update(status: &Status, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?;
let asset = latest_release
.assets
.iter()
.find(|a| a.name == BIN_NAME)
.ok_or(anyhow::Error::msg(formatcp!("No release asset for {}", BIN_NAME)))?;
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
let tmp_path = tmp_dir.path().join(&asset.name);
let tmp_file = File::create(&tmp_path)?;
self_update::Download::from_url(&asset.download_url)
.set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
.download_to(&tmp_file)?;
update_status(status, "Extracting release".to_string(), 2, 3, &cancel)?;
let tmp_file = tmp_dir.path().join("replacement_tmp");
let target_file = current_exe()?;
self_update::Move::from_source(&tmp_path)
.replace_using_temp(&tmp_file)
.to_dest(&target_file)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&target_file)?.permissions();
perms.set_mode(0755);
fs::set_permissions(&target_file, perms)?;
}
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
Ok(Box::from(UpdateResult { exe_path: target_file }))
}
pub fn queue_update() -> JobState {
queue_job("Update app", Job::Update, move |status, cancel| {
run_update(status, cancel).map(JobResult::Update)
})
}

View File

@@ -7,4 +7,5 @@ mod diff;
mod editops;
mod jobs;
mod obj;
mod update;
mod views;

View File

@@ -1,6 +1,9 @@
#![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use std::{path::PathBuf, rc::Rc, sync::Mutex};
use cfg_if::cfg_if;
use time::UtcOffset;
// When compiling natively:
@@ -12,15 +15,40 @@ fn main() {
// Because localtime_r is unsound in multithreaded apps,
// we must call this before initializing eframe.
// https://github.com/time-rs/time/issues/293
let utc_offset = UtcOffset::current_local_offset().unwrap();
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
let exec_path_clone = exec_path.clone();
let native_options = eframe::NativeOptions::default();
// native_options.renderer = eframe::Renderer::Wgpu;
eframe::run_native(
"objdiff",
native_options,
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset))),
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset, exec_path_clone))),
);
// Attempt to relaunch application from the updated path
if let Ok(mut guard) = exec_path.lock() {
if let Some(path) = guard.take() {
cfg_if! {
if #[cfg(unix)] {
let result = exec::Command::new(path)
.args(&std::env::args().collect::<Vec<String>>())
.exec();
eprintln!("Failed to relaunch: {:?}", result);
} else {
let result = std::process::Command::new(path)
.args(std::env::args())
.spawn()
.unwrap()
.wait();
if let Err(e) = result {
eprintln!("Failed to relaunch: {:?}", e);
}
}
}
}
};
}
// when compiling to web using trunk.

View File

@@ -63,7 +63,7 @@ fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> R
addend,
diff_symbol: None,
instructions: vec![],
match_percent: 0.0,
match_percent: None,
})
}
@@ -81,7 +81,7 @@ fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
continue;
}
let name = section.name().context("Failed to process section name")?;
let data = section.data().context("Failed to read section data")?;
let data = section.uncompressed_data().context("Failed to read section data")?;
result.push(ObjSection {
name: name.to_string(),
kind: to_obj_section_kind(section.kind()),
@@ -183,7 +183,7 @@ fn find_section_symbol(
addend: offset_addr as i64,
diff_symbol: None,
instructions: vec![],
match_percent: 0.0,
match_percent: None,
})
}
@@ -290,8 +290,11 @@ fn relocations_by_section(
}
pub fn read(obj_path: &Path) -> Result<ObjInfo> {
let bin_data = fs::read(obj_path)?;
let obj_file = File::parse(&*bin_data)?;
let data = {
let file = fs::File::open(obj_path)?;
unsafe { memmap2::Mmap::map(&file) }?
};
let obj_file = File::parse(&*data)?;
let architecture = match obj_file.architecture() {
Architecture::PowerPc => ObjArchitecture::PowerPc,
Architecture::Mips => ObjArchitecture::Mips,

View File

@@ -109,6 +109,7 @@ pub struct ObjDataDiff {
pub data: Vec<u8>,
pub kind: ObjDataDiffKind,
pub len: usize,
pub symbol: String,
}
#[derive(Debug, Clone)]
pub struct ObjSymbol {
@@ -124,7 +125,7 @@ pub struct ObjSymbol {
// Diff
pub diff_symbol: Option<String>,
pub instructions: Vec<ObjInsDiff>,
pub match_percent: f32,
pub match_percent: Option<f32>,
}
#[derive(Debug, Copy, Clone)]
pub enum ObjArchitecture {

37
src/update.rs Normal file
View File

@@ -0,0 +1,37 @@
use cfg_if::cfg_if;
use const_format::formatcp;
use self_update::{cargo_crate_version, update::ReleaseUpdate};
pub const OS: &str = std::env::consts::OS;
cfg_if! {
if #[cfg(target_arch = "aarch64")] {
cfg_if! {
if #[cfg(any(windows, target_os = "macos"))] {
pub const ARCH: &str = "arm64";
} else {
pub const ARCH: &str = std::env::consts::ARCH;
}
}
} else if #[cfg(target_arch = "arm")] {
pub const ARCH: &str = "armv7l";
} else {
pub const ARCH: &str = std::env::consts::ARCH;
}
}
pub const GITHUB_USER: &str = "encounter";
pub const GITHUB_REPO: &str = "objdiff";
pub const BIN_NAME: &str =
formatcp!("{}-{}-{}{}", GITHUB_REPO, OS, ARCH, std::env::consts::EXE_SUFFIX);
pub const RELEASE_URL: &str =
formatcp!("https://github.com/{}/{}/releases/latest", GITHUB_USER, GITHUB_REPO);
pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
self_update::backends::github::Update::configure()
.repo_owner(GITHUB_USER)
.repo_name(GITHUB_REPO)
.bin_name(BIN_NAME)
.no_confirm(true)
.show_output(false)
.current_version(cargo_crate_version!())
.build()
}

View File

@@ -4,10 +4,14 @@ use std::sync::{Arc, RwLock};
#[cfg(windows)]
use anyhow::{Context, Result};
use const_format::formatcp;
use egui::{output::OpenUrl, Color32};
use self_update::cargo_crate_version;
use crate::{
app::{AppConfig, DiffKind, ViewState},
jobs::{bindiff::queue_bindiff, objdiff::queue_build},
jobs::{bindiff::queue_bindiff, objdiff::queue_build, update::queue_update},
update::RELEASE_URL,
};
#[cfg(windows)]
@@ -57,8 +61,50 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
left_obj,
right_obj,
project_dir_change,
queue_update_check,
auto_update_check,
} = &mut *config_guard;
ui.heading("Updates");
ui.checkbox(auto_update_check, "Check for updates on startup");
if ui.button("Check now").clicked() {
*queue_update_check = true;
}
ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| {
ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH")));
ui.label(formatcp!("Git commit: {}", env!("VERGEN_GIT_SHA")));
ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE")));
ui.label(formatcp!("Build type: {}", env!("VERGEN_CARGO_PROFILE")));
});
if let Some(state) = &view_state.check_update {
ui.label(format!("Latest version: {}", state.latest_release.version));
if state.update_available {
ui.colored_label(Color32::LIGHT_GREEN, "Update available");
ui.horizontal(|ui| {
if state.found_binary {
if ui
.button("Automatic")
.on_hover_text_at_pointer(
"Automatically download and replace the current build",
)
.clicked()
{
view_state.jobs.push(queue_update());
}
}
if ui
.button("Manual")
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
.clicked()
{
ui.output().open_url =
Some(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true });
}
});
}
}
ui.separator();
ui.heading("Build config");
#[cfg(windows)]
@@ -82,7 +128,14 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
}
ui.label("Custom make program:");
ui.text_edit_singleline(custom_make);
let mut custom_make_str = custom_make.clone().unwrap_or_default();
if ui.text_edit_singleline(&mut custom_make_str).changed() {
if custom_make_str.is_empty() {
*custom_make = None;
} else {
*custom_make = Some(custom_make_str);
}
}
ui.separator();

View File

@@ -5,10 +5,10 @@ use egui_extras::{Size, StripBuilder, TableBuilder};
use time::format_description;
use crate::{
app::{View, ViewState},
app::{View, ViewConfig, ViewState},
jobs::Job,
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
views::{write_text, COLOR_RED, FONT_SIZE},
views::{write_text, COLOR_RED},
};
const BYTES_PER_ROW: usize = 16;
@@ -17,12 +17,17 @@ fn find_section<'a>(obj: &'a ObjInfo, section_name: &str) -> Option<&'a ObjSecti
obj.sections.iter().find(|s| s.name == section_name)
}
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) {
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
}
let mut job = LayoutJob::default();
write_text(format!("{:08X}: ", address).as_str(), Color32::GRAY, &mut job);
write_text(
format!("{:08X}: ", address).as_str(),
Color32::GRAY,
&mut job,
config.code_font.clone(),
);
let mut cur_addr = 0usize;
for diff in diffs {
let base_color = match diff.kind {
@@ -34,7 +39,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
if diff.data.is_empty() {
let mut str = " ".repeat(diff.len);
str.push_str(" ".repeat(diff.len / 8).as_str());
write_text(str.as_str(), base_color, &mut job);
write_text(str.as_str(), base_color, &mut job, config.code_font.clone());
cur_addr += diff.len;
} else {
let mut text = String::new();
@@ -45,7 +50,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
text.push(' ');
}
}
write_text(text.as_str(), base_color, &mut job);
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
}
}
if cur_addr < BYTES_PER_ROW {
@@ -53,9 +58,9 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
let mut str = " ".to_string();
str.push_str(" ".repeat(n).as_str());
str.push_str(" ".repeat(n / 8).as_str());
write_text(str.as_str(), Color32::GRAY, &mut job);
write_text(str.as_str(), Color32::GRAY, &mut job, config.code_font.clone());
}
write_text(" ", Color32::GRAY, &mut job);
write_text(" ", Color32::GRAY, &mut job, config.code_font.clone());
for diff in diffs {
let base_color = match diff.kind {
ObjDataDiffKind::None => Color32::GRAY,
@@ -64,7 +69,12 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
ObjDataDiffKind::Insert => Color32::GREEN,
};
if diff.data.is_empty() {
write_text(" ".repeat(diff.len).as_str(), base_color, &mut job);
write_text(
" ".repeat(diff.len).as_str(),
base_color,
&mut job,
config.code_font.clone(),
);
} else {
let mut text = String::new();
for byte in &diff.data {
@@ -75,7 +85,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
text.push('.');
}
}
write_text(text.as_str(), base_color, &mut job);
write_text(text.as_str(), base_color, &mut job, config.code_font.clone());
}
}
ui.add(Label::new(job).sense(Sense::click()));
@@ -101,6 +111,8 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
},
kind: diff.kind,
len,
// TODO
symbol: String::new(),
});
remaining_in_row -= len;
cur_len += len;
@@ -121,6 +133,7 @@ fn data_table_ui(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
section_name: &str,
config: &ViewConfig,
) -> Option<()> {
let left_section = find_section(left_obj, section_name)?;
let right_section = find_section(right_obj, section_name)?;
@@ -135,13 +148,13 @@ fn data_table_ui(
let right_diffs = split_diffs(&right_section.data_diff);
table.body(|body| {
body.rows(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;
row.col(|ui| {
data_row_ui(ui, address, &left_diffs[row_index]);
data_row_ui(ui, address, &left_diffs[row_index], config);
});
row.col(|ui| {
data_row_ui(ui, address, &right_diffs[row_index]);
data_row_ui(ui, address, &right_diffs[row_index], config);
});
});
});
@@ -233,7 +246,13 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
.column(Size::relative(0.5))
.column(Size::relative(0.5))
.resizable(false);
data_table_ui(table, left_obj, right_obj, selected_symbol);
data_table_ui(
table,
left_obj,
right_obj,
selected_symbol,
&view_state.view_config,
);
}
});
});

View File

@@ -1,62 +1,62 @@
use std::default::Default;
use cwdemangle::demangle;
use egui::{text::LayoutJob, Color32, Label, Sense};
use egui::{text::LayoutJob, Color32, FontId, Label, Sense};
use egui_extras::{Size, StripBuilder, TableBuilder};
use ppc750cl::Argument;
use time::format_description;
use crate::{
app::{View, ViewState},
app::{View, ViewConfig, ViewState},
jobs::Job,
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc,
ObjRelocKind, ObjSymbol,
},
views::{symbol_diff::match_color_for_symbol, write_text, COLOR_RED, FONT_SIZE},
views::{symbol_diff::match_color_for_symbol, write_text, COLOR_RED},
};
fn write_reloc_name(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob) {
fn write_reloc_name(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob, font_id: FontId) {
let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name);
write_text(name, Color32::LIGHT_GRAY, job);
write_text(name, Color32::LIGHT_GRAY, job, font_id.clone());
if reloc.target.addend != 0 {
write_text(&format!("+{:X}", reloc.target.addend), color, job);
write_text(&format!("+{:X}", reloc.target.addend), color, job, font_id.clone());
}
}
fn write_reloc(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob) {
fn write_reloc(reloc: &ObjReloc, color: Color32, job: &mut LayoutJob, font_id: FontId) {
match reloc.kind {
ObjRelocKind::PpcAddr16Lo => {
write_reloc_name(reloc, color, job);
write_text("@l", color, job);
write_reloc_name(reloc, color, job, font_id.clone());
write_text("@l", color, job, font_id.clone());
}
ObjRelocKind::PpcAddr16Hi => {
write_reloc_name(reloc, color, job);
write_text("@h", color, job);
write_reloc_name(reloc, color, job, font_id.clone());
write_text("@h", color, job, font_id.clone());
}
ObjRelocKind::PpcAddr16Ha => {
write_reloc_name(reloc, color, job);
write_text("@ha", color, job);
write_reloc_name(reloc, color, job, font_id.clone());
write_text("@ha", color, job, font_id.clone());
}
ObjRelocKind::PpcEmbSda21 => {
write_reloc_name(reloc, color, job);
write_text("@sda21", color, job);
write_reloc_name(reloc, color, job, font_id.clone());
write_text("@sda21", color, job, font_id.clone());
}
ObjRelocKind::MipsHi16 => {
write_text("%hi(", color, job);
write_reloc_name(reloc, color, job);
write_text(")", color, job);
write_text("%hi(", color, job, font_id.clone());
write_reloc_name(reloc, color, job, font_id.clone());
write_text(")", color, job, font_id.clone());
}
ObjRelocKind::MipsLo16 => {
write_text("%lo(", color, job);
write_reloc_name(reloc, color, job);
write_text(")", color, job);
write_text("%lo(", color, job, font_id.clone());
write_reloc_name(reloc, color, job, font_id.clone());
write_text(")", color, job, font_id.clone());
}
ObjRelocKind::Absolute
| ObjRelocKind::PpcRel24
| ObjRelocKind::PpcRel14
| ObjRelocKind::Mips26 => {
write_reloc_name(reloc, color, job);
write_reloc_name(reloc, color, job, font_id.clone());
}
};
}
@@ -67,6 +67,7 @@ fn write_ins(
args: &[Option<ObjInsArgDiff>],
base_addr: u32,
job: &mut LayoutJob,
config: &ViewConfig,
) {
let base_color = match diff_kind {
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => {
@@ -83,54 +84,60 @@ fn write_ins(
_ => base_color,
},
job,
config.code_font.clone(),
);
let mut writing_offset = false;
for (i, arg) in ins.args.iter().enumerate() {
if i == 0 {
write_text(" ", base_color, job);
write_text(" ", base_color, job, config.code_font.clone());
}
if i > 0 && !writing_offset {
write_text(", ", base_color, job);
write_text(", ", base_color, job, config.code_font.clone());
}
let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) {
COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
config.diff_colors[diff.idx % config.diff_colors.len()]
} else {
base_color
};
match arg {
ObjInsArg::PpcArg(arg) => match arg {
Argument::Offset(val) => {
write_text(&format!("{}", val), color, job);
write_text("(", base_color, job);
write_text(&format!("{}", val), color, job, config.code_font.clone());
write_text("(", base_color, job, config.code_font.clone());
writing_offset = true;
continue;
}
Argument::Uimm(_) | Argument::Simm(_) => {
write_text(&format!("{}", arg), color, job);
write_text(&format!("{}", arg), color, job, config.code_font.clone());
}
_ => {
write_text(&format!("{}", arg), color, job);
write_text(&format!("{}", arg), color, job, config.code_font.clone());
}
},
ObjInsArg::Reloc => {
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job);
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job, config.code_font.clone());
}
ObjInsArg::RelocWithBase => {
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job);
write_text("(", base_color, job);
write_reloc(ins.reloc.as_ref().unwrap(), base_color, job, config.code_font.clone());
write_text("(", base_color, job, config.code_font.clone());
writing_offset = true;
continue;
}
ObjInsArg::MipsArg(str) => {
write_text(str.strip_prefix('$').unwrap_or(str), color, job);
write_text(
str.strip_prefix('$').unwrap_or(str),
color,
job,
config.code_font.clone(),
);
}
ObjInsArg::BranchOffset(offset) => {
let addr = offset + ins.address as i32 - base_addr as i32;
write_text(&format!("{:x}", addr), color, job);
write_text(&format!("{:x}", addr), color, job, config.code_font.clone());
}
}
if writing_offset {
write_text(")", base_color, job);
write_text(")", base_color, job, config.code_font.clone());
writing_offset = false;
}
}
@@ -233,24 +240,12 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
});
}
const COLOR_ROTATION: [Color32; 9] = [
Color32::from_rgb(255, 0, 255),
Color32::from_rgb(0, 255, 255),
Color32::from_rgb(0, 128, 0),
Color32::from_rgb(255, 0, 0),
Color32::from_rgb(255, 255, 0),
Color32::from_rgb(255, 192, 203),
Color32::from_rgb(0, 0, 255),
Color32::from_rgb(0, 255, 0),
Color32::from_rgb(128, 128, 128),
];
fn find_symbol<'a>(obj: &'a ObjInfo, section_name: &str, name: &str) -> Option<&'a ObjSymbol> {
let section = obj.sections.iter().find(|s| s.name == section_name)?;
section.symbols.iter().find(|s| s.name == name)
}
fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) {
fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol, config: &ViewConfig) {
if ins_diff.kind != ObjInsDiffKind::None {
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
}
@@ -268,15 +263,26 @@ fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol) {
&format!("{:<6}", format!("{:x}:", ins.address - symbol.address as u32)),
base_color,
&mut job,
config.code_font.clone(),
);
if let Some(branch) = &ins_diff.branch_from {
write_text("~> ", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job);
write_text(
"~> ",
config.diff_colors[branch.branch_idx % config.diff_colors.len()],
&mut job,
config.code_font.clone(),
);
} else {
write_text(" ", base_color, &mut job);
write_text(" ", base_color, &mut job, config.code_font.clone());
}
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job);
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, config);
if let Some(branch) = &ins_diff.branch_to {
write_text(" ~>", COLOR_ROTATION[branch.branch_idx % COLOR_ROTATION.len()], &mut job);
write_text(
" ~>",
config.diff_colors[branch.branch_idx % config.diff_colors.len()],
&mut job,
config.code_font.clone(),
);
}
ui.add(Label::new(job).sense(Sense::click()))
.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
@@ -291,16 +297,22 @@ fn asm_table_ui(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
fn_name: &str,
config: &ViewConfig,
) -> Option<()> {
let left_symbol = find_symbol(left_obj, ".text", fn_name)?;
let right_symbol = find_symbol(right_obj, ".text", fn_name)?;
let left_symbol = find_symbol(left_obj, ".text", fn_name);
let right_symbol = find_symbol(right_obj, ".text", fn_name);
let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?;
table.body(|body| {
body.rows(FONT_SIZE, left_symbol.instructions.len(), |row_index, mut row| {
body.rows(config.code_font.size, instructions_len, |row_index, mut row| {
row.col(|ui| {
asm_row_ui(ui, &left_symbol.instructions[row_index], left_symbol);
if let Some(symbol) = left_symbol {
asm_row_ui(ui, &symbol.instructions[row_index], symbol, config);
}
});
row.col(|ui| {
asm_row_ui(ui, &right_symbol.instructions[row_index], right_symbol);
if let Some(symbol) = right_symbol {
asm_row_ui(ui, &symbol.instructions[row_index], symbol, config);
}
});
});
});
@@ -379,14 +391,16 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if let Some(obj) = &result.second_obj {
if let Some(symbol) = find_symbol(obj, ".text", selected_symbol)
{
ui.colored_label(
match_color_for_symbol(symbol),
&format!("{:.0}%", symbol.match_percent),
);
}
if let Some(match_percent) = result
.second_obj
.as_ref()
.and_then(|obj| find_symbol(obj, ".text", selected_symbol))
.and_then(|symbol| symbol.match_percent)
{
ui.colored_label(
match_color_for_symbol(match_percent),
&format!("{:.0}%", match_percent),
);
}
ui.label("Diff base:");
ui.separator();
@@ -404,7 +418,13 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
.column(Size::relative(0.5))
.column(Size::relative(0.5))
.resizable(false);
asm_table_ui(table, left_obj, right_obj, selected_symbol);
asm_table_ui(
table,
left_obj,
right_obj,
selected_symbol,
&view_state.view_config,
);
}
});
});

View File

@@ -2,13 +2,26 @@ use egui::{Color32, ProgressBar, Widget};
use crate::app::ViewState;
pub fn jobs_ui(ui: &mut egui::Ui, view_state: &ViewState) {
pub fn jobs_ui(ui: &mut egui::Ui, view_state: &mut ViewState) {
ui.label("Jobs");
for job in &view_state.jobs {
let mut remove_job: Option<usize> = None;
for (idx, job) in view_state.jobs.iter_mut().enumerate() {
if let Ok(status) = job.status.read() {
ui.group(|ui| {
ui.label(&status.title);
ui.horizontal(|ui| {
ui.label(&status.title);
if ui.small_button("").clicked() {
if job.handle.is_some() {
job.should_remove = true;
if let Err(e) = job.cancel.send(()) {
eprintln!("Failed to cancel job: {:?}", e);
}
} else {
remove_job = Some(idx);
}
}
});
let mut bar = ProgressBar::new(status.progress_percent);
if let Some(items) = &status.progress_items {
bar = bar.text(format!("{} / {}", items[0], items[1]));
@@ -35,4 +48,8 @@ pub fn jobs_ui(ui: &mut egui::Ui, view_state: &ViewState) {
});
}
}
if let Some(idx) = remove_job {
view_state.jobs.remove(idx);
}
}

View File

@@ -1,4 +1,4 @@
use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat};
use egui::{text::LayoutJob, Color32, FontId, TextFormat};
pub(crate) mod config;
pub(crate) mod data_diff;
@@ -6,11 +6,8 @@ pub(crate) mod function_diff;
pub(crate) mod jobs;
pub(crate) mod symbol_diff;
const FONT_SIZE: f32 = 14.0;
const FONT_ID: FontId = FontId::new(FONT_SIZE, FontFamily::Monospace);
const COLOR_RED: Color32 = Color32::from_rgb(200, 40, 41);
fn write_text(str: &str, color: Color32, job: &mut LayoutJob) {
job.append(str, 0.0, TextFormat { font_id: FONT_ID, color, ..Default::default() });
fn write_text(str: &str, color: Color32, job: &mut LayoutJob, font_id: FontId) {
job.append(str, 0.0, TextFormat { font_id, color, ..Default::default() });
}

View File

@@ -4,16 +4,16 @@ use egui::{
use egui_extras::{Size, StripBuilder};
use crate::{
app::{View, ViewState},
app::{View, ViewConfig, ViewState},
jobs::objdiff::BuildStatus,
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
views::write_text,
};
pub fn match_color_for_symbol(symbol: &ObjSymbol) -> Color32 {
if symbol.match_percent == 100.0 {
pub fn match_color_for_symbol(match_percent: f32) -> Color32 {
if match_percent == 100.0 {
Color32::GREEN
} else if symbol.match_percent >= 50.0 {
} else if match_percent >= 50.0 {
Color32::LIGHT_BLUE
} else {
Color32::RED
@@ -45,7 +45,11 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol) {
ui.colored_label(Color32::WHITE, format!("Name: {}", symbol.name));
ui.colored_label(Color32::WHITE, format!("Address: {:x}", symbol.address));
ui.colored_label(Color32::WHITE, format!("Size: {:x}", symbol.size));
if symbol.size_known {
ui.colored_label(Color32::WHITE, format!("Size: {:x}", symbol.size));
} else {
ui.colored_label(Color32::WHITE, format!("Size: {:x} (assumed)", symbol.size));
}
});
}
@@ -56,6 +60,7 @@ fn symbol_ui(
highlighted_symbol: &mut Option<String>,
selected_symbol: &mut Option<String>,
current_view: &mut View,
config: &ViewConfig,
) {
let mut job = LayoutJob::default();
let name: &str =
@@ -64,28 +69,29 @@ fn symbol_ui(
if let Some(sym) = highlighted_symbol {
selected = sym == &symbol.name;
}
write_text("[", Color32::GRAY, &mut job);
write_text("[", Color32::GRAY, &mut job, config.code_font.clone());
if symbol.flags.0.contains(ObjSymbolFlags::Common) {
write_text("c", Color32::from_rgb(0, 255, 255), &mut job);
write_text("c", Color32::from_rgb(0, 255, 255), &mut job, config.code_font.clone());
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) {
write_text("g", Color32::GREEN, &mut job);
write_text("g", Color32::GREEN, &mut job, config.code_font.clone());
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) {
write_text("l", Color32::GRAY, &mut job);
write_text("l", Color32::GRAY, &mut job, config.code_font.clone());
}
if symbol.flags.0.contains(ObjSymbolFlags::Weak) {
write_text("w", Color32::GRAY, &mut job);
write_text("w", Color32::GRAY, &mut job, config.code_font.clone());
}
write_text("] ", Color32::GRAY, &mut job);
if symbol.match_percent > 0.0 {
write_text("(", Color32::GRAY, &mut job);
write_text("] ", Color32::GRAY, &mut job, config.code_font.clone());
if let Some(match_percent) = symbol.match_percent {
write_text("(", Color32::GRAY, &mut job, config.code_font.clone());
write_text(
&format!("{:.0}%", symbol.match_percent),
match_color_for_symbol(symbol),
&format!("{:.0}%", match_percent),
match_color_for_symbol(match_percent),
&mut job,
config.code_font.clone(),
);
write_text(") ", Color32::GRAY, &mut job);
write_text(") ", Color32::GRAY, &mut job, config.code_font.clone());
}
write_text(name, Color32::WHITE, &mut job);
write_text(name, Color32::WHITE, &mut job, config.code_font.clone());
let response = SelectableLabel::new(selected, job)
.ui(ui)
.context_menu(|ui| symbol_context_menu_ui(ui, symbol))
@@ -123,6 +129,7 @@ fn symbol_list_ui(
current_view: &mut View,
reverse_function_order: bool,
search: &mut String,
config: &ViewConfig,
) {
ui.text_edit_singleline(search);
let lower_search = search.to_ascii_lowercase();
@@ -142,6 +149,7 @@ fn symbol_list_ui(
highlighted_symbol,
selected_symbol,
current_view,
config,
);
}
});
@@ -163,6 +171,7 @@ fn symbol_list_ui(
highlighted_symbol,
selected_symbol,
current_view,
config,
);
}
} else {
@@ -177,6 +186,7 @@ fn symbol_list_ui(
highlighted_symbol,
selected_symbol,
current_view,
config,
);
}
}
@@ -255,6 +265,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) {
current_view,
view_state.reverse_fn_order,
search,
&view_state.view_config,
);
});
}
@@ -274,6 +285,7 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) {
current_view,
view_state.reverse_fn_order,
search,
&view_state.view_config,
);
});
}