Skip to main content

objdiff_core/diff/
data.rs

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