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 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 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 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 (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 name_ok = true;
325 } else if left_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
326 && right_reloc.symbol.flags.contains(SymbolFlag::CompilerGenerated)
327 {
328 name_ok = true;
330 } else if symbol_name_addend_matches || address_eq(left_reloc, right_reloc) {
331 name_ok = true;
333 }
334 let mut value_ok = false;
335 if diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress {
336 value_ok = true;
338 } else if left_reloc.symbol.kind != SymbolKind::Object {
339 value_ok = true;
341 } else if right_reloc.symbol.size == 0 {
342 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 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 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 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 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 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 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}