Refactor data diffing & expose WASM API (#256)

* Refactor data diffing & expose WASM API

* Update test snapshots
This commit is contained in:
Luke Street 2025-09-07 18:59:46 -06:00 committed by GitHub
parent f7cb494a62
commit fbdaa89cc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 444 additions and 255 deletions

8
Cargo.lock generated
View File

@ -3435,7 +3435,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "3.1.1"
version = "3.2.0"
dependencies = [
"anyhow",
"argp",
@ -3458,7 +3458,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "3.1.1"
version = "3.2.0"
dependencies = [
"anyhow",
"arm-attr",
@ -3513,7 +3513,7 @@ dependencies = [
[[package]]
name = "objdiff-gui"
version = "3.1.1"
version = "3.2.0"
dependencies = [
"anyhow",
"argp",
@ -3550,7 +3550,7 @@ dependencies = [
[[package]]
name = "objdiff-wasm"
version = "3.1.1"
version = "3.2.0"
dependencies = [
"log",
"objdiff-core",

View File

@ -14,7 +14,7 @@ default-members = [
resolver = "3"
[workspace.package]
version = "3.1.1"
version = "3.2.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"

View File

@ -5,7 +5,7 @@ use anyhow::{Result, anyhow};
use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
use super::{
DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
DataDiff, DataDiffKind, DataDiffRow, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
code::{address_eq, section_name_eq},
};
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
@ -111,14 +111,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(),
kind,
len,
..Default::default()
size: len,
});
right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(),
kind,
len,
..Default::default()
size: len,
});
if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) {
@ -127,14 +125,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Insert,
len,
..Default::default()
size: len,
});
right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(),
kind: DataDiffKind::Insert,
len,
..Default::default()
size: len,
});
}
Ordering::Greater => {
@ -142,14 +138,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(),
kind: DataDiffKind::Delete,
len,
..Default::default()
size: len,
});
right_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Delete,
len,
..Default::default()
size: len,
});
}
Ordering::Equal => {}
@ -219,16 +213,17 @@ fn diff_data_relocs_for_range<'left, 'right>(
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
let section = &obj.sections[section_idx];
let len = section.data.len();
let data = &section.data[0..len];
let data_diff =
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
let data_diff = vec![DataDiff {
data: section.data.0.clone(),
kind: DataDiffKind::None,
size: section.data.len(),
}];
let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() {
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len;
let range = reloc.address..reloc.address + reloc_len as u64;
reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(),
kind: DataDiffKind::None,
@ -279,8 +274,7 @@ pub fn diff_data_section(
) {
if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
let range = left_reloc.relocation.address as usize
..left_reloc.relocation.address as usize + len;
let range = left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(),
kind: diff_kind,
@ -289,8 +283,7 @@ pub fn diff_data_section(
}
if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
let range = right_reloc.relocation.address as usize
..right_reloc.relocation.address as usize + len;
let range = right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(),
kind: diff_kind,
@ -345,9 +338,11 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
let range = start as usize..end as usize;
let data = &section.data[range.clone()];
let len = symbol.size as usize;
let data_diff =
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
let data_diff = vec![DataDiff {
data: data.to_vec(),
kind: DataDiffKind::None,
size: symbol.size as usize,
}];
let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() {
@ -355,7 +350,7 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
continue;
}
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len;
let range = reloc.address..reloc.address + reloc_len as u64;
reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(),
kind: DataDiffKind::None,
@ -363,12 +358,12 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
});
}
let data_rows = build_data_diff_rows(&data_diff, &reloc_diffs, symbol.address);
Ok(SymbolDiff {
target_symbol: None,
match_percent: None,
diff_score: None,
data_diff,
data_reloc_diff: reloc_diffs,
data_rows,
..Default::default()
})
}
@ -454,8 +449,8 @@ pub fn diff_data_symbol(
if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
let range = left_reloc.relocation.address as usize
..left_reloc.relocation.address as usize + len;
let range =
left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(),
kind: diff_kind,
@ -464,8 +459,8 @@ pub fn diff_data_symbol(
}
if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
let range = right_reloc.relocation.address as usize
..right_reloc.relocation.address as usize + len;
let range =
right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(),
kind: diff_kind,
@ -486,23 +481,29 @@ pub fn diff_data_symbol(
}
}
left_reloc_diffs
.sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
right_reloc_diffs
.sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
let match_percent = match_ratio * 100.0;
let left_rows = build_data_diff_rows(&left_data_diff, &left_reloc_diffs, left_symbol.address);
let right_rows =
build_data_diff_rows(&right_data_diff, &right_reloc_diffs, right_symbol.address);
Ok((
SymbolDiff {
target_symbol: Some(right_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
data_diff: left_data_diff,
data_reloc_diff: left_reloc_diffs,
data_rows: left_rows,
..Default::default()
},
SymbolDiff {
target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
data_diff: right_data_diff,
data_reloc_diff: right_reloc_diffs,
data_rows: right_rows,
..Default::default()
},
))
@ -593,3 +594,68 @@ fn symbols_matching_section(
&& !s.flags.contains(SymbolFlag::Ignored)
})
}
pub const BYTES_PER_ROW: usize = 16;
fn build_data_diff_row(
data_diffs: &[DataDiff],
reloc_diffs: &[DataRelocationDiff],
symbol_address: u64,
row_index: usize,
) -> DataDiffRow {
let row_start = row_index * BYTES_PER_ROW;
let row_end = row_start + BYTES_PER_ROW;
let mut row_diff = DataDiffRow {
address: symbol_address + row_start as u64,
segments: Vec::new(),
relocations: Vec::new(),
};
// Collect all segments that overlap with this row
let mut current_offset = 0;
for diff in data_diffs {
let diff_end = current_offset + diff.size;
if current_offset < row_end && diff_end > row_start {
let start_in_diff = row_start.saturating_sub(current_offset);
let end_in_diff = row_end.min(diff_end) - current_offset;
if start_in_diff < end_in_diff {
let data_slice = if diff.data.is_empty() {
Vec::new()
} else {
diff.data[start_in_diff..end_in_diff.min(diff.data.len())].to_vec()
};
row_diff.segments.push(DataDiff {
data: data_slice,
kind: diff.kind,
size: end_in_diff - start_in_diff,
});
}
}
current_offset = diff_end;
if current_offset >= row_start + BYTES_PER_ROW {
break;
}
}
// Collect all relocations that overlap with this row
let row_end_absolute = row_diff.address + BYTES_PER_ROW as u64;
row_diff.relocations = reloc_diffs
.iter()
.filter(|rd| rd.range.start < row_end_absolute && rd.range.end > row_diff.address)
.cloned()
.collect();
row_diff
}
fn build_data_diff_rows(
segments: &[DataDiff],
relocations: &[DataRelocationDiff],
symbol_address: u64,
) -> Vec<DataDiffRow> {
let total_len = segments.iter().map(|s| s.size as u64).sum::<u64>();
let num_rows = total_len.div_ceil(BYTES_PER_ROW as u64) as usize;
(0..num_rows)
.map(|row_index| build_data_diff_row(segments, relocations, symbol_address, row_index))
.collect()
}

View File

@ -12,7 +12,10 @@ use itertools::Itertools;
use regex::Regex;
use crate::{
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
diff::{
DataDiffKind, DataDiffRow, DiffObjConfig, InstructionDiffKind, InstructionDiffRow,
ObjectDiff, SymbolDiff, data::resolve_relocation,
},
obj::{
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
@ -494,6 +497,57 @@ pub fn relocation_context(
out
}
pub fn data_row_hover(obj: &Object, diff_row: &DataDiffRow) -> Vec<HoverItem> {
let mut out = Vec::new();
let mut prev_reloc = None;
let mut first = true;
for reloc_diff in diff_row.relocations.iter() {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
if first {
first = false;
} else {
out.push(HoverItem::Separator);
}
let reloc = resolve_relocation(&obj.symbols, reloc);
let color = match reloc_diff.kind {
DataDiffKind::None => HoverItemColor::Normal,
DataDiffKind::Replace => HoverItemColor::Special,
DataDiffKind::Delete => HoverItemColor::Delete,
DataDiffKind::Insert => HoverItemColor::Insert,
};
out.append(&mut relocation_hover(obj, reloc, Some(color)));
}
out
}
pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem> {
let mut out = Vec::new();
let mut prev_reloc = None;
for reloc_diff in diff_row.relocations.iter() {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_context(obj, reloc, None));
}
out
}
pub fn relocation_hover(
obj: &Object,
reloc: ResolvedRelocation,
@ -677,6 +731,7 @@ pub struct SectionDisplay {
pub size: u64,
pub match_percent: Option<f32>,
pub symbols: Vec<SectionDisplaySymbol>,
pub kind: SectionKind,
}
pub fn display_sections(
@ -755,6 +810,7 @@ pub fn display_sections(
size: section.size,
match_percent: section_diff.match_percent,
symbols,
kind: section.kind,
});
} else {
// Don't sort, preserve order of absolute symbols
@ -764,6 +820,7 @@ pub fn display_sections(
size: 0,
match_percent: None,
symbols,
kind: SectionKind::Common,
});
}
}

View File

@ -45,8 +45,7 @@ pub struct SymbolDiff {
pub match_percent: Option<f32>,
pub diff_score: Option<(u64, u64)>,
pub instruction_rows: Vec<InstructionDiffRow>,
pub data_diff: Vec<DataDiff>,
pub data_reloc_diff: Vec<DataRelocationDiff>,
pub data_rows: Vec<DataDiffRow>,
}
#[derive(Debug, Clone, Default)]
@ -83,16 +82,15 @@ pub enum InstructionDiffKind {
#[derive(Debug, Clone, Default)]
pub struct DataDiff {
pub data: Vec<u8>,
pub size: usize,
pub kind: DataDiffKind,
pub len: usize,
pub symbol: String,
}
#[derive(Debug, Clone)]
pub struct DataRelocationDiff {
pub reloc: Relocation,
pub range: Range<u64>,
pub kind: DataDiffKind,
pub range: Range<usize>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
@ -104,6 +102,13 @@ pub enum DataDiffKind {
Insert,
}
#[derive(Debug, Clone, Default)]
pub struct DataDiffRow {
pub address: u64,
pub segments: Vec<DataDiff>,
pub relocations: Vec<DataRelocationDiff>,
}
/// Index of the argument diff for coloring.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Default)]

View File

@ -2645,8 +2645,7 @@ expression: "(target_symbol_diff, base_symbol_diff)"
arg_diff: [],
},
],
data_diff: [],
data_reloc_diff: [],
data_rows: [],
},
SymbolDiff {
target_symbol: Some(
@ -5290,7 +5289,6 @@ expression: "(target_symbol_diff, base_symbol_diff)"
arg_diff: [],
},
],
data_diff: [],
data_reloc_diff: [],
data_rows: [],
},
)

View File

@ -26,6 +26,7 @@ expression: sections_display
is_mapping_symbol: false,
},
],
kind: Common,
},
SectionDisplay {
id: ".ctors-0",
@ -40,6 +41,7 @@ expression: sections_display
is_mapping_symbol: false,
},
],
kind: Data,
},
SectionDisplay {
id: ".text-0",
@ -82,5 +84,6 @@ expression: sections_display
is_mapping_symbol: false,
},
],
kind: Code,
},
]

View File

@ -14,6 +14,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-1",
@ -26,6 +27,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-2",
@ -38,6 +40,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-3",
@ -50,6 +53,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-4",
@ -62,6 +66,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-5",
@ -74,6 +79,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-6",
@ -86,6 +92,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-7",
@ -98,6 +105,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-8",
@ -110,6 +118,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-9",
@ -122,6 +131,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-10",
@ -134,6 +144,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-11",
@ -146,6 +157,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-12",
@ -158,6 +170,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-13",
@ -170,6 +183,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-14",
@ -182,6 +196,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-15",
@ -194,6 +209,7 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
SectionDisplay {
id: ".text-16",
@ -206,5 +222,6 @@ expression: section_display
is_mapping_symbol: false,
},
],
kind: Code,
},
]

View File

@ -1,11 +1,11 @@
use std::{cmp::min, default::Default, mem::take};
use std::default::Default;
use egui::{Label, Sense, Widget, text::LayoutJob};
use objdiff_core::{
diff::{
DataDiff, DataDiffKind, DataRelocationDiff,
data::resolve_relocation,
display::{ContextItem, HoverItem, HoverItemColor, relocation_context, relocation_hover},
DataDiffKind, DataDiffRow,
data::BYTES_PER_ROW,
display::{data_row_context, data_row_hover},
},
obj::Object,
};
@ -13,84 +13,30 @@ use objdiff_core::{
use super::diff::{context_menu_items_ui, hover_items_ui};
use crate::views::{appearance::Appearance, write_text};
pub(crate) const BYTES_PER_ROW: usize = 16;
fn data_row_hover(obj: &Object, diffs: &[(DataDiff, Vec<DataRelocationDiff>)]) -> Vec<HoverItem> {
let mut out = Vec::new();
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
let mut prev_reloc = None;
let mut first = true;
for reloc_diff in reloc_diffs {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
if first {
first = false;
} else {
out.push(HoverItem::Separator);
}
let color = get_hover_item_color_for_diff_kind(reloc_diff.kind);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_hover(obj, reloc, Some(color)));
}
out
}
fn data_row_context(
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
) -> Vec<ContextItem> {
let mut out = Vec::new();
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
let mut prev_reloc = None;
for reloc_diff in reloc_diffs {
let reloc = &reloc_diff.reloc;
if prev_reloc == Some(reloc) {
// Avoid showing consecutive duplicate relocations.
// We do this because a single relocation can span across multiple diffs if the
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
continue;
}
prev_reloc = Some(reloc);
let reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_context(obj, reloc, None));
}
out
}
fn data_row_hover_ui(
ui: &mut egui::Ui,
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
diff_row: &DataDiffRow,
appearance: &Appearance,
) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
hover_items_ui(ui, data_row_hover(obj, diffs), appearance);
hover_items_ui(ui, data_row_hover(obj, diff_row), appearance);
});
}
fn data_row_context_menu(
ui: &mut egui::Ui,
obj: &Object,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
diff_row: &DataDiffRow,
column: usize,
appearance: &Appearance,
) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
context_menu_items_ui(ui, data_row_context(obj, diffs), column, appearance);
context_menu_items_ui(ui, data_row_context(obj, diff_row), column, appearance);
});
}
@ -103,27 +49,18 @@ fn get_color_for_diff_kind(diff_kind: DataDiffKind, appearance: &Appearance) ->
}
}
fn get_hover_item_color_for_diff_kind(diff_kind: DataDiffKind) -> HoverItemColor {
match diff_kind {
DataDiffKind::None => HoverItemColor::Normal,
DataDiffKind::Replace => HoverItemColor::Special,
DataDiffKind::Delete => HoverItemColor::Delete,
DataDiffKind::Insert => HoverItemColor::Insert,
}
}
pub(crate) fn data_row_ui(
ui: &mut egui::Ui,
obj: Option<&Object>,
base_address: usize,
row_address: usize,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
base_address: u64,
row_address: u64,
diff_row: &DataDiffRow,
appearance: &Appearance,
column: usize,
) {
if diffs.iter().any(|(dd, rds)| {
dd.kind != DataDiffKind::None || rds.iter().any(|rd| rd.kind != DataDiffKind::None)
}) {
if diff_row.segments.iter().any(|dd| dd.kind != DataDiffKind::None)
|| diff_row.relocations.iter().any(|rd| rd.kind != DataDiffKind::None)
{
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
}
let mut job = LayoutJob::default();
@ -137,20 +74,21 @@ pub(crate) fn data_row_ui(
let mut cur_addr = 0usize;
// The offset into the actual bytes of the section on this side, ignoring differences.
let mut cur_addr_actual = base_address + row_address;
for (diff, reloc_diffs) in diffs {
for diff in diff_row.segments.iter() {
let base_color = get_color_for_diff_kind(diff.kind, appearance);
if diff.data.is_empty() {
let mut str = " ".repeat(diff.len);
let mut str = " ".repeat(diff.size);
let n1 = cur_addr / 8;
let n2 = (diff.len + cur_addr) / 8;
let n2 = (diff.size + cur_addr) / 8;
str.push_str(" ".repeat(n2 - n1).as_str());
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
cur_addr += diff.len;
cur_addr += diff.size;
} else {
for byte in &diff.data {
let mut byte_text = format!("{byte:02x} ");
let mut byte_color = base_color;
if let Some(reloc_diff) = reloc_diffs
if let Some(reloc_diff) = diff_row
.relocations
.iter()
.find(|reloc_diff| reloc_diff.range.contains(&cur_addr_actual))
{
@ -179,11 +117,11 @@ pub(crate) fn data_row_ui(
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
}
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
for (diff, _) in diffs {
for diff in diff_row.segments.iter() {
let base_color = get_color_for_diff_kind(diff.kind, appearance);
if diff.data.is_empty() {
write_text(
" ".repeat(diff.len).as_str(),
" ".repeat(diff.size).as_str(),
base_color,
&mut job,
appearance.code_font.clone(),
@ -204,70 +142,7 @@ pub(crate) fn data_row_ui(
let response = Label::new(job).sense(Sense::click()).ui(ui);
if let Some(obj) = obj {
response.context_menu(|ui| data_row_context_menu(ui, obj, diffs, column, appearance));
response.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diffs, appearance));
response.context_menu(|ui| data_row_context_menu(ui, obj, diff_row, column, appearance));
response.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diff_row, appearance));
}
}
pub(crate) fn split_diffs(
diffs: &[DataDiff],
reloc_diffs: &[DataRelocationDiff],
symbol_offset_in_section: usize,
) -> Vec<Vec<(DataDiff, Vec<DataRelocationDiff>)>> {
let mut split_diffs = Vec::<Vec<(DataDiff, Vec<DataRelocationDiff>)>>::new();
let mut row_diffs = Vec::<(DataDiff, Vec<DataRelocationDiff>)>::new();
// The offset shown on the side of the GUI, shifted by insertions/deletions.
let mut cur_addr = 0usize;
// The offset into the actual bytes of the section on this side, ignoring differences.
let mut cur_addr_actual = symbol_offset_in_section;
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);
let data_diff = DataDiff {
data: if diff.data.is_empty() {
Vec::new()
} else {
diff.data[cur_len..cur_len + len].to_vec()
},
kind: diff.kind,
len,
symbol: String::new(), // TODO
};
let row_reloc_diffs: Vec<DataRelocationDiff> = if diff.data.is_empty() {
Vec::new()
} else {
let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len;
reloc_diffs
.iter()
.filter_map(|reloc_diff| {
if reloc_diff.range.start < diff_range.end
&& diff_range.start < reloc_diff.range.end
{
Some(reloc_diff.clone())
} else {
None
}
})
.collect()
};
let row_diff = (data_diff, row_reloc_diffs);
row_diffs.push(row_diff);
remaining_in_row -= len;
cur_len += len;
cur_addr += len;
if remaining_in_row == 0 {
split_diffs.push(take(&mut row_diffs));
}
}
cur_addr_actual += diff.data.len();
}
if !row_diffs.is_empty() {
split_diffs.push(take(&mut row_diffs));
}
split_diffs
}

View File

@ -3,6 +3,7 @@ use objdiff_core::{
build::BuildStatus,
diff::{
DiffObjConfig, ObjectDiff, SymbolDiff,
data::BYTES_PER_ROW,
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
},
obj::{Object, Symbol},
@ -14,7 +15,7 @@ use crate::{
views::{
appearance::Appearance,
column_layout::{render_header, render_strips, render_table},
data_diff::{BYTES_PER_ROW, data_row_ui, split_diffs},
data_diff::data_row_ui,
extab_diff::extab_ui,
function_diff::{FunctionDiffContext, asm_col_ui},
symbol_diff::{
@ -470,28 +471,14 @@ pub fn diff_view_ui(
{
// Joint diff view
hotkeys::check_scroll_hotkeys(ui, true);
let left_total_bytes =
left_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
let right_total_bytes =
right_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if left_total_bytes != right_total_bytes {
ui.label("Data size mismatch");
let total_rows = left_symbol_diff.data_rows.len();
if total_rows != right_symbol_diff.data_rows.len() {
ui.label("Row count mismatch");
return;
}
if left_total_bytes == 0 {
if total_rows == 0 {
return;
}
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
let left_diffs = split_diffs(
&left_symbol_diff.data_diff,
&left_symbol_diff.data_reloc_diff,
left_symbol.address as usize,
);
let right_diffs = split_diffs(
&right_symbol_diff.data_diff,
&right_symbol_diff.data_reloc_diff,
right_symbol.address as usize,
);
render_table(
ui,
available_width,
@ -500,15 +487,15 @@ pub fn diff_view_ui(
total_rows,
|row, column| {
let i = row.index();
let row_offset = i * BYTES_PER_ROW;
let row_offset = i as u64 * BYTES_PER_ROW as u64;
row.col(|ui| {
if column == 0 {
data_row_ui(
ui,
Some(left_obj),
left_symbol.address as usize,
left_symbol.address,
row_offset,
&left_diffs[i],
&left_symbol_diff.data_rows[i],
appearance,
column,
);
@ -516,9 +503,9 @@ pub fn diff_view_ui(
data_row_ui(
ui,
Some(right_obj),
right_symbol.address as usize,
right_symbol.address,
row_offset,
&right_diffs[i],
&right_symbol_diff.data_rows[i],
appearance,
column,
);
@ -618,17 +605,10 @@ fn diff_col_ui(
extab_ui(ui, ctx, appearance, column);
} else if state.current_view == View::DataDiff {
hotkeys::check_scroll_hotkeys(ui, false);
let total_bytes =
symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if total_bytes == 0 {
let total_rows = symbol_diff.data_rows.len();
if total_rows == 0 {
return ret;
}
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
let diffs = split_diffs(
&symbol_diff.data_diff,
&symbol_diff.data_reloc_diff,
symbol.address as usize,
);
render_table(
ui,
available_width / 2.0,
@ -637,14 +617,14 @@ fn diff_col_ui(
total_rows,
|row, _column| {
let i = row.index();
let row_offset = i * BYTES_PER_ROW;
let row_offset = i as u64 * BYTES_PER_ROW as u64;
row.col(|ui| {
data_row_ui(
ui,
Some(obj),
symbol.address as usize,
symbol.address,
row_offset,
&diffs[i],
&symbol_diff.data_rows[i],
appearance,
column,
);

View File

@ -1,12 +1,12 @@
{
"name": "objdiff-wasm",
"version": "3.1.1",
"version": "3.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "objdiff-wasm",
"version": "3.1.1",
"version": "3.2.0",
"license": "MIT OR Apache-2.0",
"devDependencies": {
"@biomejs/biome": "^1.9.3",

View File

@ -1,6 +1,6 @@
{
"name": "objdiff-wasm",
"version": "3.1.1",
"version": "3.2.0",
"description": "A local diffing tool for decompilation projects.",
"author": {
"name": "Luke Street",

View File

@ -26,13 +26,14 @@ use exports::objdiff::core::{
diff::{
DiffConfigBorrow, DiffResult, DiffSide, Guest as GuestDiff, GuestDiffConfig, GuestObject,
GuestObjectDiff, MappingConfig, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
SymbolFlags, SymbolInfo, SymbolKind, SymbolRef,
SectionKind, SymbolFlags, SymbolInfo, SymbolKind, SymbolRef,
},
display::{
ContextItem, ContextItemCopy, ContextItemNavigate, DiffText, DiffTextColor, DiffTextOpcode,
DiffTextSegment, DiffTextSymbol, DisplayConfig, Guest as GuestDisplay, HoverItem,
HoverItemColor, HoverItemText, InstructionDiffKind, InstructionDiffRow, SectionDisplay,
SymbolDisplay, SymbolFilter, SymbolNavigationKind,
ContextItem, ContextItemCopy, ContextItemNavigate, DataDiff, DataDiffKind, DataDiffRow,
DataRelocationDiff, DiffText, DiffTextColor, DiffTextOpcode, DiffTextSegment,
DiffTextSymbol, DisplayConfig, Guest as GuestDisplay, HoverItem, HoverItemColor,
HoverItemText, InstructionDiffKind, InstructionDiffRow, SectionDisplay, SymbolDisplay,
SymbolFilter, SymbolNavigationKind,
},
};
@ -138,6 +139,7 @@ impl GuestDisplay for Component {
size: d.size,
match_percent: d.match_percent,
symbols: d.symbols.into_iter().map(to_symbol_ref).collect(),
kind: d.kind.into(),
})
.collect()
}
@ -162,6 +164,7 @@ impl GuestDisplay for Component {
} else {
obj_diff.symbols.get(symbol_display.symbol)
};
let section = symbol.section.and_then(|s| obj.sections.get(s));
SymbolDisplay {
info: SymbolInfo {
id: to_symbol_ref(symbol_display),
@ -171,9 +174,8 @@ impl GuestDisplay for Component {
size: symbol.size,
kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
section_name: section.map(|sec| sec.name.clone()),
section_kind: section.map_or(SectionKind::Unknown, |sec| sec.kind.into()),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
@ -181,7 +183,8 @@ impl GuestDisplay for Component {
target_symbol: symbol_diff.and_then(|sd| sd.target_symbol.map(|s| s as u32)),
match_percent: symbol_diff.and_then(|sd| sd.match_percent),
diff_score: symbol_diff.and_then(|sd| sd.diff_score),
row_count: symbol_diff.map_or(0, |sd| sd.instruction_rows.len() as u32),
row_count: symbol_diff
.map_or(0, |sd| sd.instruction_rows.len().max(sd.data_rows.len()) as u32),
}
}
@ -335,6 +338,120 @@ impl GuestDisplay for Component {
.map(HoverItem::from)
.collect()
}
fn display_data_row(
diff: ObjectDiffBorrow,
symbol_ref: SymbolRef,
row_index: u32,
) -> DataDiffRow {
let obj_diff = diff.get::<ResourceObjectDiff>();
let obj_diff = &obj_diff.1;
let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff
.mapping_symbols
.iter()
.find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff)
} else {
obj_diff.symbols.get(symbol_display.symbol)
};
let Some(symbol_diff) = symbol_diff else {
return DataDiffRow::default();
};
symbol_diff.data_rows.get(row_index as usize).map(DataDiffRow::from).unwrap_or_default()
}
fn data_hover(diff: ObjectDiffBorrow, symbol_ref: SymbolRef, row_index: u32) -> Vec<HoverItem> {
let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = &obj_diff.0;
let obj_diff = &obj_diff.1;
let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff
.mapping_symbols
.iter()
.find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff)
} else {
obj_diff.symbols.get(symbol_display.symbol)
};
let Some(symbol_diff) = symbol_diff else {
return vec![];
};
let Some(diff_row) = symbol_diff.data_rows.get(row_index as usize) else {
return vec![];
};
diff::display::data_row_hover(obj, diff_row).into_iter().map(HoverItem::from).collect()
}
fn data_context(
diff: ObjectDiffBorrow,
symbol_ref: SymbolRef,
row_index: u32,
) -> Vec<ContextItem> {
let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = &obj_diff.0;
let obj_diff = &obj_diff.1;
let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff
.mapping_symbols
.iter()
.find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff)
} else {
obj_diff.symbols.get(symbol_display.symbol)
};
let Some(symbol_diff) = symbol_diff else {
return vec![];
};
let Some(diff_row) = symbol_diff.data_rows.get(row_index as usize) else {
return vec![];
};
diff::display::data_row_context(obj, diff_row).into_iter().map(ContextItem::from).collect()
}
}
impl From<diff::DataDiffKind> for DataDiffKind {
fn from(kind: diff::DataDiffKind) -> Self {
match kind {
diff::DataDiffKind::None => DataDiffKind::None,
diff::DataDiffKind::Replace => DataDiffKind::Replace,
diff::DataDiffKind::Delete => DataDiffKind::Delete,
diff::DataDiffKind::Insert => DataDiffKind::Insert,
}
}
}
impl Default for DataDiffRow {
fn default() -> Self { Self { address: 0, segments: Vec::new(), relocations: Vec::new() } }
}
impl From<&diff::DataDiffRow> for DataDiffRow {
fn from(row: &diff::DataDiffRow) -> Self {
Self {
address: row.address,
segments: row.segments.iter().map(DataDiff::from).collect(),
relocations: row.relocations.iter().map(DataRelocationDiff::from).collect(),
}
}
}
impl From<&diff::DataDiff> for DataDiff {
fn from(diff: &diff::DataDiff) -> Self {
Self { data: diff.data.clone(), size: diff.size as u32, kind: diff.kind.into() }
}
}
impl From<&diff::DataRelocationDiff> for DataRelocationDiff {
fn from(diff: &diff::DataRelocationDiff) -> Self {
Self {
address: diff.reloc.address,
size: (diff.range.end - diff.range.start) as u32,
kind: diff.kind.into(),
}
}
}
impl From<obj::SymbolKind> for SymbolKind {
@ -523,6 +640,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
}
})?;
let symbol = obj.symbols.get(symbol_idx)?;
let section = symbol.section.and_then(|s| obj.sections.get(s));
Some(SymbolInfo {
id: symbol_idx as SymbolRef,
name: symbol.name.clone(),
@ -531,9 +649,8 @@ impl GuestObjectDiff for ResourceObjectDiff {
size: symbol.size,
kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
section_name: section.map(|sec| sec.name.clone()),
section_kind: section.map_or(SectionKind::Unknown, |sec| sec.kind.into()),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
@ -544,6 +661,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
let obj = self.0.as_ref();
let symbol_display = from_symbol_ref(symbol_ref);
let symbol = obj.symbols.get(symbol_display.symbol)?;
let section = symbol.section.and_then(|s| obj.sections.get(s));
Some(SymbolInfo {
id: to_symbol_ref(symbol_display),
name: symbol.name.clone(),
@ -552,9 +670,8 @@ impl GuestObjectDiff for ResourceObjectDiff {
size: symbol.size,
kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
section_name: section.map(|sec| sec.name.clone()),
section_kind: section.map_or(SectionKind::Unknown, |sec| sec.kind.into()),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
@ -639,6 +756,7 @@ impl Default for SymbolInfo {
kind: Default::default(),
section: Default::default(),
section_name: Default::default(),
section_kind: Default::default(),
flags: Default::default(),
align: Default::default(),
virtual_address: Default::default(),
@ -684,4 +802,20 @@ fn to_symbol_ref(display_symbol: diff::display::SectionDisplaySymbol) -> SymbolR
}
}
impl Default for SectionKind {
fn default() -> Self { Self::Unknown }
}
impl From<obj::SectionKind> for SectionKind {
fn from(kind: obj::SectionKind) -> Self {
match kind {
obj::SectionKind::Unknown => SectionKind::Unknown,
obj::SectionKind::Code => SectionKind::Code,
obj::SectionKind::Data => SectionKind::Data,
obj::SectionKind::Bss => SectionKind::Bss,
obj::SectionKind::Common => SectionKind::Common,
}
}
}
export!(Component);

View File

@ -54,6 +54,7 @@ interface diff {
kind: symbol-kind,
section: option<u32>,
section-name: option<string>,
section-kind: section-kind,
%flags: symbol-flags,
align: option<u32>,
virtual-address: option<u64>,
@ -86,6 +87,14 @@ interface diff {
target,
base,
}
enum section-kind {
unknown,
code,
data,
bss,
common,
}
}
interface display {
@ -94,7 +103,8 @@ interface display {
object-diff,
diff-config,
symbol-info,
symbol-ref
symbol-ref,
section-kind
};
record display-config {
@ -114,6 +124,7 @@ interface display {
size: u64,
match-percent: option<f32>,
symbols: list<symbol-ref>,
kind: section-kind,
}
record symbol-display {
@ -238,6 +249,31 @@ interface display {
delete,
}
enum data-diff-kind {
none,
replace,
delete,
insert,
}
record data-diff {
data: list<u8>,
size: u32,
kind: data-diff-kind,
}
record data-relocation-diff {
address: u64,
size: u32,
kind: data-diff-kind,
}
record data-diff-row {
address: u64,
segments: list<data-diff>,
relocations: list<data-relocation-diff>,
}
display-sections: func(
diff: borrow<object-diff>,
filter: symbol-filter,
@ -279,6 +315,24 @@ interface display {
row-index: u32,
config: borrow<diff-config>,
) -> list<hover-item>;
display-data-row: func(
diff: borrow<object-diff>,
symbol: symbol-ref,
row-index: u32,
) -> data-diff-row;
data-context: func(
diff: borrow<object-diff>,
symbol: symbol-ref,
row-index: u32,
) -> list<context-item>;
data-hover: func(
diff: borrow<object-diff>,
symbol: symbol-ref,
row-index: u32,
) -> list<hover-item>;
}
world api {