1use alloc::{vec, vec::Vec};
2use core::{cmp::Ordering, ops::Range};
3
4use anyhow::{Result, anyhow};
5use similar::{Algorithm, capture_diff_slices, diff_ratio};
6
7use super::{
8 DataDiff, DataDiffKind, DataDiffRow, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
9};
10use crate::{
11 diff::{address_eq, section_name_eq, symbol_name_matches},
12 obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind},
13};
14
15pub fn diff_bss_symbol(
16 left_obj: &Object,
17 right_obj: &Object,
18 left_symbol_ref: usize,
19 right_symbol_ref: usize,
20) -> Result<(SymbolDiff, SymbolDiff)> {
21 let left_symbol = &left_obj.symbols[left_symbol_ref];
22 let right_symbol = &right_obj.symbols[right_symbol_ref];
23 let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
24 Ok((
25 SymbolDiff {
26 target_symbol: Some(right_symbol_ref),
27 match_percent: Some(percent),
28 diff_score: None,
29 ..Default::default()
30 },
31 SymbolDiff {
32 target_symbol: Some(left_symbol_ref),
33 match_percent: Some(percent),
34 diff_score: None,
35 ..Default::default()
36 },
37 ))
38}
39
40fn reloc_eq(
41 left_obj: &Object,
42 right_obj: &Object,
43 left_reloc: ResolvedRelocation,
44 right_reloc: ResolvedRelocation,
45) -> bool {
46 if left_reloc.relocation.flags != right_reloc.relocation.flags {
47 return false;
48 }
49
50 let symbol_name_addend_matches = symbol_name_matches(left_reloc.symbol, right_reloc.symbol)
51 && left_reloc.relocation.addend == right_reloc.relocation.addend;
52 match (left_reloc.symbol.section, right_reloc.symbol.section) {
53 (Some(sl), Some(sr)) => {
54 if !section_name_eq(left_obj, right_obj, sl, sr) {
55 return false;
56 };
57 let mut name_ok = false;
58 if left_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
59 && right_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
60 {
61 name_ok = true;
63 } else if symbol_name_addend_matches || address_eq(left_reloc, right_reloc) {
64 name_ok = true;
66 }
67 name_ok
68 }
69 (Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
70 }
71}
72
73#[inline]
74pub fn resolve_relocation<'obj>(
75 symbols: &'obj [Symbol],
76 reloc: &'obj Relocation,
77) -> ResolvedRelocation<'obj> {
78 let symbol = &symbols[reloc.target_symbol];
79 ResolvedRelocation { relocation: reloc, symbol }
80}
81
82fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>, Vec<DataDiff>) {
84 let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
85 let bytes_match_ratio = diff_ratio(&ops, left_data.len(), right_data.len());
86
87 let mut left_data_diff = Vec::<DataDiff>::new();
88 let mut right_data_diff = Vec::<DataDiff>::new();
89 for op in ops {
90 let (tag, left_range, right_range) = op.as_tag_tuple();
91 let left_len = left_range.len();
92 let right_len = right_range.len();
93 let mut len = left_len.max(right_len);
94 let kind = match tag {
95 similar::DiffTag::Equal => DataDiffKind::None,
96 similar::DiffTag::Delete => DataDiffKind::Delete,
97 similar::DiffTag::Insert => DataDiffKind::Insert,
98 similar::DiffTag::Replace => {
99 len = left_len.min(right_len);
101 DataDiffKind::Replace
102 }
103 };
104 let left_data = &left_data[left_range];
105 let right_data = &right_data[right_range];
106 left_data_diff.push(DataDiff {
107 data: left_data[..len.min(left_data.len())].to_vec(),
108 kind,
109 size: len,
110 });
111 right_data_diff.push(DataDiff {
112 data: right_data[..len.min(right_data.len())].to_vec(),
113 kind,
114 size: len,
115 });
116 if kind == DataDiffKind::Replace {
117 match left_len.cmp(&right_len) {
118 Ordering::Less => {
119 let len = right_len - left_len;
120 left_data_diff.push(DataDiff {
121 data: vec![],
122 kind: DataDiffKind::Insert,
123 size: len,
124 });
125 right_data_diff.push(DataDiff {
126 data: right_data[left_len..right_len].to_vec(),
127 kind: DataDiffKind::Insert,
128 size: len,
129 });
130 }
131 Ordering::Greater => {
132 let len = left_len - right_len;
133 left_data_diff.push(DataDiff {
134 data: left_data[right_len..left_len].to_vec(),
135 kind: DataDiffKind::Delete,
136 size: len,
137 });
138 right_data_diff.push(DataDiff {
139 data: vec![],
140 kind: DataDiffKind::Delete,
141 size: len,
142 });
143 }
144 Ordering::Equal => {}
145 }
146 }
147 }
148
149 (bytes_match_ratio, left_data_diff, right_data_diff)
150}
151
152fn diff_data_relocs_for_range<'left, 'right>(
154 left_obj: &'left Object,
155 right_obj: &'right Object,
156 left_section_idx: usize,
157 right_section_idx: usize,
158 left_range: Range<usize>,
159 right_range: Range<usize>,
160) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
161 let left_section = &left_obj.sections[left_section_idx];
162 let right_section = &right_obj.sections[right_section_idx];
163 let mut diffs = Vec::new();
164 for left_reloc in left_section.relocations.iter() {
165 if !left_range.contains(&(left_reloc.address as usize)) {
166 continue;
167 }
168 let left_offset = left_reloc.address as usize - left_range.start;
169 let left_reloc = resolve_relocation(&left_obj.symbols, left_reloc);
170 let Some(right_reloc) = right_section.relocations.iter().find(|r| {
171 if !right_range.contains(&(r.address as usize)) {
172 return false;
173 }
174 let right_offset = r.address as usize - right_range.start;
175 right_offset == left_offset
176 }) else {
177 diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
178 continue;
179 };
180 let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
181 if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
182 diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
183 } else {
184 diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc)));
185 }
186 }
187 for right_reloc in right_section.relocations.iter() {
188 if !right_range.contains(&(right_reloc.address as usize)) {
189 continue;
190 }
191 let right_offset = right_reloc.address as usize - right_range.start;
192 let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
193 let Some(_) = left_section.relocations.iter().find(|r| {
194 if !left_range.contains(&(r.address as usize)) {
195 return false;
196 }
197 let left_offset = r.address as usize - left_range.start;
198 left_offset == right_offset
199 }) else {
200 diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
201 continue;
202 };
203 }
206 diffs
207}
208
209pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
210 let section = &obj.sections[section_idx];
211
212 let data_diff = vec![DataDiff {
213 data: section.data.0.clone(),
214 kind: DataDiffKind::None,
215 size: section.data.len(),
216 }];
217
218 let mut reloc_diffs = Vec::new();
219 for reloc in section.relocations.iter() {
220 let reloc_len = obj.arch.data_reloc_size(reloc.flags);
221 let range = reloc.address..reloc.address + reloc_len as u64;
222 reloc_diffs.push(DataRelocationDiff {
223 reloc: reloc.clone(),
224 kind: DataDiffKind::None,
225 range,
226 });
227 }
228
229 Ok(SectionDiff { match_percent: Some(0.0), data_diff, reloc_diff: reloc_diffs })
230}
231
232pub fn diff_data_section(
234 left_obj: &Object,
235 right_obj: &Object,
236 left_diff: &ObjectDiff,
237 right_diff: &ObjectDiff,
238 left_section_idx: usize,
239 right_section_idx: usize,
240) -> Result<(SectionDiff, SectionDiff)> {
241 let left_section = &left_obj.sections[left_section_idx];
242 let right_section = &right_obj.sections[right_section_idx];
243 let left_max = symbols_matching_section(&left_obj.symbols, left_section_idx)
244 .filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| a + s.size))
245 .max()
246 .unwrap_or(0)
247 .min(left_section.size);
248 let right_max = symbols_matching_section(&right_obj.symbols, right_section_idx)
249 .filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| a + s.size))
250 .max()
251 .unwrap_or(0)
252 .min(right_section.size);
253 let left_data = &left_section.data[..left_max as usize];
254 let right_data = &right_section.data[..right_max as usize];
255
256 let (bytes_match_ratio, left_data_diff, right_data_diff) =
257 diff_data_range(left_data, right_data);
258 let match_percent = bytes_match_ratio * 100.0;
259
260 let mut left_reloc_diffs = Vec::new();
261 let mut right_reloc_diffs = Vec::new();
262 for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
263 left_obj,
264 right_obj,
265 left_section_idx,
266 right_section_idx,
267 0..left_max as usize,
268 0..right_max as usize,
269 ) {
270 if let Some(left_reloc) = left_reloc {
271 let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
272 let range = left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
273 left_reloc_diffs.push(DataRelocationDiff {
274 reloc: left_reloc.relocation.clone(),
275 kind: diff_kind,
276 range,
277 });
278 }
279 if let Some(right_reloc) = right_reloc {
280 let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
281 let range = right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
282 right_reloc_diffs.push(DataRelocationDiff {
283 reloc: right_reloc.relocation.clone(),
284 kind: diff_kind,
285 range,
286 });
287 }
288 }
289
290 let (mut left_section_diff, mut right_section_diff) = diff_generic_section(
291 left_obj,
292 right_obj,
293 left_diff,
294 right_diff,
295 left_section_idx,
296 right_section_idx,
297 )?;
298 let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == DataDiffKind::None);
299 left_section_diff.data_diff = left_data_diff;
300 right_section_diff.data_diff = right_data_diff;
301 left_section_diff.reloc_diff = left_reloc_diffs;
302 right_section_diff.reloc_diff = right_reloc_diffs;
303 if all_left_relocs_match {
304 if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
309 left_section_diff.match_percent = Some(match_percent);
310 }
311 }
312 Ok((left_section_diff, right_section_diff))
313}
314
315pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDiff> {
316 let symbol = &obj.symbols[symbol_index];
317 let section_idx = symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
318 let section = &obj.sections[section_idx];
319
320 let start = symbol
321 .address
322 .checked_sub(section.address)
323 .ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
324 let end = start + symbol.size;
325 if end > section.size {
326 return Err(anyhow!(
327 "Symbol {} size out of section bounds ({} > {})",
328 symbol.name,
329 end,
330 section.size
331 ));
332 }
333 let range = start as usize..end as usize;
334 let data = §ion.data[range.clone()];
335
336 let data_diff = vec![DataDiff {
337 data: data.to_vec(),
338 kind: DataDiffKind::None,
339 size: symbol.size as usize,
340 }];
341
342 let mut reloc_diffs = Vec::new();
343 for reloc in section.relocations.iter() {
344 if !range.contains(&(reloc.address as usize)) {
345 continue;
346 }
347 let reloc_len = obj.arch.data_reloc_size(reloc.flags);
348 let range = reloc.address..reloc.address + reloc_len as u64;
349 reloc_diffs.push(DataRelocationDiff {
350 reloc: reloc.clone(),
351 kind: DataDiffKind::None,
352 range,
353 });
354 }
355
356 let data_rows = build_data_diff_rows(&data_diff, &reloc_diffs, symbol.address);
357 Ok(SymbolDiff {
358 target_symbol: None,
359 match_percent: None,
360 diff_score: None,
361 data_rows,
362 ..Default::default()
363 })
364}
365
366pub fn diff_data_symbol(
367 left_obj: &Object,
368 right_obj: &Object,
369 left_symbol_idx: usize,
370 right_symbol_idx: usize,
371) -> Result<(SymbolDiff, SymbolDiff)> {
372 let left_symbol = &left_obj.symbols[left_symbol_idx];
373 let right_symbol = &right_obj.symbols[right_symbol_idx];
374
375 let left_section_idx =
376 left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
377 let right_section_idx =
378 right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
379
380 let left_section = &left_obj.sections[left_section_idx];
381 let right_section = &right_obj.sections[right_section_idx];
382
383 let left_start = left_symbol
384 .address
385 .checked_sub(left_section.address)
386 .ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
387 let right_start = right_symbol
388 .address
389 .checked_sub(right_section.address)
390 .ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
391 let left_end = left_start + left_symbol.size;
392 if left_end > left_section.size {
393 return Err(anyhow!(
394 "Symbol {} size out of section bounds ({} > {})",
395 left_symbol.name,
396 left_end,
397 left_section.size
398 ));
399 }
400 let right_end = right_start + right_symbol.size;
401 if right_end > right_section.size {
402 return Err(anyhow!(
403 "Symbol {} size out of section bounds ({} > {})",
404 right_symbol.name,
405 right_end,
406 right_section.size
407 ));
408 }
409 let left_range = left_start as usize..left_end as usize;
410 let right_range = right_start as usize..right_end as usize;
411 let left_data = &left_section.data[left_range.clone()];
412 let right_data = &right_section.data[right_range.clone()];
413
414 let (bytes_match_ratio, left_data_diff, right_data_diff) =
415 diff_data_range(left_data, right_data);
416
417 let reloc_diffs = diff_data_relocs_for_range(
418 left_obj,
419 right_obj,
420 left_section_idx,
421 right_section_idx,
422 left_range,
423 right_range,
424 );
425
426 let mut match_ratio = bytes_match_ratio;
427 let mut left_reloc_diffs = Vec::new();
428 let mut right_reloc_diffs = Vec::new();
429 if !reloc_diffs.is_empty() {
430 let mut total_reloc_bytes = 0;
431 let mut matching_reloc_bytes = 0;
432 for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
433 let reloc_diff_len = match (left_reloc, right_reloc) {
434 (None, None) => unreachable!(),
435 (None, Some(right_reloc)) => {
436 right_obj.arch.data_reloc_size(right_reloc.relocation.flags)
437 }
438 (Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags),
439 };
440 total_reloc_bytes += reloc_diff_len;
441 if diff_kind == DataDiffKind::None {
442 matching_reloc_bytes += reloc_diff_len;
443 }
444
445 if let Some(left_reloc) = left_reloc {
446 let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
447 let range =
448 left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
449 left_reloc_diffs.push(DataRelocationDiff {
450 reloc: left_reloc.relocation.clone(),
451 kind: diff_kind,
452 range,
453 });
454 }
455 if let Some(right_reloc) = right_reloc {
456 let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
457 let range =
458 right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
459 right_reloc_diffs.push(DataRelocationDiff {
460 reloc: right_reloc.relocation.clone(),
461 kind: diff_kind,
462 range,
463 });
464 }
465 }
466 if total_reloc_bytes > 0 {
467 let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
468 match_ratio = ((bytes_match_ratio * (left_data.len() as f32))
474 + (relocs_match_ratio * total_reloc_bytes as f32))
475 / (left_data.len() + total_reloc_bytes) as f32;
476 }
477 }
478
479 left_reloc_diffs
480 .sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
481 right_reloc_diffs
482 .sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
483
484 let match_percent = match_ratio * 100.0;
485 let left_rows = build_data_diff_rows(&left_data_diff, &left_reloc_diffs, left_symbol.address);
486 let right_rows =
487 build_data_diff_rows(&right_data_diff, &right_reloc_diffs, right_symbol.address);
488
489 Ok((
490 SymbolDiff {
491 target_symbol: Some(right_symbol_idx),
492 match_percent: Some(match_percent),
493 diff_score: None,
494 data_rows: left_rows,
495 ..Default::default()
496 },
497 SymbolDiff {
498 target_symbol: Some(left_symbol_idx),
499 match_percent: Some(match_percent),
500 diff_score: None,
501 data_rows: right_rows,
502 ..Default::default()
503 },
504 ))
505}
506
507pub fn diff_generic_section(
510 left_obj: &Object,
511 _right_obj: &Object,
512 left_diff: &ObjectDiff,
513 _right_diff: &ObjectDiff,
514 left_section_idx: usize,
515 _right_section_idx: usize,
516) -> Result<(SectionDiff, SectionDiff)> {
517 let match_percent = if symbols_matching_section(&left_obj.symbols, left_section_idx)
518 .map(|(i, _)| &left_diff.symbols[i])
519 .all(|d| d.match_percent == Some(100.0))
520 {
521 100.0 } else {
523 let (matched, total) = symbols_matching_section(&left_obj.symbols, left_section_idx)
524 .map(|(i, s)| (s, &left_diff.symbols[i]))
525 .fold((0.0, 0.0), |(matched, total), (s, d)| {
526 (matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
527 });
528 if total == 0.0 { 100.0 } else { matched / total }
529 };
530 Ok((
531 SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
532 SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
533 ))
534}
535
536pub fn no_diff_bss_section() -> Result<SectionDiff> {
537 Ok(SectionDiff { match_percent: Some(0.0), data_diff: vec![], reloc_diff: vec![] })
538}
539
540pub fn diff_bss_section(
542 left_obj: &Object,
543 right_obj: &Object,
544 left_diff: &ObjectDiff,
545 right_diff: &ObjectDiff,
546 left_section_idx: usize,
547 right_section_idx: usize,
548) -> Result<(SectionDiff, SectionDiff)> {
549 let left_section = &left_obj.sections[left_section_idx];
550 let left_sizes = symbols_matching_section(&left_obj.symbols, left_section_idx)
551 .filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| (a, s.size)))
552 .collect::<Vec<_>>();
553 let right_section = &right_obj.sections[right_section_idx];
554 let right_sizes = symbols_matching_section(&right_obj.symbols, right_section_idx)
555 .filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| (a, s.size)))
556 .collect::<Vec<_>>();
557 let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
558 let mut match_percent = diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
559
560 let (generic_diff, _) = diff_generic_section(
564 left_obj,
565 right_obj,
566 left_diff,
567 right_diff,
568 left_section_idx,
569 right_section_idx,
570 )?;
571 if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
572 match_percent = generic_diff.match_percent.unwrap();
573 }
574
575 Ok((
576 SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
577 SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
578 ))
579}
580
581fn symbols_matching_section(
582 symbols: &[Symbol],
583 section_idx: usize,
584) -> impl Iterator<Item = (usize, &Symbol)> + '_ {
585 symbols.iter().enumerate().filter(move |(_, s)| {
586 s.section == Some(section_idx)
587 && s.kind != SymbolKind::Section
588 && s.size > 0
589 && !s.flags.contains(SymbolFlag::Hidden)
590 && !s.flags.contains(SymbolFlag::Ignored)
591 })
592}
593
594pub const BYTES_PER_ROW: usize = 16;
595
596fn build_data_diff_row(
597 data_diffs: &[DataDiff],
598 reloc_diffs: &[DataRelocationDiff],
599 symbol_address: u64,
600 row_index: usize,
601) -> DataDiffRow {
602 let row_start = row_index * BYTES_PER_ROW;
603 let row_end = row_start + BYTES_PER_ROW;
604 let mut row_diff = DataDiffRow {
605 address: symbol_address + row_start as u64,
606 segments: Vec::new(),
607 relocations: Vec::new(),
608 };
609
610 let mut current_offset = 0;
612 for diff in data_diffs {
613 let diff_end = current_offset + diff.size;
614 if current_offset < row_end && diff_end > row_start {
615 let start_in_diff = row_start.saturating_sub(current_offset);
616 let end_in_diff = row_end.min(diff_end) - current_offset;
617 if start_in_diff < end_in_diff {
618 let data_slice = if diff.data.is_empty() {
619 Vec::new()
620 } else {
621 diff.data[start_in_diff..end_in_diff.min(diff.data.len())].to_vec()
622 };
623 row_diff.segments.push(DataDiff {
624 data: data_slice,
625 kind: diff.kind,
626 size: end_in_diff - start_in_diff,
627 });
628 }
629 }
630 current_offset = diff_end;
631 if current_offset >= row_start + BYTES_PER_ROW {
632 break;
633 }
634 }
635
636 let row_end_absolute = row_diff.address + BYTES_PER_ROW as u64;
638 row_diff.relocations = reloc_diffs
639 .iter()
640 .filter(|rd| rd.range.start < row_end_absolute && rd.range.end > row_diff.address)
641 .cloned()
642 .collect();
643
644 row_diff
645}
646
647fn build_data_diff_rows(
648 segments: &[DataDiff],
649 relocations: &[DataRelocationDiff],
650 symbol_address: u64,
651) -> Vec<DataDiffRow> {
652 let total_len = segments.iter().map(|s| s.size as u64).sum::<u64>();
653 let num_rows = total_len.div_ceil(BYTES_PER_ROW as u64) as usize;
654 (0..num_rows)
655 .map(|row_index| build_data_diff_row(segments, relocations, symbol_address, row_index))
656 .collect()
657}