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};
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                // Match if both symbol names are fully compiler-generated
62                name_ok = true;
63            } else if symbol_name_addend_matches || address_eq(left_reloc, right_reloc) {
64                // Match if name+addend or address match
65                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
82/// Compares the bytes within a certain data range.
83fn 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                // Ensure replacements are equal length
100                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
152/// Compares relocations contained within a certain data range.
153fn 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        // No need to check the cases for relocations being deleted or matching again.
204        // They were already handled in the loop over the left relocs.
205    }
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
232/// Compare the data sections of two object files.
233pub 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        // Use the highest match percent between two options:
305        // - Left symbols matching right symbols by name
306        // - Diff of the data itself
307        // We only do this when all relocations on the left side match.
308        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 = &section.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            // Adjust the overall match ratio to include relocation differences.
469            // We calculate it so that bytes that contain a relocation are counted twice: once for the
470            // byte's raw value, and once for its relocation.
471            // e.g. An 8 byte symbol that has 8 matching raw bytes and a single 4 byte relocation that
472            // doesn't match would show as 66% (weighted average of 100% and 0%).
473            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
507/// Compares a section of two object files.
508/// This essentially adds up the match percentage of each symbol in the section.
509pub 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 // Avoid fp precision issues
522    } 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
540/// Compare the addresses and sizes of each symbol in the BSS sections.
541pub 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    // Use the highest match percent between two options:
561    // - Left symbols matching right symbols by name
562    // - Diff of the addresses and sizes of each symbol
563    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    // Collect all segments that overlap with this row
611    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    // Collect all relocations that overlap with this row
637    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}