Initial work on data diffing

This commit is contained in:
Luke Street 2022-11-06 00:49:46 -04:00
parent ff9b378445
commit d9efa97c30
7 changed files with 489 additions and 16 deletions

View File

@ -20,16 +20,18 @@ use crate::{
Job, JobResult, JobState,
},
views::{
config::config_ui, function_diff::function_diff_ui, jobs::jobs_ui,
config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui,
symbol_diff::symbol_diff_ui,
},
};
#[allow(clippy::enum_variant_names)]
#[derive(Default, Eq, PartialEq)]
pub enum View {
#[default]
SymbolDiff,
FunctionDiff,
DataDiff,
}
#[derive(Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
@ -197,6 +199,16 @@ impl eframe::App for App {
.push(queue_build(config.clone(), view_state.diff_config.clone()));
}
});
} else if view_state.current_view == View::DataDiff
&& matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success)
{
egui::CentralPanel::default().show(ctx, |ui| {
if data_diff_ui(ui, view_state) {
view_state
.jobs
.push(queue_build(config.clone(), view_state.diff_config.clone()));
}
});
} else {
egui::SidePanel::left("side_panel").show(ctx, |ui| {
config_ui(ui, config, view_state);

View File

@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, mem::take};
use anyhow::Result;
@ -6,9 +6,9 @@ use crate::{
app::DiffConfig,
editops::{editops_find, LevEditType},
obj::{
mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom,
ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSection, ObjSectionKind,
ObjSymbol, ObjSymbolFlags,
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc,
ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags,
},
};
@ -340,13 +340,13 @@ fn find_symbol<'a>(symbols: &'a mut [ObjSymbol], name: &str) -> Option<&'a mut O
pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> {
for left_section in &mut left.sections {
if let Some(right_section) = find_section(right, &left_section.name) {
for left_symbol in &mut left_section.symbols {
if let Some(right_symbol) =
find_symbol(&mut right_section.symbols, &left_symbol.name)
{
left_symbol.diff_symbol = Some(right_symbol.name.clone());
right_symbol.diff_symbol = Some(left_symbol.name.clone());
if left_section.kind == ObjSectionKind::Code {
if left_section.kind == ObjSectionKind::Code {
for left_symbol in &mut left_section.symbols {
if let Some(right_symbol) =
find_symbol(&mut right_section.symbols, &left_symbol.name)
{
left_symbol.diff_symbol = Some(right_symbol.name.clone());
right_symbol.diff_symbol = Some(left_symbol.name.clone());
diff_code(
left.architecture,
&left_section.data,
@ -358,8 +358,189 @@ pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffCon
)?;
}
}
} else if left_section.kind == ObjSectionKind::Data {
diff_data(left_section, right_section);
}
}
}
Ok(())
}
fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
let edit_ops = editops_find(&left.data, &right.data);
if edit_ops.is_empty() && !left.data.is_empty() {
left.data_diff = vec![ObjDataDiff {
data: left.data.clone(),
kind: ObjDataDiffKind::None,
len: left.data.len(),
}];
right.data_diff = vec![ObjDataDiff {
data: right.data.clone(),
kind: ObjDataDiffKind::None,
len: right.data.len(),
}];
return;
}
let mut left_diff = Vec::<ObjDataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new();
let mut left_cur = 0usize;
let mut right_cur = 0usize;
let mut cur_op = LevEditType::Keep;
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 {
match cur_op {
LevEditType::Keep => {}
LevEditType::Replace => {
let left_data = take(&mut cur_left_data);
let right_data = take(&mut cur_right_data);
let left_data_len = left_data.len();
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
});
}
LevEditType::Insert => {
let right_data = take(&mut cur_right_data);
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
});
}
LevEditType::Delete => {
let left_data = take(&mut cur_left_data);
let left_data_len = left_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
});
}
}
}
match op.op_type {
LevEditType::Replace => {
cur_left_data.push(left.data[left_cur]);
cur_right_data.push(right.data[right_cur]);
left_cur += 1;
right_cur += 1;
}
LevEditType::Insert => {
cur_right_data.push(right.data[right_cur]);
right_cur += 1;
}
LevEditType::Delete => {
cur_left_data.push(left.data[left_cur]);
left_cur += 1;
}
LevEditType::Keep => unreachable!(),
}
cur_op = op.op_type;
}
// if left_cur < left.data.len() {
// let len = left.data.len() - left_cur;
// left_diff.push(ObjDataDiff {
// data: left.data[left_cur..].to_vec(),
// kind: ObjDataDiffKind::Delete,
// len,
// });
// right_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Delete, len });
// } else if right_cur < right.data.len() {
// let len = right.data.len() - right_cur;
// left_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Insert, len });
// right_diff.push(ObjDataDiff {
// data: right.data[right_cur..].to_vec(),
// kind: ObjDataDiffKind::Insert,
// len,
// });
// }
// TODO: merge with above
match cur_op {
LevEditType::Keep => {}
LevEditType::Replace => {
let left_data = take(&mut cur_left_data);
let right_data = take(&mut cur_right_data);
let left_data_len = left_data.len();
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Replace,
len: left_data_len,
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Replace,
len: right_data_len,
});
}
LevEditType::Insert => {
let right_data = take(&mut cur_right_data);
let right_data_len = right_data.len();
left_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Insert,
len: right_data_len,
});
right_diff.push(ObjDataDiff {
data: right_data,
kind: ObjDataDiffKind::Insert,
len: right_data_len,
});
}
LevEditType::Delete => {
let left_data = take(&mut cur_left_data);
let left_data_len = left_data.len();
left_diff.push(ObjDataDiff {
data: left_data,
kind: ObjDataDiffKind::Delete,
len: left_data_len,
});
right_diff.push(ObjDataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
len: left_data_len,
});
}
}
left.data_diff = left_diff;
right.data_diff = right_diff;
}

View File

@ -91,6 +91,8 @@ fn filter_sections(obj_file: &File<'_>) -> Result<Vec<ObjSection>> {
index: section.index().0,
symbols: Vec::new(),
relocations: Vec::new(),
data_diff: vec![],
match_percent: 0.0,
});
}
result.sort_by(|a, b| a.name.cmp(&b.name));

View File

@ -32,6 +32,10 @@ pub struct ObjSection {
pub index: usize,
pub symbols: Vec<ObjSymbol>,
pub relocations: Vec<ObjReloc>,
// Diff
pub data_diff: Vec<ObjDataDiff>,
pub match_percent: f32,
}
#[derive(Debug, Clone)]
pub enum ObjInsArg {
@ -92,6 +96,20 @@ pub struct ObjInsDiff {
/// Arg diffs
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjDataDiffKind {
#[default]
None,
Replace,
Delete,
Insert,
}
#[derive(Debug, Clone, Default)]
pub struct ObjDataDiff {
pub data: Vec<u8>,
pub kind: ObjDataDiffKind,
pub len: usize,
}
#[derive(Debug, Clone)]
pub struct ObjSymbol {
pub name: String,

242
src/views/data_diff.rs Normal file
View File

@ -0,0 +1,242 @@
use std::{cmp::min, default::Default, mem::take};
use egui::{text::LayoutJob, Color32, Label, Sense};
use egui_extras::{Size, StripBuilder, TableBuilder};
use time::format_description;
use crate::{
app::{View, ViewState},
jobs::Job,
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection},
views::{write_text, COLOR_RED, FONT_SIZE},
};
const BYTES_PER_ROW: usize = 16;
fn find_section<'a>(obj: &'a ObjInfo, section_name: &str) -> Option<&'a ObjSection> {
obj.sections.iter().find(|s| s.name == section_name)
}
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff]) {
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);
let mut cur_addr = 0usize;
for diff in diffs {
let base_color = match diff.kind {
ObjDataDiffKind::None => Color32::GRAY,
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
ObjDataDiffKind::Delete => COLOR_RED,
ObjDataDiffKind::Insert => Color32::GREEN,
};
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);
cur_addr += diff.len;
} else {
let mut text = String::new();
for byte in &diff.data {
text.push_str(format!("{:02X} ", byte).as_str());
cur_addr += 1;
if cur_addr % 8 == 0 {
text.push(' ');
}
}
write_text(text.as_str(), base_color, &mut job);
}
}
if cur_addr < BYTES_PER_ROW {
let n = BYTES_PER_ROW - cur_addr;
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(" ", Color32::GRAY, &mut job);
for diff in diffs {
let base_color = match diff.kind {
ObjDataDiffKind::None => Color32::GRAY,
ObjDataDiffKind::Replace => Color32::LIGHT_BLUE,
ObjDataDiffKind::Delete => COLOR_RED,
ObjDataDiffKind::Insert => Color32::GREEN,
};
if diff.data.is_empty() {
write_text(" ".repeat(diff.len).as_str(), base_color, &mut job);
} else {
let mut text = String::new();
for byte in &diff.data {
let c = char::from(*byte);
if c.is_ascii() && !c.is_ascii_control() {
text.push(c);
} else {
text.push('.');
}
}
write_text(text.as_str(), base_color, &mut job);
}
}
ui.add(Label::new(job).sense(Sense::click()));
// .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
// .context_menu(|ui| ins_context_menu(ui, ins));
}
fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
let mut split_diffs = Vec::<Vec<ObjDataDiff>>::new();
let mut row_diffs = Vec::<ObjDataDiff>::new();
let mut cur_addr = 0usize;
for diff in diffs {
let mut cur_len = 0usize;
while cur_len < diff.len {
let remaining_len = diff.len - cur_len;
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
let len = min(remaining_len, remaining_in_row);
row_diffs.push(ObjDataDiff {
data: if diff.data.is_empty() {
Vec::new()
} else {
diff.data[cur_len..cur_len + len].to_vec()
},
kind: diff.kind,
len,
});
remaining_in_row -= len;
cur_len += len;
cur_addr += len;
if remaining_in_row == 0 {
split_diffs.push(take(&mut row_diffs));
}
}
}
if !row_diffs.is_empty() {
split_diffs.push(take(&mut row_diffs));
}
split_diffs
}
fn data_table_ui(
table: TableBuilder<'_>,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
section_name: &str,
) -> Option<()> {
let left_section = find_section(left_obj, section_name)?;
let right_section = find_section(right_obj, section_name)?;
let total_bytes = left_section.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if total_bytes == 0 {
return None;
}
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
let left_diffs = split_diffs(&left_section.data_diff);
let right_diffs = split_diffs(&right_section.data_diff);
table.body(|body| {
body.rows(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]);
});
row.col(|ui| {
data_row_ui(ui, address, &right_diffs[row_index]);
});
});
});
Some(())
}
pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool {
let mut rebuild = false;
if let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol)
{
StripBuilder::new(ui)
.size(Size::exact(20.0))
.size(Size::exact(40.0))
.size(Size::remainder())
.vertical(|mut strip| {
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Back").clicked() {
view_state.current_view = View::SymbolDiff;
}
});
});
strip.cell(|ui| {
ui.horizontal(|ui| {
if ui.button("Build").clicked() {
rebuild = true;
}
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
if view_state
.jobs
.iter()
.any(|job| job.job_type == Job::ObjDiff)
{
ui.label("Building...");
} else {
ui.label("Last built:");
let format =
format_description::parse("[hour]:[minute]:[second]")
.unwrap();
ui.label(
result
.time
.to_offset(view_state.utc_offset)
.format(&format)
.unwrap(),
);
}
});
});
});
});
});
strip.strip(|builder| {
builder.sizes(Size::remainder(), 2).horizontal(|mut strip| {
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.colored_label(Color32::WHITE, selected_symbol);
ui.label("Diff target:");
ui.separator();
});
});
strip.cell(|ui| {
ui.scope(|ui| {
ui.style_mut().override_text_style =
Some(egui::TextStyle::Monospace);
ui.style_mut().wrap = Some(false);
ui.label("");
ui.label("Diff base:");
ui.separator();
});
});
});
});
strip.cell(|ui| {
if let (Some(left_obj), Some(right_obj)) =
(&result.first_obj, &result.second_obj)
{
let table = TableBuilder::new(ui)
.striped(false)
.cell_layout(egui::Layout::left_to_right(egui::Align::Min))
.column(Size::relative(0.5))
.column(Size::relative(0.5))
.resizable(false);
data_table_ui(table, left_obj, right_obj, selected_symbol);
}
});
});
}
rebuild
}

View File

@ -1,6 +1,7 @@
use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat};
pub(crate) mod config;
pub(crate) mod data_diff;
pub(crate) mod function_diff;
pub(crate) mod jobs;
pub(crate) mod symbol_diff;

View File

@ -6,7 +6,7 @@ use egui_extras::{Size, StripBuilder};
use crate::{
app::{View, ViewState},
jobs::objdiff::BuildStatus,
obj::{ObjInfo, ObjSymbol, ObjSymbolFlags},
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
views::write_text,
};
@ -52,6 +52,7 @@ fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol) {
fn symbol_ui(
ui: &mut Ui,
symbol: &ObjSymbol,
section: Option<&ObjSection>,
highlighted_symbol: &mut Option<String>,
selected_symbol: &mut Option<String>,
current_view: &mut View,
@ -90,8 +91,15 @@ fn symbol_ui(
.context_menu(|ui| symbol_context_menu_ui(ui, symbol))
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol));
if response.clicked() {
*selected_symbol = Some(symbol.name.clone());
*current_view = View::FunctionDiff;
if let Some(section) = section {
if section.kind == ObjSectionKind::Code {
*selected_symbol = Some(symbol.name.clone());
*current_view = View::FunctionDiff;
} else if section.kind == ObjSectionKind::Data {
*selected_symbol = Some(section.name.clone());
*current_view = View::DataDiff;
}
}
} else if response.hovered() {
*highlighted_symbol = Some(symbol.name.clone());
}
@ -127,7 +135,14 @@ fn symbol_list_ui(
if !obj.common.is_empty() {
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
for symbol in &obj.common {
symbol_ui(ui, symbol, highlighted_symbol, selected_symbol, current_view);
symbol_ui(
ui,
symbol,
None,
highlighted_symbol,
selected_symbol,
current_view,
);
}
});
}
@ -144,6 +159,7 @@ fn symbol_list_ui(
symbol_ui(
ui,
symbol,
Some(section),
highlighted_symbol,
selected_symbol,
current_view,
@ -157,6 +173,7 @@ fn symbol_list_ui(
symbol_ui(
ui,
symbol,
Some(section),
highlighted_symbol,
selected_symbol,
current_view,