Implement diffing individual data symbols (#244)

* Implement diffing individual data symbols

* Remove unused code for diffing sections

* Data diff view: Make rows show offset within the symbol, not within the section

* Remove SelectedSymbol enum as it only has a single variant now

* Create fake data section symbols to allow diffing entire sections again

* Fix text sections not having their size zeroed out

* Update test snapshots

* Clean up code for inferring section symbol size

* Fix bug where PPC pool references weren't ignoring section symbols

* Update comment

* Always add unique section symbols for data sections

* Update test snapshots

* Remove unnecessary clone in format! call

* Auto-start mapping for unpaired data symbols
This commit is contained in:
LagoLunatic 2025-09-02 21:37:17 -04:00 committed by GitHub
parent 6fb4bb8855
commit 23009bf9a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 687 additions and 221 deletions

View File

@ -9,7 +9,7 @@ use objdiff_core::{
},
config::path::platform_path,
diff,
obj::{self, SectionKind, SymbolFlag},
obj::{self, SectionKind, SymbolFlag, SymbolKind},
};
use prost::Message;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
@ -247,6 +247,7 @@ fn report_object(
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored)
|| symbol.kind == SymbolKind::Section
{
continue;
}

View File

@ -21,6 +21,7 @@ use crate::{
obj::{
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
SymbolKind,
},
};
@ -832,6 +833,7 @@ fn make_fake_pool_reloc(
&& s.size > 0
&& !s.flags.contains(SymbolFlag::Hidden)
&& !s.flags.contains(SymbolFlag::Ignored)
&& s.kind != SymbolKind::Section
&& (s.address..s.address + s.size).contains(&target_address)
})?;
addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;

View File

@ -41,7 +41,13 @@ pub fn no_diff_code(
instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
}
resolve_branches(&ops, &mut instruction_rows);
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
Ok(SymbolDiff {
target_symbol: None,
match_percent: None,
diff_score: None,
instruction_rows,
..Default::default()
})
}
const PENALTY_IMM_DIFF: u64 = 1;
@ -147,12 +153,14 @@ pub fn diff_code(
match_percent: Some(match_percent),
diff_score: Some((diff_score, max_score)),
instruction_rows: left_rows,
..Default::default()
},
SymbolDiff {
target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent),
diff_score: Some((diff_score, max_score)),
instruction_rows: right_rows,
..Default::default()
},
))
}

View File

@ -24,13 +24,13 @@ pub fn diff_bss_symbol(
target_symbol: Some(right_symbol_ref),
match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
..Default::default()
},
SymbolDiff {
target_symbol: Some(left_symbol_ref),
match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
..Default::default()
},
))
}
@ -84,7 +84,83 @@ pub fn resolve_relocation<'obj>(
ResolvedRelocation { relocation: reloc, symbol }
}
/// Compares relocations contained with a certain data range.
/// Compares the bytes within a certain data range.
fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>, Vec<DataDiff>) {
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
let mut left_data_diff = Vec::<DataDiff>::new();
let mut right_data_diff = Vec::<DataDiff>::new();
for op in ops {
let (tag, left_range, right_range) = op.as_tag_tuple();
let left_len = left_range.len();
let right_len = right_range.len();
let mut len = left_len.max(right_len);
let kind = match tag {
similar::DiffTag::Equal => DataDiffKind::None,
similar::DiffTag::Delete => DataDiffKind::Delete,
similar::DiffTag::Insert => DataDiffKind::Insert,
similar::DiffTag::Replace => {
// Ensure replacements are equal length
len = left_len.min(right_len);
DataDiffKind::Replace
}
};
let left_data = &left_data[left_range];
let right_data = &right_data[right_range];
left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(),
kind,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(),
kind,
len,
..Default::default()
});
if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) {
Ordering::Less => {
let len = right_len - left_len;
left_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Insert,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(),
kind: DataDiffKind::Insert,
len,
..Default::default()
});
}
Ordering::Greater => {
let len = left_len - right_len;
left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(),
kind: DataDiffKind::Delete,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Delete,
len,
..Default::default()
});
}
Ordering::Equal => {}
}
}
}
(bytes_match_ratio, left_data_diff, right_data_diff)
}
/// Compares relocations contained within a certain data range.
fn diff_data_relocs_for_range<'left, 'right>(
left_obj: &'left Object,
right_obj: &'right Object,
@ -186,76 +262,10 @@ pub fn diff_data_section(
.min(right_section.size);
let left_data = &left_section.data[..left_max as usize];
let right_data = &right_section.data[..right_max as usize];
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
let mut left_data_diff = Vec::<DataDiff>::new();
let mut right_data_diff = Vec::<DataDiff>::new();
for op in ops {
let (tag, left_range, right_range) = op.as_tag_tuple();
let left_len = left_range.len();
let right_len = right_range.len();
let mut len = left_len.max(right_len);
let kind = match tag {
similar::DiffTag::Equal => DataDiffKind::None,
similar::DiffTag::Delete => DataDiffKind::Delete,
similar::DiffTag::Insert => DataDiffKind::Insert,
similar::DiffTag::Replace => {
// Ensure replacements are equal length
len = left_len.min(right_len);
DataDiffKind::Replace
}
};
let left_data = &left_section.data[left_range];
let right_data = &right_section.data[right_range];
left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(),
kind,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(),
kind,
len,
..Default::default()
});
if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) {
Ordering::Less => {
let len = right_len - left_len;
left_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Insert,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(),
kind: DataDiffKind::Insert,
len,
..Default::default()
});
}
Ordering::Greater => {
let len = left_len - right_len;
left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(),
kind: DataDiffKind::Delete,
len,
..Default::default()
});
right_data_diff.push(DataDiff {
data: vec![],
kind: DataDiffKind::Delete,
len,
..Default::default()
});
}
Ordering::Equal => {}
}
}
}
let (bytes_match_ratio, left_data_diff, right_data_diff) =
diff_data_range(left_data, right_data);
let match_percent = bytes_match_ratio * 100.0;
let mut left_reloc_diffs = Vec::new();
let mut right_reloc_diffs = Vec::new();
@ -314,6 +324,55 @@ pub fn diff_data_section(
Ok((left_section_diff, right_section_diff))
}
pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDiff> {
let symbol = &obj.symbols[symbol_index];
let section_idx = symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let section = &obj.sections[section_idx];
let start = symbol
.address
.checked_sub(section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
let end = start + symbol.size;
if end > section.size {
return Err(anyhow!(
"Symbol {} size out of section bounds ({} > {})",
symbol.name,
end,
section.size
));
}
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 mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() {
if !range.contains(&(reloc.address as usize)) {
continue;
}
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len;
reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(),
kind: DataDiffKind::None,
range,
});
}
Ok(SymbolDiff {
target_symbol: None,
match_percent: None,
diff_score: None,
data_diff,
data_reloc_diff: reloc_diffs,
..Default::default()
})
}
pub fn diff_data_symbol(
left_obj: &Object,
right_obj: &Object,
@ -362,6 +421,9 @@ pub fn diff_data_symbol(
let left_data = &left_section.data[left_range.clone()];
let right_data = &right_section.data[right_range.clone()];
let (bytes_match_ratio, left_data_diff, right_data_diff) =
diff_data_range(left_data, right_data);
let reloc_diffs = diff_data_relocs_for_range(
left_obj,
right_obj,
@ -371,10 +433,9 @@ pub fn diff_data_symbol(
right_range,
);
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
let mut match_ratio = bytes_match_ratio;
let mut left_reloc_diffs = Vec::new();
let mut right_reloc_diffs = Vec::new();
if !reloc_diffs.is_empty() {
let mut total_reloc_bytes = 0;
let mut matching_reloc_bytes = 0;
@ -390,6 +451,27 @@ pub fn diff_data_symbol(
if diff_kind == DataDiffKind::None {
matching_reloc_bytes += reloc_diff_len;
}
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;
left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(),
kind: diff_kind,
range,
});
}
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;
right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(),
kind: diff_kind,
range,
});
}
}
if total_reloc_bytes > 0 {
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
@ -411,13 +493,17 @@ pub fn diff_data_symbol(
target_symbol: Some(right_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
data_diff: left_data_diff,
data_reloc_diff: left_reloc_diffs,
..Default::default()
},
SymbolDiff {
target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
data_diff: right_data_diff,
data_reloc_diff: right_reloc_diffs,
..Default::default()
},
))
}

View File

@ -13,7 +13,8 @@ use crate::{
code::{diff_code, no_diff_code},
data::{
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
diff_generic_section, no_diff_bss_section, no_diff_data_section, symbol_name_matches,
diff_generic_section, no_diff_bss_section, no_diff_data_section, no_diff_data_symbol,
symbol_name_matches,
},
},
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
@ -44,6 +45,8 @@ 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>,
}
#[derive(Debug, Clone, Default)]
@ -163,7 +166,7 @@ impl ObjectDiff {
target_symbol: None,
match_percent: None,
diff_score: None,
instruction_rows: vec![],
..Default::default()
});
}
for _ in obj.sections.iter() {
@ -262,7 +265,11 @@ pub fn diff_objs(
left_out.symbols[left_symbol_ref] =
no_diff_code(left_obj, left_symbol_ref, diff_config)?;
}
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
SectionKind::Data => {
left_out.symbols[left_symbol_ref] =
no_diff_data_symbol(left_obj, left_symbol_ref)?;
}
SectionKind::Bss | SectionKind::Common => {
// Nothing needs to be done
}
SectionKind::Unknown => unreachable!(),
@ -275,7 +282,11 @@ pub fn diff_objs(
right_out.symbols[right_symbol_ref] =
no_diff_code(right_obj, right_symbol_ref, diff_config)?;
}
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
SectionKind::Data => {
right_out.symbols[right_symbol_ref] =
no_diff_data_symbol(right_obj, right_symbol_ref)?;
}
SectionKind::Bss | SectionKind::Common => {
// Nothing needs to be done
}
SectionKind::Unknown => unreachable!(),

View File

@ -15,7 +15,7 @@ use crate::{
diff::{DiffObjConfig, DiffSide},
obj::{
FlowAnalysisResult, Object, Relocation, RelocationFlags, Section, SectionData, SectionFlag,
SectionKind, Symbol, SymbolFlag, SymbolKind,
SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
split_meta::{SPLITMETA_SECTION, SplitMeta},
},
util::{align_data_slice_to, align_u64_to, read_u16, read_u32},
@ -118,7 +118,7 @@ fn map_symbols(
split_meta: Option<&SplitMeta>,
) -> Result<(Vec<Symbol>, Vec<usize>)> {
let symbol_count = obj_file.symbols().count();
let mut symbols = Vec::<Symbol>::with_capacity(symbol_count);
let mut symbols = Vec::<Symbol>::with_capacity(symbol_count + obj_file.sections().count());
let mut symbol_indices = Vec::<usize>::with_capacity(symbol_count + 1);
for obj_symbol in obj_file.symbols() {
if symbol_indices.len() <= obj_symbol.index().0 {
@ -135,6 +135,52 @@ fn map_symbols(
Ok((symbols, symbol_indices))
}
/// Add an extra fake symbol to the start of each data section in order to allow the user to diff
/// all of the data in the section at once by clicking on this fake symbol at the top of the list.
fn add_section_symbols(sections: &[Section], symbols: &mut Vec<Symbol>) {
for (section_idx, section) in sections.iter().enumerate() {
if section.kind != SectionKind::Data {
continue;
}
// Instead of naming the fake section symbol after `section.name` (e.g. ".data") we use
// `section.id` (e.g. ".data-0") so that it is unique when multiple sections with the same
// name exist and it also doesn't conflict with any real section symbols from the object.
let name = if section.flags.contains(SectionFlag::Combined) {
// For combined sections, `section.id` (e.g. ".data-combined") is inconsistent with
// uncombined section IDs, so we add the "-0" suffix to the name to enable proper
// pairing when one side had multiple sections combined and the other only had one
// section to begin with.
format!("[{}-0]", section.name)
} else {
format!("[{}]", section.id)
};
// `section.size` can include extra padding, so instead prefer using the address that the
// last symbol ends at when there are any symbols in the section.
let size = symbols
.iter()
.filter(|s| {
s.section == Some(section_idx) && s.kind == SymbolKind::Object && s.size > 0
})
.map(|s| s.address + s.size)
.max()
.unwrap_or(section.size);
symbols.push(Symbol {
name,
demangled_name: None,
address: 0,
size,
kind: SymbolKind::Section,
section: Some(section_idx),
flags: SymbolFlagSet::default() | SymbolFlag::Local,
align: None,
virtual_address: None,
});
}
}
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
/// usually emitted as branch targets and do not represent the start of a function or object.
fn is_local_label(symbol: &Symbol) -> bool {
@ -216,7 +262,11 @@ fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Secti
let section = &sections[section_idx];
let next_address =
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
let new_size = if section.kind == SectionKind::Code {
let new_size = if symbol.kind == SymbolKind::Section && section.kind == SectionKind::Data {
// Data sections already have always-visible section symbols created by objdiff to allow
// diffing them, so no need to unhide these.
0
} else if section.kind == SectionKind::Code {
arch.infer_function_size(symbol, section, next_address)?
} else {
next_address.saturating_sub(symbol.address)
@ -954,6 +1004,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig, diff_side: DiffSide) -> Result
if config.combine_data_sections || config.combine_text_sections {
combine_sections(&mut sections, &mut symbols, config)?;
}
add_section_symbols(&sections, &mut symbols);
arch.post_init(&sections, &symbols);
let mut obj = Object {
arch,

View File

@ -1507,6 +1507,19 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[.data-0]",
demangled_name: None,
address: 0,
size: 76,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -449,4 +449,17 @@ expression: obj.symbols
align: None,
virtual_address: None,
},
Symbol {
name: "[.data-0]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
]

View File

@ -10,10 +10,10 @@ expression: output
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSleep", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 0, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadEffect", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 0, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadSwd", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]

View File

@ -112,7 +112,7 @@ Object {
name: "[.sdata]",
demangled_name: None,
address: 0,
size: 64,
size: 0,
kind: Section,
section: Some(
8,
@ -673,6 +673,45 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[.data-0]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rodata-0]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
7,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.sdata-0]",
demangled_name: None,
address: 0,
size: 76,
kind: Section,
section: Some(
8,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

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

View File

@ -1,6 +1,5 @@
---
source: objdiff-core/tests/arch_ppc.rs
assertion_line: 70
expression: sections_display
---
[
@ -37,7 +36,7 @@ expression: sections_display
),
symbols: [
SectionDisplaySymbol {
symbol: 2,
symbol: 16,
is_mapping_symbol: false,
},
],

View File

@ -308,6 +308,32 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[extab-0]",
demangled_name: None,
address: 0,
size: 40,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[extabindex-0]",
demangled_name: None,
address: 0,
size: 36,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -43,7 +43,7 @@ Object {
name: "[.ctors]",
demangled_name: None,
address: 0,
size: 4,
size: 0,
kind: Section,
section: Some(
1,
@ -157,6 +157,19 @@ Object {
0,
),
},
Symbol {
name: "[.ctors-0]",
demangled_name: None,
address: 0,
size: 4,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -1101,6 +1101,45 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[.XBLD$W-0]",
demangled_name: None,
address: 0,
size: 16,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rdata-0]",
demangled_name: None,
address: 0,
size: 416,
kind: Section,
section: Some(
4,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-0]",
demangled_name: None,
address: 0,
size: 40,
kind: Section,
section: Some(
6,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -124,6 +124,19 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[.data-0]",
demangled_name: None,
address: 0,
size: 10,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -854,6 +854,201 @@ Object {
align: None,
virtual_address: None,
},
Symbol {
name: "[.xdata-0]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
7,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-0]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
8,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.xdata-1]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
9,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-1]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
10,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.xdata-2]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
11,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-2]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
12,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.xdata-3]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
13,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-3]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
14,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rdata-0]",
demangled_name: None,
address: 0,
size: 256,
kind: Section,
section: Some(
15,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.xdata-4]",
demangled_name: None,
address: 0,
size: 20,
kind: Section,
section: Some(
16,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdata-4]",
demangled_name: None,
address: 0,
size: 12,
kind: Section,
section: Some(
17,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rtc$IMZ-0]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
19,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rtc$TMZ-0]",
demangled_name: None,
address: 0,
size: 8,
kind: Section,
section: Some(
20,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rdata-1]",
demangled_name: None,
address: 0,
size: 4,
kind: Section,
section: Some(
21,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.rdata-2]",
demangled_name: None,
address: 0,
size: 4,
kind: Section,
section: Some(
22,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
],
sections: [
Section {

View File

@ -115,7 +115,8 @@ fn get_hover_item_color_for_diff_kind(diff_kind: DataDiffKind) -> HoverItemColor
pub(crate) fn data_row_ui(
ui: &mut egui::Ui,
obj: Option<&Object>,
address: usize,
base_address: usize,
row_address: usize,
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
appearance: &Appearance,
column: usize,
@ -127,7 +128,7 @@ pub(crate) fn data_row_ui(
}
let mut job = LayoutJob::default();
write_text(
format!("{address:08x}: ").as_str(),
format!("{row_address:08x}: ").as_str(),
appearance.text_color,
&mut job,
appearance.code_font.clone(),
@ -135,7 +136,7 @@ pub(crate) fn data_row_ui(
// 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 = address;
let mut cur_addr_actual = base_address + row_address;
for (diff, reloc_diffs) in diffs {
let base_color = get_color_for_diff_kind(diff.kind, appearance);
if diff.data.is_empty() {
@ -211,13 +212,14 @@ pub(crate) fn data_row_ui(
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 = 0usize;
let mut cur_addr_actual = symbol_offset_in_section;
for diff in diffs {
let mut cur_len = 0usize;
while cur_len < diff.len {

View File

@ -2,10 +2,10 @@ use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJ
use objdiff_core::{
build::BuildStatus,
diff::{
DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff,
DiffObjConfig, ObjectDiff, SymbolDiff,
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
},
obj::{Object, Section, Symbol},
obj::{Object, Symbol},
};
use time::format_description;
@ -25,17 +25,10 @@ use crate::{
},
};
#[derive(Clone, Copy)]
enum SelectedSymbol {
Symbol(usize),
Section(usize),
}
#[derive(Clone, Copy)]
struct DiffColumnContext<'a> {
status: &'a BuildStatus,
obj: Option<&'a (Object, ObjectDiff)>,
section: Option<(&'a Section, &'a SectionDiff, usize)>,
symbol: Option<(&'a Symbol, &'a SymbolDiff, usize)>,
}
@ -46,49 +39,28 @@ impl<'a> DiffColumnContext<'a> {
obj: Option<&'a (Object, ObjectDiff)>,
selected_symbol: Option<&SymbolRefByName>,
) -> Self {
let selected_symbol = match view {
let selected_symbol_idx = match view {
View::SymbolDiff => None,
View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) {
(Some(obj), Some(s)) => {
obj.0.symbol_by_name(&s.symbol_name).map(SelectedSymbol::Symbol)
}
_ => None,
},
View::DataDiff => match (obj, selected_symbol) {
(Some(obj), Some(SymbolRefByName { section_name: Some(section_name), .. })) => {
find_section(&obj.0, section_name).map(SelectedSymbol::Section)
}
View::FunctionDiff | View::DataDiff | View::ExtabDiff => match (obj, selected_symbol) {
(Some(obj), Some(s)) => obj.0.symbol_by_name(&s.symbol_name),
_ => None,
},
};
let (section, symbol) = match (obj, selected_symbol) {
(Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => {
let symbol = match (obj, selected_symbol_idx) {
(Some((obj, obj_diff)), Some(symbol_ref)) => {
let symbol = &obj.symbols[symbol_ref];
(
symbol.section.map(|section_idx| {
(&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)
}),
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref)),
)
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref))
}
(Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => (
Some((&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)),
None,
),
_ => (None, None),
_ => None,
};
Self { status, obj, section, symbol }
Self { status, obj, symbol }
}
#[inline]
pub fn has_symbol(&self) -> bool { self.section.is_some() || self.symbol.is_some() }
pub fn has_symbol(&self) -> bool { self.symbol.is_some() }
#[inline]
pub fn id(&self) -> Option<&str> {
self.symbol
.map(|(symbol, _, _)| symbol.name.as_str())
.or_else(|| self.section.map(|(section, _, _)| section.name.as_str()))
}
pub fn id(&self) -> Option<&str> { self.symbol.map(|(symbol, _, _)| symbol.name.as_str()) }
}
#[must_use]
@ -133,10 +105,7 @@ pub fn diff_view_ui(
{
navigation.right_symbol = Some(target_symbol_ref);
}
} else if navigation.left_symbol.is_some()
&& left_ctx.obj.is_some()
&& left_ctx.section.is_none()
{
} else if navigation.left_symbol.is_some() && left_ctx.obj.is_some() {
// Clear selection if symbol goes missing
navigation.left_symbol = None;
}
@ -147,10 +116,7 @@ pub fn diff_view_ui(
{
navigation.left_symbol = Some(target_symbol_ref);
}
} else if navigation.right_symbol.is_some()
&& right_ctx.obj.is_some()
&& right_ctx.section.is_none()
{
} else if navigation.right_symbol.is_some() && right_ctx.obj.is_some() {
// Clear selection if symbol goes missing
navigation.right_symbol = None;
}
@ -225,12 +191,6 @@ pub fn diff_view_ui(
{
ret = Some(action);
}
} else if let Some((section, _, _)) = left_ctx.section {
ui.label(
RichText::new(section.name.clone())
.font(appearance.code_font.clone())
.color(appearance.highlight_color),
);
} else if right_ctx.has_symbol() {
ui.label(
RichText::new("Choose target symbol")
@ -363,12 +323,6 @@ pub fn diff_view_ui(
{
ret = Some(action);
}
} else if let Some((section, _, _)) = right_ctx.section {
ui.label(
RichText::new(section.name.clone())
.font(appearance.code_font.clone())
.color(appearance.highlight_color),
);
} else if left_ctx.has_symbol() {
ui.label(
RichText::new("Choose base symbol")
@ -509,17 +463,17 @@ pub fn diff_view_ui(
View::DataDiff,
Some((left_obj, _left_diff)),
Some((right_obj, _right_diff)),
Some((_left_section, left_section_diff, _left_symbol_idx)),
Some((_right_section, right_section_diff, _right_symbol_idx)),
Some((left_symbol, left_symbol_diff, _left_symbol_idx)),
Some((right_symbol, right_symbol_diff, _right_symbol_idx)),
) =
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section)
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol)
{
// Joint diff view
hotkeys::check_scroll_hotkeys(ui, true);
let left_total_bytes =
left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
left_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
let right_total_bytes =
right_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
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");
return;
@ -528,10 +482,16 @@ pub fn diff_view_ui(
return;
}
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
let left_diffs =
split_diffs(&left_section_diff.data_diff, &left_section_diff.reloc_diff);
let right_diffs =
split_diffs(&right_section_diff.data_diff, &right_section_diff.reloc_diff);
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,
@ -540,13 +500,14 @@ pub fn diff_view_ui(
total_rows,
|row, column| {
let i = row.index();
let address = i * BYTES_PER_ROW;
let row_offset = i * BYTES_PER_ROW;
row.col(|ui| {
if column == 0 {
data_row_ui(
ui,
Some(left_obj),
address,
left_symbol.address as usize,
row_offset,
&left_diffs[i],
appearance,
column,
@ -555,7 +516,8 @@ pub fn diff_view_ui(
data_row_ui(
ui,
Some(right_obj),
address,
right_symbol.address as usize,
row_offset,
&right_diffs[i],
appearance,
column,
@ -649,11 +611,46 @@ fn diff_col_ui(
if !ctx.status.success {
build_log_ui(ui, ctx.status, appearance);
} else if let Some((obj, diff)) = ctx.obj {
if let Some((_symbol, symbol_diff, symbol_idx)) = ctx.symbol {
if let Some((symbol, symbol_diff, symbol_idx)) = ctx.symbol {
hotkeys::check_scroll_hotkeys(ui, false);
let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_idx) };
if state.current_view == View::ExtabDiff {
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 {
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,
1,
appearance.code_font.size,
total_rows,
|row, _column| {
let i = row.index();
let row_offset = i * BYTES_PER_ROW;
row.col(|ui| {
data_row_ui(
ui,
Some(obj),
symbol.address as usize,
row_offset,
&diffs[i],
appearance,
column,
);
});
},
);
} else {
render_table(
ui,
@ -678,29 +675,6 @@ fn diff_col_ui(
},
);
}
} else if let Some((_section, section_diff, _section_idx)) = ctx.section {
hotkeys::check_scroll_hotkeys(ui, false);
let total_bytes =
section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
if total_bytes == 0 {
return ret;
}
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
let diffs = split_diffs(&section_diff.data_diff, &section_diff.reloc_diff);
render_table(
ui,
available_width / 2.0,
1,
appearance.code_font.size,
total_rows,
|row, _column| {
let i = row.index();
let address = i * BYTES_PER_ROW;
row.col(|ui| {
data_row_ui(ui, Some(obj), address, &diffs[i], appearance, column);
});
},
);
} else if let Some((_other_symbol, _other_symbol_diff, other_symbol_idx)) = other_ctx.symbol
{
if let Some(action) = symbol_list_ui(
@ -796,10 +770,6 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
});
}
fn find_section(obj: &Object, section_name: &str) -> Option<usize> {
obj.sections.iter().position(|section| section.name == section_name)
}
pub fn hover_items_ui(ui: &mut Ui, items: Vec<HoverItem>, appearance: &Appearance) {
for item in items {
match item {

View File

@ -233,7 +233,6 @@ impl DiffViewState {
let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right);
if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some())
|| (resolved_nav.left_symbol.is_none() && resolved_nav.right_symbol.is_none())
|| resolved_nav.view != View::FunctionDiff
{
// Regular navigation
if state.is_selecting_symbol() {
@ -416,14 +415,8 @@ fn resolve_navigation(
},
(SectionKind::Data, SectionKind::Data) => ResolvedNavigation {
view: View::DataDiff,
left_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(left.section.name.clone()),
}),
right_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(right.section.name.clone()),
}),
left_symbol: Some(left.symbol_ref),
right_symbol: Some(right.symbol_ref),
},
_ => ResolvedNavigation::default(),
},
@ -438,14 +431,8 @@ fn resolve_navigation(
},
SectionKind::Data => ResolvedNavigation {
view: View::DataDiff,
left_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(left.section.name.clone()),
}),
right_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(left.section.name.clone()),
}),
left_symbol: Some(left.symbol_ref),
right_symbol: None,
},
_ => ResolvedNavigation::default(),
},
@ -460,14 +447,8 @@ fn resolve_navigation(
},
SectionKind::Data => ResolvedNavigation {
view: View::DataDiff,
left_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(right.section.name.clone()),
}),
right_symbol: Some(SymbolRefByName {
symbol_name: "".to_string(),
section_name: Some(right.section.name.clone()),
}),
left_symbol: None,
right_symbol: Some(right.symbol_ref),
},
_ => ResolvedNavigation::default(),
},