1use std::{sync::mpsc::Receiver, task::Waker};
2
3use anyhow::Result;
4use typed_path::Utf8PlatformPathBuf;
5
6use crate::{
7 build::{BuildConfig, run_make},
8 diff::{DiffObjConfig, DiffSide, display::InstructionPart, find_similar_code_symbols},
9 jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
10 obj::{InstructionArg, read},
11};
12
13pub struct FindSimilarConfig {
14 pub source_path: Utf8PlatformPathBuf,
16 pub source_symbol_name: String,
18 pub source_column: usize,
20 pub objects: Vec<ScanObject>,
22 pub diff_config: DiffObjConfig,
23 pub build_config: BuildConfig,
24 pub build_base: bool,
25 pub build_target: bool,
26}
27
28pub struct ScanObject {
29 pub name: String,
30 pub target_path: Option<Utf8PlatformPathBuf>,
31 pub base_path: Option<Utf8PlatformPathBuf>,
32}
33
34#[derive(Debug, Clone)]
35pub struct SimilarFunctionMatch {
36 pub symbol_name: String,
37 pub demangled_name: Option<String>,
38 pub match_percent: f32,
39 pub object_name: String,
41}
42
43pub struct FindSimilarResult {
44 pub source_symbol_name: String,
45 pub source_column: usize,
46 pub matches: Vec<SimilarFunctionMatch>,
47}
48
49fn run_find_similar(
50 context: &JobContext,
51 cancel: Receiver<()>,
52 config: FindSimilarConfig,
53) -> Result<Box<FindSimilarResult>> {
54 let diff_side = if config.source_column == 0 { DiffSide::Target } else { DiffSide::Base };
55 let source_obj = read::read(config.source_path.as_ref(), &config.diff_config, diff_side)?;
56 let source_symbol_idx =
57 source_obj.symbol_by_name(&config.source_symbol_name).ok_or_else(|| {
58 anyhow::anyhow!("Source symbol '{}' not found", config.source_symbol_name)
59 })?;
60
61 'print: {
63 let symbol = &source_obj.symbols[source_symbol_idx];
64 let Some(section_index) = symbol.section else { break 'print };
65 let section = &source_obj.sections[section_index];
66 let Some(data) = section.data_range(symbol.address, symbol.size as usize) else {
67 break 'print;
68 };
69 let Ok(instructions) = source_obj.arch.scan_instructions(
70 crate::obj::ResolvedSymbol {
71 obj: &source_obj,
72 symbol_index: source_symbol_idx,
73 symbol,
74 section_index,
75 section,
76 data,
77 },
78 &config.diff_config,
79 ) else {
80 break 'print;
81 };
82 log::info!(
83 "find_similar: source symbol '{}' — {} instructions",
84 config.source_symbol_name,
85 instructions.len()
86 );
87 for ins_ref in &instructions {
88 let Some(resolved) = source_obj.resolve_instruction_ref(source_symbol_idx, *ins_ref)
89 else {
90 continue;
91 };
92 let mut text = format!("{:#010x} ", ins_ref.address);
93 let _ =
94 source_obj.arch.display_instruction(resolved, &config.diff_config, &mut |part| {
95 match part {
96 InstructionPart::Basic(s) | InstructionPart::Opcode(s, _) => {
97 text.push_str(&s)
98 }
99 InstructionPart::Arg(InstructionArg::Value(v)) => {
100 text.push_str(&v.to_string())
101 }
102 InstructionPart::Arg(InstructionArg::BranchDest(addr)) => {
103 text.push_str(&format!("{addr:#x}"))
104 }
105 InstructionPart::Arg(InstructionArg::Reloc) => {
106 if let Some(reloc) = resolved.relocation {
107 let sym = &source_obj.symbols[reloc.relocation.target_symbol];
108 text.push_str(sym.demangled_name.as_deref().unwrap_or(&sym.name));
109 if reloc.relocation.addend != 0 {
110 text.push_str(&format!("+{:#x}", reloc.relocation.addend));
111 }
112 } else {
113 text.push_str("<reloc>");
114 }
115 }
116 InstructionPart::Separator => text.push_str(", "),
117 }
118 Ok(())
119 });
120 log::info!("{text}");
121 }
122 }
123
124 let total = config.objects.len() as u32;
125 let mut all_matches = Vec::new();
126
127 for (idx, scan_obj) in config.objects.iter().enumerate() {
128 update_status(context, format!("Scanning {}", scan_obj.name), idx as u32, total, &cancel)?;
129
130 let project_dir = config.build_config.project_dir.as_deref();
131
132 for side in [DiffSide::Target, DiffSide::Base] {
133 let (path, should_build) = match side {
134 DiffSide::Target => (scan_obj.target_path.as_ref(), config.build_target),
135 DiffSide::Base => (scan_obj.base_path.as_ref(), config.build_base),
136 };
137 let Some(path) = path else { continue };
138
139 if should_build
140 && let Some(project_dir) = project_dir
141 && let Ok(rel_path) = path.strip_prefix(project_dir)
142 {
143 run_make(&config.build_config, rel_path.with_unix_encoding().as_ref());
144 }
145
146 let Ok(obj) = read::read(path.as_ref(), &config.diff_config, side) else { continue };
147 let similar = find_similar_code_symbols(
148 &source_obj,
149 source_symbol_idx,
150 &obj,
151 &config.diff_config,
152 );
153 let side_label = if side == DiffSide::Target { "target" } else { "base" };
154 for sim in similar {
155 let symbol = &obj.symbols[sim.symbol_idx];
156 all_matches.push(SimilarFunctionMatch {
157 symbol_name: symbol.name.clone(),
158 demangled_name: symbol.demangled_name.clone(),
159 match_percent: sim.match_percent,
160 object_name: format!("{} ({})", scan_obj.name, side_label),
161 });
162 }
163 }
164 }
165
166 all_matches.sort_by(|a, b| {
167 b.match_percent.partial_cmp(&a.match_percent).unwrap_or(std::cmp::Ordering::Equal)
168 });
169
170 Ok(Box::new(FindSimilarResult {
171 source_symbol_name: config.source_symbol_name,
172 source_column: config.source_column,
173 matches: all_matches,
174 }))
175}
176
177pub fn start_find_similar(waker: Waker, config: FindSimilarConfig) -> JobState {
178 start_job(waker, "Find similar functions", Job::FindSimilar, move |context, cancel| {
179 run_find_similar(&context, cancel, config).map(|r| JobResult::FindSimilar(Some(r)))
180 })
181}