Skip to main content

objdiff_core/diff/
code.rs

1use alloc::{
2    collections::{BTreeMap, btree_map},
3    string::{String, ToString},
4    vec,
5    vec::Vec,
6};
7
8use anyhow::{Context, Result, anyhow, ensure};
9
10use super::{
11    DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
12    InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, PreferredStringEncoding,
13    SymbolDiff, display::display_ins_data_literals,
14};
15use crate::{
16    diff::{address_eq, section_name_eq, symbol_name_matches},
17    obj::{
18        InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
19        ResolvedRelocation, ResolvedSymbol, SymbolFlag, SymbolKind,
20    },
21};
22
23pub fn no_diff_code(
24    obj: &Object,
25    symbol_index: usize,
26    diff_config: &DiffObjConfig,
27) -> Result<SymbolDiff> {
28    let symbol = &obj.symbols[symbol_index];
29    let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
30    let section = &obj.sections[section_index];
31    let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
32        anyhow!(
33            "Symbol data out of bounds: {:#x}..{:#x}",
34            symbol.address,
35            symbol.address + symbol.size
36        )
37    })?;
38    let ops = obj.arch.scan_instructions(
39        ResolvedSymbol { obj, symbol_index, symbol, section_index, section, data },
40        diff_config,
41    )?;
42    let mut instruction_rows = Vec::<InstructionDiffRow>::new();
43    for i in &ops {
44        instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
45    }
46    resolve_branches(&ops, &mut instruction_rows);
47    Ok(SymbolDiff {
48        target_symbol: None,
49        match_percent: None,
50        diff_score: None,
51        instruction_rows,
52        ..Default::default()
53    })
54}
55
56const PENALTY_IMM_DIFF: u64 = 1;
57const PENALTY_REG_DIFF: u64 = 5;
58const PENALTY_REPLACE: u64 = 60;
59const PENALTY_INSERT_DELETE: u64 = 100;
60
61pub fn diff_code(
62    left_obj: &Object,
63    right_obj: &Object,
64    left_symbol_idx: usize,
65    right_symbol_idx: usize,
66    diff_config: &DiffObjConfig,
67) -> Result<(SymbolDiff, SymbolDiff)> {
68    let left_symbol = &left_obj.symbols[left_symbol_idx];
69    let right_symbol = &right_obj.symbols[right_symbol_idx];
70    let left_section = left_symbol
71        .section
72        .and_then(|i| left_obj.sections.get(i))
73        .ok_or_else(|| anyhow!("Missing section for symbol"))?;
74    let right_section = right_symbol
75        .section
76        .and_then(|i| right_obj.sections.get(i))
77        .ok_or_else(|| anyhow!("Missing section for symbol"))?;
78    let left_data = left_section
79        .data_range(left_symbol.address, left_symbol.size as usize)
80        .ok_or_else(|| {
81            anyhow!(
82                "Symbol data out of bounds: {:#x}..{:#x}",
83                left_symbol.address,
84                left_symbol.address + left_symbol.size
85            )
86        })?;
87    let right_data = right_section
88        .data_range(right_symbol.address, right_symbol.size as usize)
89        .ok_or_else(|| {
90            anyhow!(
91                "Symbol data out of bounds: {:#x}..{:#x}",
92                right_symbol.address,
93                right_symbol.address + right_symbol.size
94            )
95        })?;
96
97    let left_section_idx = left_symbol.section.unwrap();
98    let right_section_idx = right_symbol.section.unwrap();
99    let left_ops = left_obj.arch.scan_instructions(
100        ResolvedSymbol {
101            obj: left_obj,
102            symbol_index: left_symbol_idx,
103            symbol: left_symbol,
104            section_index: left_section_idx,
105            section: left_section,
106            data: left_data,
107        },
108        diff_config,
109    )?;
110    let right_ops = right_obj.arch.scan_instructions(
111        ResolvedSymbol {
112            obj: right_obj,
113            symbol_index: right_symbol_idx,
114            symbol: right_symbol,
115            section_index: right_section_idx,
116            section: right_section,
117            data: right_data,
118        },
119        diff_config,
120    )?;
121    let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
122    resolve_branches(&left_ops, &mut left_rows);
123    resolve_branches(&right_ops, &mut right_rows);
124
125    let mut diff_state = InstructionDiffState::default();
126    for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
127        let result = diff_instruction(
128            left_obj,
129            right_obj,
130            left_symbol_idx,
131            right_symbol_idx,
132            left_row.ins_ref,
133            right_row.ins_ref,
134            left_row,
135            right_row,
136            diff_config,
137            &mut diff_state,
138        )?;
139        left_row.kind = result.kind;
140        right_row.kind = result.kind;
141        left_row.arg_diff = result.left_args_diff;
142        right_row.arg_diff = result.right_args_diff;
143    }
144
145    let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
146    let diff_score = diff_state.diff_score.min(max_score);
147    let match_percent = if max_score == 0 {
148        100.0
149    } else {
150        ((1.0 - (diff_score as f64 / max_score as f64)) * 100.0) as f32
151    };
152
153    Ok((
154        SymbolDiff {
155            target_symbol: Some(right_symbol_idx),
156            match_percent: Some(match_percent),
157            diff_score: Some((diff_score, max_score)),
158            instruction_rows: left_rows,
159            ..Default::default()
160        },
161        SymbolDiff {
162            target_symbol: Some(left_symbol_idx),
163            match_percent: Some(match_percent),
164            diff_score: Some((diff_score, max_score)),
165            instruction_rows: right_rows,
166            ..Default::default()
167        },
168    ))
169}
170
171fn diff_instructions(
172    left_insts: &[InstructionRef],
173    right_insts: &[InstructionRef],
174) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
175    let left_ops = left_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
176    let right_ops = right_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
177    let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
178    if ops.is_empty() {
179        ensure!(left_insts.len() == right_insts.len());
180        let left_diff = left_insts
181            .iter()
182            .map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
183            .collect();
184        let right_diff = right_insts
185            .iter()
186            .map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
187            .collect();
188        return Ok((left_diff, right_diff));
189    }
190
191    let row_count = ops
192        .iter()
193        .map(|op| match *op {
194            similar::DiffOp::Equal { len, .. } => len,
195            similar::DiffOp::Delete { old_len, .. } => old_len,
196            similar::DiffOp::Insert { new_len, .. } => new_len,
197            similar::DiffOp::Replace { old_len, new_len, .. } => old_len.max(new_len),
198        })
199        .sum();
200    let mut left_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
201    let mut right_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
202    for op in ops {
203        let (_tag, left_range, right_range) = op.as_tag_tuple();
204        let len = left_range.len().max(right_range.len());
205        left_diff.extend(
206            left_range
207                .clone()
208                .map(|i| InstructionDiffRow { ins_ref: Some(left_insts[i]), ..Default::default() }),
209        );
210        right_diff.extend(
211            right_range.clone().map(|i| InstructionDiffRow {
212                ins_ref: Some(right_insts[i]),
213                ..Default::default()
214            }),
215        );
216        if left_range.len() < len {
217            left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
218        }
219        if right_range.len() < len {
220            right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
221        }
222    }
223    Ok((left_diff, right_diff))
224}
225
226fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> String {
227    match arg {
228        InstructionArg::Value(arg) => arg.to_string(),
229        InstructionArg::Reloc => {
230            reloc.as_ref().map_or_else(|| "<unknown>".to_string(), |r| r.symbol.name.clone())
231        }
232        InstructionArg::BranchDest(arg) => arg.to_string(),
233    }
234}
235
236fn resolve_branches(ops: &[InstructionRef], rows: &mut [InstructionDiffRow]) {
237    let mut branch_idx = 0u32;
238    // Map addresses to indices
239    let mut addr_map = BTreeMap::<u64, u32>::new();
240    for (i, ins_diff) in rows.iter().enumerate() {
241        if let Some(ins) = ins_diff.ins_ref {
242            addr_map.insert(ins.address, i as u32);
243        }
244    }
245    // Generate branches
246    let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
247    for ((i, ins_diff), ins) in
248        rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
249    {
250        if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a).copied()) {
251            match branches.entry(ins_idx) {
252                btree_map::Entry::Vacant(e) => {
253                    ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });
254                    e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
255                    branch_idx += 1;
256                }
257                btree_map::Entry::Occupied(e) => {
258                    let branch = e.into_mut();
259                    ins_diff.branch_to =
260                        Some(InstructionBranchTo { ins_idx, branch_idx: branch.branch_idx });
261                    branch.ins_idx.push(i as u32);
262                }
263            }
264        }
265    }
266    // Store branch from
267    for (i, branch) in branches {
268        rows[i as usize].branch_from = Some(branch);
269    }
270}
271
272fn ins_data_literals_eq(
273    left_obj: &Object,
274    right_obj: &Object,
275    left_ins: ResolvedInstructionRef,
276    right_ins: ResolvedInstructionRef,
277    diff_config: &DiffObjConfig,
278) -> bool {
279    let mut left_literals = display_ins_data_literals(left_obj, left_ins);
280    let mut right_literals = display_ins_data_literals(right_obj, right_ins);
281    if left_literals == right_literals {
282        return true;
283    }
284    if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
285        return left_literals == right_literals;
286    }
287    left_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
288    right_literals.retain(|lit_info| !lit_info.hidden(Some(diff_config)));
289    left_literals == right_literals
290}
291
292fn reloc_eq(
293    left_obj: &Object,
294    right_obj: &Object,
295    left_ins: ResolvedInstructionRef,
296    right_ins: ResolvedInstructionRef,
297    diff_config: &DiffObjConfig,
298) -> bool {
299    let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
300    let (left_reloc, right_reloc) = match (left_ins.relocation, right_ins.relocation) {
301        (Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
302        // If relocations are relaxed, match if left is missing a reloc
303        (None, Some(_)) => return relax_reloc_diffs,
304        (None, None) => return true,
305        _ => return false,
306    };
307    if left_reloc.relocation.flags != right_reloc.relocation.flags {
308        return false;
309    }
310    if relax_reloc_diffs {
311        return true;
312    }
313
314    let symbol_name_addend_matches = symbol_name_matches(left_reloc.symbol, right_reloc.symbol)
315        && left_reloc.relocation.addend == right_reloc.relocation.addend;
316    match (left_reloc.symbol.section, right_reloc.symbol.section) {
317        (Some(sl), Some(sr)) => {
318            if !section_name_eq(left_obj, right_obj, sl, sr) {
319                return false;
320            };
321            let mut name_ok = false;
322            if diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue {
323                // Ignore names entirely
324                name_ok = true;
325            } else if left_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
326                && right_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
327            {
328                // Match if both symbol names are fully compiler-generated
329                name_ok = true;
330            } else if symbol_name_addend_matches || address_eq(left_reloc, right_reloc) {
331                // Match if name+addend or address match
332                name_ok = true;
333            }
334            let mut value_ok = false;
335            if diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress {
336                // Ignore data values entirely
337                value_ok = true;
338            } else if left_reloc.symbol.kind != SymbolKind::Object {
339                // Not a data symbol, don't diff value
340                value_ok = true;
341            } else if right_reloc.symbol.size == 0 {
342                // Likely a pool symbol like ...data, don't treat this as a diff
343                value_ok = true;
344            } else if ins_data_literals_eq(left_obj, right_obj, left_ins, right_ins, diff_config) {
345                value_ok = true;
346            }
347            name_ok && value_ok
348        }
349        (Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
350    }
351}
352
353fn arg_eq(
354    left_obj: &Object,
355    right_obj: &Object,
356    left_row: &InstructionDiffRow,
357    right_row: &InstructionDiffRow,
358    left_arg: &InstructionArg,
359    right_arg: &InstructionArg,
360    left_ins: ResolvedInstructionRef,
361    right_ins: ResolvedInstructionRef,
362    diff_config: &DiffObjConfig,
363) -> bool {
364    match left_arg {
365        InstructionArg::Value(l) => match right_arg {
366            InstructionArg::Value(r) => l.loose_eq(r),
367            // If relocations are relaxed, match if left is a constant and right is a reloc
368            // Useful for instances where the target object is created without relocations
369            InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
370            _ => false,
371        },
372        InstructionArg::Reloc => {
373            matches!(right_arg, InstructionArg::Reloc)
374                && reloc_eq(left_obj, right_obj, left_ins, right_ins, diff_config)
375        }
376        InstructionArg::BranchDest(_) => match right_arg {
377            // Compare dest instruction idx after diffing
378            InstructionArg::BranchDest(_) => {
379                left_row.branch_to.as_ref().map(|b| b.ins_idx)
380                    == right_row.branch_to.as_ref().map(|b| b.ins_idx)
381            }
382            // If relocations are relaxed, match if left is a constant and right is a reloc
383            // Useful for instances where the target object is created without relocations
384            InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
385            _ => false,
386        },
387    }
388}
389
390#[derive(Default)]
391struct InstructionDiffState {
392    diff_score: u64,
393    left_arg_idx: u32,
394    right_arg_idx: u32,
395    left_args_idx: BTreeMap<String, u32>,
396    right_args_idx: BTreeMap<String, u32>,
397}
398
399#[derive(Default)]
400struct InstructionDiffResult {
401    kind: InstructionDiffKind,
402    left_args_diff: Vec<InstructionArgDiffIndex>,
403    right_args_diff: Vec<InstructionArgDiffIndex>,
404}
405
406impl InstructionDiffResult {
407    #[inline]
408    const fn new(kind: InstructionDiffKind) -> Self {
409        Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
410    }
411}
412
413fn diff_instruction(
414    left_obj: &Object,
415    right_obj: &Object,
416    left_symbol_idx: usize,
417    right_symbol_idx: usize,
418    l: Option<InstructionRef>,
419    r: Option<InstructionRef>,
420    left_row: &InstructionDiffRow,
421    right_row: &InstructionDiffRow,
422    diff_config: &DiffObjConfig,
423    state: &mut InstructionDiffState,
424) -> Result<InstructionDiffResult> {
425    let (l, r) = match (l, r) {
426        (Some(l), Some(r)) => (l, r),
427        (Some(_), None) => {
428            state.diff_score += PENALTY_INSERT_DELETE;
429            return Ok(InstructionDiffResult::new(InstructionDiffKind::Delete));
430        }
431        (None, Some(_)) => {
432            state.diff_score += PENALTY_INSERT_DELETE;
433            return Ok(InstructionDiffResult::new(InstructionDiffKind::Insert));
434        }
435        (None, None) => return Ok(InstructionDiffResult::new(InstructionDiffKind::None)),
436    };
437
438    // If opcodes don't match, replace
439    if l.opcode != r.opcode {
440        state.diff_score += PENALTY_REPLACE;
441        return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
442    }
443
444    let left_resolved = left_obj
445        .resolve_instruction_ref(left_symbol_idx, l)
446        .context("Failed to resolve left instruction")?;
447    let right_resolved = right_obj
448        .resolve_instruction_ref(right_symbol_idx, r)
449        .context("Failed to resolve right instruction")?;
450
451    if left_resolved.code != right_resolved.code
452        || !reloc_eq(left_obj, right_obj, left_resolved, right_resolved, diff_config)
453    {
454        // If either the raw code bytes or relocations don't match, process instructions and compare args
455        let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?;
456        let right_ins = right_obj.arch.process_instruction(right_resolved, diff_config)?;
457        if left_ins.args.len() != right_ins.args.len() {
458            state.diff_score += PENALTY_REPLACE;
459            return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
460        }
461        let mut result = InstructionDiffResult::new(InstructionDiffKind::None);
462        if left_ins.mnemonic != right_ins.mnemonic {
463            state.diff_score += PENALTY_REG_DIFF;
464            result.kind = InstructionDiffKind::OpMismatch;
465        }
466        for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
467            if arg_eq(
468                left_obj,
469                right_obj,
470                left_row,
471                right_row,
472                a,
473                b,
474                left_resolved,
475                right_resolved,
476                diff_config,
477            ) {
478                result.left_args_diff.push(InstructionArgDiffIndex::NONE);
479                result.right_args_diff.push(InstructionArgDiffIndex::NONE);
480            } else {
481                state.diff_score += if let InstructionArg::Value(
482                    InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
483                ) = a
484                {
485                    PENALTY_IMM_DIFF
486                } else {
487                    PENALTY_REG_DIFF
488                };
489                if result.kind == InstructionDiffKind::None {
490                    result.kind = InstructionDiffKind::ArgMismatch;
491                }
492                let a_str = arg_to_string(a, left_resolved.relocation);
493                let a_diff = match state.left_args_idx.entry(a_str) {
494                    btree_map::Entry::Vacant(e) => {
495                        let idx = state.left_arg_idx;
496                        state.left_arg_idx = idx + 1;
497                        e.insert(idx);
498                        idx
499                    }
500                    btree_map::Entry::Occupied(e) => *e.get(),
501                };
502                let b_str = arg_to_string(b, right_resolved.relocation);
503                let b_diff = match state.right_args_idx.entry(b_str) {
504                    btree_map::Entry::Vacant(e) => {
505                        let idx = state.right_arg_idx;
506                        state.right_arg_idx = idx + 1;
507                        e.insert(idx);
508                        idx
509                    }
510                    btree_map::Entry::Occupied(e) => *e.get(),
511                };
512                result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
513                result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
514            }
515        }
516        if result.kind == InstructionDiffKind::None
517            && left_resolved.code.len() != right_resolved.code.len()
518        {
519            // If everything else matches but the raw code length differs (e.g. x86 instructions
520            // with same disassembly but different encoding), mark as op mismatch
521            result.kind = InstructionDiffKind::OpMismatch;
522            state.diff_score += PENALTY_REG_DIFF;
523        }
524        return Ok(result);
525    }
526
527    Ok(InstructionDiffResult::new(InstructionDiffKind::None))
528}