1use crate::binary::{BinaryProvider, MmapBinaryProvider};
2use crate::disasm;
3use crate::dwarf::{DwarfIndex, SourceLocation};
4use crate::error::Result;
5use crate::normalize::NormalizerChain;
6use crate::symbol::ResolvedSymbol;
7use rayon::prelude::*;
8use serde::Serialize;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11
12const MERGE_THRESHOLD: usize = 8;
13
14#[derive(Debug, Clone, Serialize)]
15#[non_exhaustive]
16pub struct ByteRange {
17 pub offset: u64,
18 pub len: usize,
19 pub left_bytes: Vec<u8>,
20 pub right_bytes: Vec<u8>,
21}
22
23#[derive(Debug, Clone, Serialize)]
24#[non_exhaustive]
25pub struct SymbolDiff {
26 pub symbol: ResolvedSymbol,
27 pub source_location: Option<SourceLocation>,
28 pub ranges: Vec<ByteRange>,
29 pub disasm_left: Option<Vec<String>>,
30 pub disasm_right: Option<Vec<String>>,
31}
32
33#[derive(Debug, Clone, Serialize)]
34#[non_exhaustive]
35pub struct AnonymousDiff {
36 pub section: String,
37 pub ranges: Vec<ByteRange>,
38}
39
40#[derive(Debug, Clone, Serialize)]
41#[non_exhaustive]
42pub struct DiffResult {
43 pub left_path: PathBuf,
44 pub right_path: PathBuf,
45 pub identical: bool,
46 pub symbol_diffs: Vec<SymbolDiff>,
47 pub anonymous_diffs: Vec<AnonymousDiff>,
48 pub sections_only_in_left: Vec<String>,
49 pub sections_only_in_right: Vec<String>,
50}
51
52pub struct DiffConfig {
53 pub normalizer: NormalizerChain,
54 pub show_disasm: bool,
55 pub parallel: bool,
56 pub ignored_sections: Vec<String>,
57}
58
59impl Default for DiffConfig {
60 fn default() -> Self {
61 Self {
62 normalizer: NormalizerChain::default(),
63 show_disasm: false,
64 parallel: true,
65 ignored_sections: Vec::new(),
66 }
67 }
68}
69
70const _: fn() = || {
72 fn assert_send_sync<T: Send + Sync>() {}
73 assert_send_sync::<DiffResult>();
74};
75
76pub struct SemanticDiff;
77
78impl SemanticDiff {
79 pub fn compare_paths(left: &Path, right: &Path) -> Result<DiffResult> {
82 let left_bin = MmapBinaryProvider::open(left)?;
83 let right_bin = MmapBinaryProvider::open(right)?;
84 Self::compare(&left_bin, &right_bin, &DiffConfig::default())
85 }
86
87 pub fn compare(
88 left: &dyn BinaryProvider,
89 right: &dyn BinaryProvider,
90 config: &DiffConfig,
91 ) -> Result<DiffResult> {
92 let mut left_sections: HashMap<String, (Vec<u8>, u64)> = HashMap::new();
94 let mut right_sections: HashMap<String, (Vec<u8>, u64)> = HashMap::new();
95
96 left.visit_sections(&mut |name, _kind, addr, data| {
97 if !config.ignored_sections.iter().any(|s| s == name) {
98 left_sections.insert(name.to_owned(), (data.to_vec(), addr));
99 }
100 Ok(())
101 })?;
102
103 right.visit_sections(&mut |name, _kind, addr, data| {
104 if !config.ignored_sections.iter().any(|s| s == name) {
105 right_sections.insert(name.to_owned(), (data.to_vec(), addr));
106 }
107 Ok(())
108 })?;
109
110 let sections_only_in_left: Vec<String> = left_sections
111 .keys()
112 .filter(|k| !right_sections.contains_key(*k))
113 .cloned()
114 .collect();
115
116 let sections_only_in_right: Vec<String> = right_sections
117 .keys()
118 .filter(|k| !left_sections.contains_key(*k))
119 .cloned()
120 .collect();
121
122 let shared: Vec<String> = left_sections
123 .keys()
124 .filter(|k| right_sections.contains_key(*k))
125 .cloned()
126 .collect();
127
128 let left_syms = left.symbol_table();
130
131 let section_results: Vec<Result<(Vec<SymbolDiff>, Vec<AnonymousDiff>)>> = if config.parallel {
132 shared
133 .par_iter()
134 .map(|name| {
135 diff_section(
136 name,
137 &left_sections[name],
138 &right_sections[name],
139 &config.normalizer,
140 left_syms,
141 )
142 })
143 .collect()
144 } else {
145 shared
146 .iter()
147 .map(|name| {
148 diff_section(
149 name,
150 &left_sections[name],
151 &right_sections[name],
152 &config.normalizer,
153 left_syms,
154 )
155 })
156 .collect()
157 };
158
159 let mut symbol_diffs: Vec<SymbolDiff> = Vec::new();
160 let mut anonymous_diffs: Vec<AnonymousDiff> = Vec::new();
161
162 for result in section_results {
163 let (syms, anons) = result?;
164 symbol_diffs.extend(syms);
165 anonymous_diffs.extend(anons);
166 }
167
168 if !symbol_diffs.is_empty() {
170 if let Ok(dwarf) = DwarfIndex::load(left) {
171 for sd in &mut symbol_diffs {
172 sd.source_location = dwarf.resolve(sd.symbol.address).ok().flatten();
173 }
174 }
175 }
176
177 if config.show_disasm {
179 if let Some(bitness) = disasm::bitness_for(left.architecture()) {
180 for sd in &mut symbol_diffs {
181 let addr = sd.symbol.address;
182 let size = sd.symbol.size as usize;
183
184 let left_bytes = left
185 .section_data(sd.symbol.section.as_deref().unwrap_or(".text"))
186 .and_then(|(data, sec_addr, _)| {
187 let off = addr.saturating_sub(sec_addr) as usize;
188 data.get(off..off + size)
189 });
190
191 let right_bytes = right
192 .section_data(sd.symbol.section.as_deref().unwrap_or(".text"))
193 .and_then(|(data, sec_addr, _)| {
194 let off = addr.saturating_sub(sec_addr) as usize;
195 data.get(off..off + size)
196 });
197
198 if let Some(lb) = left_bytes {
199 sd.disasm_left = Some(disasm::disassemble(lb, addr, bitness));
200 }
201 if let Some(rb) = right_bytes {
202 sd.disasm_right = Some(disasm::disassemble(rb, addr, bitness));
203 }
204 }
205 }
206 }
207
208 let identical = symbol_diffs.is_empty() && anonymous_diffs.is_empty();
209
210 Ok(DiffResult {
211 left_path: left.path().to_path_buf(),
212 right_path: right.path().to_path_buf(),
213 identical,
214 symbol_diffs,
215 anonymous_diffs,
216 sections_only_in_left,
217 sections_only_in_right,
218 })
219 }
220}
221
222fn diff_section(
223 name: &str,
224 left: &(Vec<u8>, u64),
225 right: &(Vec<u8>, u64),
226 normalizer: &NormalizerChain,
227 syms: &crate::symbol::SymbolTable,
228) -> Result<(Vec<SymbolDiff>, Vec<AnonymousDiff>)> {
229 let (left_data, left_addr) = left;
230 let (right_data, _right_addr) = right;
231
232 let left_norm = normalizer.apply(name, left_data);
233 let right_norm = normalizer.apply(name, right_data);
234
235 if left_norm == right_norm {
236 return Ok((Vec::new(), Vec::new()));
237 }
238
239 let ranges = find_differing_ranges(&left_norm, &right_norm, *left_addr);
240
241 let mut by_symbol: HashMap<u64, (ResolvedSymbol, Vec<ByteRange>)> = HashMap::new();
243 let mut anon_ranges: Vec<ByteRange> = Vec::new();
244
245 for range in ranges {
246 let addr = range.offset;
247 if let Some(sym) = syms.symbol_at(addr) {
248 let entry = by_symbol
249 .entry(sym.address)
250 .or_insert_with(|| (sym.clone(), Vec::new()));
251 entry.1.push(range);
252 } else {
253 anon_ranges.push(range);
254 }
255 }
256
257 let sym_diffs: Vec<SymbolDiff> = by_symbol
258 .into_values()
259 .map(|(symbol, ranges)| SymbolDiff {
260 symbol,
261 source_location: None,
262 ranges,
263 disasm_left: None,
264 disasm_right: None,
265 })
266 .collect();
267
268 let anon_diffs = if anon_ranges.is_empty() {
269 Vec::new()
270 } else {
271 vec![AnonymousDiff { section: name.to_owned(), ranges: anon_ranges }]
272 };
273
274 Ok((sym_diffs, anon_diffs))
275}
276
277fn find_differing_ranges(left: &[u8], right: &[u8], base_addr: u64) -> Vec<ByteRange> {
280 let len = left.len().min(right.len());
281 let mut ranges: Vec<ByteRange> = Vec::new();
282 let mut i = 0;
283
284 while i < len {
285 if left[i] == right[i] {
286 i += 1;
287 continue;
288 }
289 let start = i;
291 i += 1;
292 while i < len {
293 if left[i] == right[i] {
294 let run_end = (i + MERGE_THRESHOLD).min(len);
296 if left[i..run_end] == right[i..run_end] {
297 break;
298 }
299 }
300 i += 1;
301 }
302 let end = i;
303 ranges.push(ByteRange {
304 offset: base_addr + start as u64,
305 len: end - start,
306 left_bytes: left[start..end].to_vec(),
307 right_bytes: right[start..end].to_vec(),
308 });
309 }
310
311 if left.len() != right.len() {
313 let extra_start = len;
314 let left_extra = &left[extra_start..];
315 let right_extra = &right[extra_start..];
316 if !left_extra.is_empty() || !right_extra.is_empty() {
317 ranges.push(ByteRange {
318 offset: base_addr + extra_start as u64,
319 len: left_extra.len().max(right_extra.len()),
320 left_bytes: left_extra.to_vec(),
321 right_bytes: right_extra.to_vec(),
322 });
323 }
324 }
325
326 ranges
327}