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