use crate::model::{SymKind, Symbol};
use pdb::FallibleIterator;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "state", rename_all = "lowercase")]
pub enum Pdb {
NotReferenced,
Missing { wanted: String },
Mismatch { path: String },
Loaded { path: String, symbols: usize },
Failed { path: String, why: String },
}
impl Pdb {
pub fn summary(&self) -> String {
match self {
Pdb::NotReferenced => "none referenced".into(),
Pdb::Missing { wanted } => format!("{wanted} not found"),
Pdb::Mismatch { path } => format!("{path} is from another build, ignored"),
Pdb::Loaded { path, symbols } => format!("{symbols} names from {path}"),
Pdb::Failed { path, why } => format!("{path} unreadable: {why}"),
}
}
pub fn is_loaded(&self) -> bool {
matches!(self, Pdb::Loaded { .. })
}
}
static OVERRIDE: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub fn set_override(path: Option<String>) {
*OVERRIDE.lock().unwrap() = path;
}
pub fn override_path() -> Option<String> {
OVERRIDE.lock().unwrap().clone()
}
pub struct Wanted {
pub name: String,
pub guid: [u8; 16],
pub age: u32,
}
fn candidates(binary: &Path, wanted: &Wanted, explicit: Option<&str>) -> Vec<PathBuf> {
if let Some(p) = explicit {
return vec![PathBuf::from(p)];
}
let mut out = Vec::new();
let stem = Path::new(&wanted.name)
.file_name()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(&wanted.name));
if let Some(dir) = binary.parent() {
out.push(dir.join(&stem));
}
out.push(stem);
out
}
pub fn load(binary: &str, wanted: Option<Wanted>, explicit: Option<&str>) -> (Vec<Symbol>, Pdb) {
let Some(wanted) = wanted else {
return (Vec::new(), Pdb::NotReferenced);
};
let binary = Path::new(binary);
for path in candidates(binary, &wanted, explicit) {
if !path.exists() {
continue;
}
let shown = path.display().to_string();
return match read(&path, &wanted) {
Ok(Some(symbols)) => {
let n = symbols.len();
(
symbols,
Pdb::Loaded {
path: shown,
symbols: n,
},
)
}
Ok(None) => (Vec::new(), Pdb::Mismatch { path: shown }),
Err(why) => (Vec::new(), Pdb::Failed { path: shown, why }),
};
}
(
Vec::new(),
Pdb::Missing {
wanted: wanted.name,
},
)
}
fn read(path: &Path, wanted: &Wanted) -> Result<Option<Vec<Symbol>>, String> {
let file = std::fs::File::open(path).map_err(|e| e.to_string())?;
let mut pdb = pdb::PDB::open(file).map_err(|e| e.to_string())?;
let info = pdb.pdb_information().map_err(|e| e.to_string())?;
let guid_ok = info.guid.to_bytes_le() == wanted.guid;
let pdb_age = pdb
.debug_information()
.ok()
.and_then(|d| d.age())
.unwrap_or(info.age);
if !guid_ok || pdb_age < wanted.age {
return Ok(None);
}
let address_map = pdb.address_map().map_err(|e| e.to_string())?;
let globals = pdb.global_symbols().map_err(|e| e.to_string())?;
let mut out = Vec::new();
let mut iter = globals.iter();
while let Some(sym) = iter.next().map_err(|e| e.to_string())? {
if let Ok(pdb::SymbolData::Public(data)) = sym.parse() {
if !data.function {
continue;
}
if let Some(rva) = data.offset.to_rva(&address_map) {
out.push(Symbol {
addr: u64::from(rva.0),
name: data.name.to_string().into_owned(),
kind: SymKind::Func,
});
}
}
}
Ok(Some(out))
}