use std::collections::HashMap;
use serde::Serialize;
use crate::pclntab::Function;
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub enum MatchKind {
Identical,
AddressMoved,
Renamed,
Added,
Removed,
}
#[derive(Debug, Clone, Serialize)]
pub struct Pairing {
pub kind: MatchKind,
pub old_addr: Option<u64>,
pub new_addr: Option<u64>,
pub old_name: Option<String>,
pub new_name: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct DiffReport {
pub old_total: usize,
pub new_total: usize,
pub identical: usize,
pub renamed: usize,
pub added: usize,
pub removed: usize,
pub address_moved: usize,
pub pairings: Vec<Pairing>,
}
pub fn compute(old: &[Function], new: &[Function]) -> DiffReport {
let _old_by_addr: HashMap<u64, &Function> = old.iter().map(|f| (f.address, f)).collect();
let new_by_addr: HashMap<u64, &Function> = new.iter().map(|f| (f.address, f)).collect();
let new_by_name: HashMap<&str, &Function> = new.iter().map(|f| (f.name.as_str(), f)).collect();
let mut pairings = Vec::with_capacity(old.len().max(new.len()));
let mut matched_old: std::collections::HashSet<u64> = std::collections::HashSet::new();
let mut matched_new: std::collections::HashSet<u64> = std::collections::HashSet::new();
let mut identical = 0usize;
let mut renamed = 0usize;
let mut added = 0usize;
let mut removed = 0usize;
let mut address_moved = 0usize;
for f in old {
if let Some(g) = new_by_addr.get(&f.address) {
matched_old.insert(f.address);
matched_new.insert(g.address);
if f.name == g.name {
identical += 1;
pairings.push(Pairing {
kind: MatchKind::Identical,
old_addr: Some(f.address),
new_addr: Some(g.address),
old_name: Some(f.name.clone()),
new_name: Some(g.name.clone()),
});
} else {
address_moved += 1;
pairings.push(Pairing {
kind: MatchKind::AddressMoved,
old_addr: Some(f.address),
new_addr: Some(g.address),
old_name: Some(f.name.clone()),
new_name: Some(g.name.clone()),
});
}
}
}
for f in old {
if matched_old.contains(&f.address) {
continue;
}
if let Some(g) = new_by_name.get(f.name.as_str()) {
if matched_new.contains(&g.address) {
continue;
}
matched_old.insert(f.address);
matched_new.insert(g.address);
renamed += 1;
pairings.push(Pairing {
kind: MatchKind::Renamed,
old_addr: Some(f.address),
new_addr: Some(g.address),
old_name: Some(f.name.clone()),
new_name: Some(g.name.clone()),
});
}
}
for f in old {
if matched_old.contains(&f.address) {
continue;
}
removed += 1;
pairings.push(Pairing {
kind: MatchKind::Removed,
old_addr: Some(f.address),
new_addr: None,
old_name: Some(f.name.clone()),
new_name: None,
});
}
for f in new {
if matched_new.contains(&f.address) {
continue;
}
added += 1;
pairings.push(Pairing {
kind: MatchKind::Added,
old_addr: None,
new_addr: Some(f.address),
old_name: None,
new_name: Some(f.name.clone()),
});
}
DiffReport {
old_total: old.len(),
new_total: new.len(),
identical,
renamed,
added,
removed,
address_moved,
pairings,
}
}
pub fn write_port_script(target: crate::export::Target, report: &DiffReport) -> String {
let mut s = String::new();
let header = match target {
crate::export::Target::Ida => concat!(
"# unstrip diff: port symbols from old binary to new.\n",
"import ida_funcs, ida_name\n\n",
"def _rename(addr, name):\n",
" ida_funcs.add_func(addr)\n",
" ida_name.set_name(addr, name, ida_name.SN_FORCE | ida_name.SN_NOCHECK)\n\n",
),
crate::export::Target::Ghidra => concat!(
"# unstrip diff: port symbols from old binary to new.\n",
"# @category unstrip\n",
"from ghidra.program.model.symbol.SourceType import USER_DEFINED\n",
"fm = currentProgram.getFunctionManager()\n",
"af = currentProgram.getAddressFactory()\n\n",
"def _rename(addr, name):\n",
" a = af.getDefaultAddressSpace().getAddress(addr)\n",
" f = fm.getFunctionAt(a)\n",
" if f is None:\n",
" try: f = fm.createFunction(name, a, None, USER_DEFINED)\n",
" except: return\n",
" else:\n",
" try: f.setName(name, USER_DEFINED)\n",
" except: pass\n\n",
),
crate::export::Target::BinaryNinja => concat!(
"# unstrip diff: port symbols from old binary to new.\n",
"from binaryninja import Symbol, SymbolType\n\n",
"def _rename(addr, name):\n",
" bv.add_function(addr)\n",
" bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, name))\n\n",
),
};
s.push_str(header);
for p in &report.pairings {
match p.kind {
MatchKind::Identical | MatchKind::Renamed => {
if let (Some(addr), Some(name)) = (p.new_addr, &p.old_name) {
s.push_str(&format!("_rename(0x{addr:x}, {})\n", quote_py(name)));
}
}
_ => {}
}
}
s.push_str(&format!(
"\nprint('unstrip: ported {} symbols ({} unchanged, {} renamed, {} added in new, {} removed from old)')\n",
report.identical + report.renamed,
report.identical,
report.renamed,
report.added,
report.removed,
));
s
}
fn quote_py(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\'' => out.push_str("\\'"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\x{:02x}", c as u32)),
c => out.push(c),
}
}
out.push('\'');
out
}