From c86f76a9d1c6cdeee2dfe13e32e2d68d5092cd88 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 7 Sep 2025 18:52:47 -0600 Subject: [PATCH 1/2] Refactor data diffing & expose WASM API --- Cargo.lock | 8 +- Cargo.toml | 2 +- objdiff-core/src/diff/data.rs | 138 +++++++++++++++++------ objdiff-core/src/diff/display.rs | 59 +++++++++- objdiff-core/src/diff/mod.rs | 15 ++- objdiff-gui/src/views/data_diff.rs | 173 ++++------------------------- objdiff-gui/src/views/diff.rs | 52 +++------ objdiff-wasm/package-lock.json | 4 +- objdiff-wasm/package.json | 2 +- objdiff-wasm/src/api.rs | 164 ++++++++++++++++++++++++--- objdiff-wasm/wit/objdiff.wit | 56 +++++++++- 11 files changed, 422 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 549d9b8..306b957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 6e73266..93a553a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ default-members = [ resolver = "3" [workspace.package] -version = "3.1.1" +version = "3.2.0" authors = ["Luke Street "] edition = "2024" license = "MIT OR Apache-2.0" diff --git a/objdiff-core/src/diff/data.rs b/objdiff-core/src/diff/data.rs index 3ed4704..222d686 100644 --- a/objdiff-core/src/diff/data.rs +++ b/objdiff-core/src/diff/data.rs @@ -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, 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, 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, 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 { let section = &obj.sections[section_idx]; - let len = section.data.len(); - let data = §ion.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 Result Result 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 { + let total_len = segments.iter().map(|s| s.size as u64).sum::(); + 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() +} diff --git a/objdiff-core/src/diff/display.rs b/objdiff-core/src/diff/display.rs index ac1f6d2..24c1dd3 100644 --- a/objdiff-core/src/diff/display.rs +++ b/objdiff-core/src/diff/display.rs @@ -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 { + 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 { + 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, pub symbols: Vec, + 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, }); } } diff --git a/objdiff-core/src/diff/mod.rs b/objdiff-core/src/diff/mod.rs index b5e16d2..26cc7aa 100644 --- a/objdiff-core/src/diff/mod.rs +++ b/objdiff-core/src/diff/mod.rs @@ -45,8 +45,7 @@ pub struct SymbolDiff { pub match_percent: Option, pub diff_score: Option<(u64, u64)>, pub instruction_rows: Vec, - pub data_diff: Vec, - pub data_reloc_diff: Vec, + pub data_rows: Vec, } #[derive(Debug, Clone, Default)] @@ -83,16 +82,15 @@ pub enum InstructionDiffKind { #[derive(Debug, Clone, Default)] pub struct DataDiff { pub data: Vec, + pub size: usize, pub kind: DataDiffKind, - pub len: usize, - pub symbol: String, } #[derive(Debug, Clone)] pub struct DataRelocationDiff { pub reloc: Relocation, + pub range: Range, pub kind: DataDiffKind, - pub range: Range, } #[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, + pub relocations: Vec, +} + /// Index of the argument diff for coloring. #[repr(transparent)] #[derive(Debug, Copy, Clone, Default)] diff --git a/objdiff-gui/src/views/data_diff.rs b/objdiff-gui/src/views/data_diff.rs index 6a68636..dd2f197 100644 --- a/objdiff-gui/src/views/data_diff.rs +++ b/objdiff-gui/src/views/data_diff.rs @@ -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)]) -> Vec { - 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)], -) -> Vec { - 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)], + 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)], + 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)], + 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)); - } -} - -pub(crate) fn split_diffs( - diffs: &[DataDiff], - reloc_diffs: &[DataRelocationDiff], - symbol_offset_in_section: usize, -) -> Vec)>> { - let mut split_diffs = Vec::)>>::new(); - let mut row_diffs = Vec::<(DataDiff, Vec)>::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 = 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)); + 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)); } - split_diffs } diff --git a/objdiff-gui/src/views/diff.rs b/objdiff-gui/src/views/diff.rs index 695c95b..cb8804e 100644 --- a/objdiff-gui/src/views/diff.rs +++ b/objdiff-gui/src/views/diff.rs @@ -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, ); diff --git a/objdiff-wasm/package-lock.json b/objdiff-wasm/package-lock.json index eb8fade..6601141 100644 --- a/objdiff-wasm/package-lock.json +++ b/objdiff-wasm/package-lock.json @@ -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", diff --git a/objdiff-wasm/package.json b/objdiff-wasm/package.json index 54163c6..ad1d6a5 100644 --- a/objdiff-wasm/package.json +++ b/objdiff-wasm/package.json @@ -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", diff --git a/objdiff-wasm/src/api.rs b/objdiff-wasm/src/api.rs index a3f515c..a4f7560 100644 --- a/objdiff-wasm/src/api.rs +++ b/objdiff-wasm/src/api.rs @@ -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::(); + 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 { + let obj_diff = diff.get::(); + 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 { + let obj_diff = diff.get::(); + 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 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 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 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); diff --git a/objdiff-wasm/wit/objdiff.wit b/objdiff-wasm/wit/objdiff.wit index 3ec4b51..bdd5c97 100644 --- a/objdiff-wasm/wit/objdiff.wit +++ b/objdiff-wasm/wit/objdiff.wit @@ -54,6 +54,7 @@ interface diff { kind: symbol-kind, section: option, section-name: option, + section-kind: section-kind, %flags: symbol-flags, align: option, virtual-address: option, @@ -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, symbols: list, + 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, + 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, + relocations: list, + } + display-sections: func( diff: borrow, filter: symbol-filter, @@ -279,6 +315,24 @@ interface display { row-index: u32, config: borrow, ) -> list; + + display-data-row: func( + diff: borrow, + symbol: symbol-ref, + row-index: u32, + ) -> data-diff-row; + + data-context: func( + diff: borrow, + symbol: symbol-ref, + row-index: u32, + ) -> list; + + data-hover: func( + diff: borrow, + symbol: symbol-ref, + row-index: u32, + ) -> list; } world api { From 438a297968e0168a4d0b83e16edd2aa70ec9ba14 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 7 Sep 2025 18:54:51 -0600 Subject: [PATCH 2/2] Update test snapshots --- .../tests/snapshots/arch_ppc__diff_ppc-2.snap | 6 ++---- .../tests/snapshots/arch_ppc__diff_ppc.snap | 3 +++ .../arch_x86__display_section_ordering.snap | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap b/objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap index a86efe3..d9147d1 100644 --- a/objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap +++ b/objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap @@ -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: [], }, ) diff --git a/objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap b/objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap index 1fb15ee..f91c7ee 100644 --- a/objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap +++ b/objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap @@ -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, }, ] diff --git a/objdiff-core/tests/snapshots/arch_x86__display_section_ordering.snap b/objdiff-core/tests/snapshots/arch_x86__display_section_ordering.snap index 39d8a5f..008863e 100644 --- a/objdiff-core/tests/snapshots/arch_x86__display_section_ordering.snap +++ b/objdiff-core/tests/snapshots/arch_x86__display_section_ordering.snap @@ -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, }, ]