Skip to main content

objdiff_core/jobs/
find_similar.rs

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    /// Path to the source object file to search for.
15    pub source_path: Utf8PlatformPathBuf,
16    /// Mangled name of the source symbol.
17    pub source_symbol_name: String,
18    /// 0 = left/target column, 1 = right/base column.
19    pub source_column: usize,
20    /// All project objects to scan.
21    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    /// Human-readable identifier: "ObjectName (target)" or "ObjectName (base)".
40    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 the instructions of the source symbol to the console.
62    '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}