summaryrefslogtreecommitdiff
path: root/plugins/pdb-ng/src/parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/pdb-ng/src/parser.rs')
-rw-r--r--plugins/pdb-ng/src/parser.rs505
1 files changed, 505 insertions, 0 deletions
diff --git a/plugins/pdb-ng/src/parser.rs b/plugins/pdb-ng/src/parser.rs
new file mode 100644
index 00000000..00b44e70
--- /dev/null
+++ b/plugins/pdb-ng/src/parser.rs
@@ -0,0 +1,505 @@
+// Copyright 2022-2024 Vector 35 Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::collections::{BTreeMap, HashMap, HashSet};
+use std::env;
+use std::fmt::Display;
+use std::sync::OnceLock;
+
+use anyhow::{anyhow, Result};
+use log::{debug, info};
+use pdb::*;
+
+use crate::symbol_parser::{ParsedDataSymbol, ParsedProcedure, ParsedSymbol};
+use crate::type_parser::ParsedType;
+use binaryninja::architecture::{Architecture, CoreArchitecture};
+use binaryninja::binary_view::{BinaryView, BinaryViewExt};
+use binaryninja::calling_convention::CoreCallingConvention;
+use binaryninja::confidence::{Conf, MIN_CONFIDENCE};
+use binaryninja::debuginfo::{DebugFunctionInfo, DebugInfo};
+use binaryninja::platform::Platform;
+use binaryninja::rc::Ref;
+use binaryninja::settings::{QueryOptions, Settings};
+use binaryninja::types::{
+ EnumerationBuilder, NamedTypeReference, NamedTypeReferenceClass, QualifiedName,
+ StructureBuilder, StructureType, Type, TypeClass,
+};
+use binaryninja::variable::NamedDataVariableWithType;
+
+/// Megastruct for all the parsing
+/// Certain fields are only used by specific files, as marked below.
+/// Why not make new structs for them? Because vvvv this garbage
+pub struct PDBParserInstance<'a, S: Source<'a> + 'a> {
+ /// DebugInfo where types/functions will be stored eventually
+ pub(crate) debug_info: &'a mut DebugInfo,
+ /// Parent binary view (usually during BinaryView::Finalize)
+ pub(crate) bv: &'a BinaryView,
+ /// Default arch of self.bv
+ pub(crate) arch: CoreArchitecture,
+ /// Default calling convention for self.arch
+ pub(crate) default_cc: Ref<CoreCallingConvention>,
+ /// Thiscall calling convention for self.bv, or default_cc if we can't find one
+ pub(crate) thiscall_cc: Ref<CoreCallingConvention>,
+ /// Cdecl calling convention for self.bv, or default_cc if we can't find one
+ pub(crate) cdecl_cc: Ref<CoreCallingConvention>,
+ /// Default platform of self.bv
+ pub(crate) platform: Ref<Platform>,
+ /// pdb-rs structure for making lifetime hell a real place
+ pub(crate) pdb: PDB<'a, S>,
+ /// pdb-rs Mapping of modules to addresses for resolving RVAs
+ pub(crate) address_map: AddressMap<'a>,
+ /// Binja Settings instance (for optimization)
+ pub(crate) settings: Ref<Settings>,
+ /// Binja Settings query instance (for optimization)
+ pub(crate) settings_query_opts: QueryOptions<'a>,
+
+ /// type_parser.rs
+
+ /// TypeIndex -> ParsedType enum used during parsing
+ pub(crate) indexed_types: BTreeMap<TypeIndex, ParsedType>,
+ /// QName -> Binja Type for finished types
+ pub(crate) named_types: BTreeMap<QualifiedName, Ref<Type>>,
+ /// Raw (mangled) name -> TypeIndex for resolving forward references
+ pub(crate) full_type_indices: BTreeMap<String, TypeIndex>,
+ /// Stack of types we're currently parsing
+ pub(crate) type_stack: Vec<TypeIndex>,
+ /// Stack of parent types we're parsing nested types inside of
+ pub(crate) namespace_stack: QualifiedName,
+ /// Type Index -> Does it return on the stack
+ pub(crate) type_default_returnable: BTreeMap<TypeIndex, bool>,
+
+ /// symbol_parser.rs
+
+ /// List of fully parsed symbols from all modules
+ pub(crate) parsed_symbols: Vec<ParsedSymbol>,
+ /// Raw name -> index in parsed_symbols
+ pub(crate) parsed_symbols_by_name: BTreeMap<String, usize>,
+ /// Raw name -> Symbol index for looking up symbols for the currently parsing module (mostly for thunks)
+ pub(crate) named_symbols: BTreeMap<String, SymbolIndex>,
+ /// Parent -> Children symbol index tree for the currently parsing module
+ pub(crate) symbol_tree: BTreeMap<SymbolIndex, Vec<SymbolIndex>>,
+ /// Child -> Parent symbol index mapping, inverse of symbol_tree
+ pub(crate) symbol_parents: BTreeMap<SymbolIndex, SymbolIndex>,
+ /// Stack of (start, end) indices for the current symbols being parsed while constructing the tree
+ pub(crate) symbol_stack: Vec<(SymbolIndex, SymbolIndex)>,
+ /// Index -> parsed symbol for the currently parsing module
+ pub(crate) indexed_symbols: BTreeMap<SymbolIndex, ParsedSymbol>,
+ /// Symbol address -> Symbol for looking up by address
+ pub(crate) addressed_symbols: BTreeMap<u64, Vec<ParsedSymbol>>,
+ /// CPU type of the currently parsing module
+ pub(crate) module_cpu_type: Option<CPUType>,
+}
+
+impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> {
+ /// Try to create a new parser instance from a given bv/pdb
+ pub fn new(
+ debug_info: &'a mut DebugInfo,
+ bv: &'a BinaryView,
+ mut pdb: PDB<'a, S>,
+ ) -> Result<Self> {
+ let arch = if let Some(arch) = bv.default_arch() {
+ arch
+ } else {
+ return Err(anyhow!("Cannot parse to view with no architecture"));
+ };
+
+ let platform = bv
+ .default_platform()
+ .expect("Expected bv to have a platform");
+
+ let address_map = pdb.address_map()?;
+
+ let default_cc = platform
+ .get_default_calling_convention()
+ .expect("Expected default calling convention");
+
+ let thiscall_cc = Self::find_calling_convention(platform.as_ref(), "thiscall")
+ .unwrap_or(default_cc.clone());
+
+ let cdecl_cc = platform
+ .get_cdecl_calling_convention()
+ .unwrap_or(default_cc.clone());
+
+ Ok(Self {
+ debug_info,
+ bv,
+ arch,
+ default_cc,
+ thiscall_cc,
+ cdecl_cc,
+ platform,
+ pdb,
+ address_map,
+ settings: Settings::new(),
+ settings_query_opts: QueryOptions::new_with_view(bv),
+ indexed_types: Default::default(),
+ named_types: Default::default(),
+ full_type_indices: Default::default(),
+ type_stack: Default::default(),
+ namespace_stack: Default::default(),
+ type_default_returnable: Default::default(),
+ parsed_symbols: Default::default(),
+ parsed_symbols_by_name: Default::default(),
+ named_symbols: Default::default(),
+ symbol_tree: Default::default(),
+ symbol_parents: Default::default(),
+ symbol_stack: Default::default(),
+ indexed_symbols: Default::default(),
+ addressed_symbols: Default::default(),
+ module_cpu_type: None,
+ })
+ }
+
+ /// Try to parse the pdb into the DebugInfo
+ pub fn try_parse_info(
+ &mut self,
+ progress: Box<dyn Fn(usize, usize) -> Result<()> + 'a>,
+ ) -> Result<()> {
+ self.parse_types(Self::split_progress(&progress, 0, &[1.0, 3.0, 0.5, 0.5]))?;
+ for (name, ty) in self.named_types.iter() {
+ self.debug_info.add_type(name, ty.as_ref(), &[]); // TODO : Components
+ }
+
+ info!(
+ "PDB found {} types (before resolving NTRs)",
+ self.named_types.len()
+ );
+
+ if self
+ .settings
+ .get_bool_with_opts("pdb.features.parseSymbols", &mut self.settings_query_opts)
+ {
+ let (symbols, functions) =
+ self.parse_symbols(Self::split_progress(&progress, 1, &[1.0, 3.0, 0.5, 0.5]))?;
+
+ if self.settings.get_bool_with_opts(
+ "pdb.features.createMissingNamedTypes",
+ &mut self.settings_query_opts,
+ ) {
+ self.resolve_missing_ntrs(
+ &symbols,
+ Self::split_progress(&progress, 2, &[1.0, 3.0, 0.5, 0.5]),
+ )?;
+ self.resolve_missing_ntrs(
+ &functions,
+ Self::split_progress(&progress, 3, &[1.0, 3.0, 0.5, 0.5]),
+ )?;
+ }
+
+ info!("PDB found {} types", self.named_types.len());
+ info!("PDB found {} data variables", symbols.len());
+ info!("PDB found {} functions", functions.len());
+
+ let allow_void = self.settings.get_bool_with_opts(
+ "pdb.features.allowVoidGlobals",
+ &mut self.settings_query_opts,
+ );
+
+ let min_confidence_type = Conf::new(Type::void(), MIN_CONFIDENCE);
+ for sym in symbols {
+ match sym {
+ ParsedSymbol::Data(ParsedDataSymbol {
+ address,
+ name,
+ type_,
+ ..
+ }) => {
+ let real_type = type_.as_ref().unwrap_or(&min_confidence_type);
+
+ if real_type.contents.type_class() == TypeClass::VoidTypeClass {
+ if !allow_void {
+ self.log(|| {
+ format!("Not adding void-typed symbol {:?}@{:x}", name, address)
+ });
+ continue;
+ }
+ }
+
+ self.log(|| {
+ format!(
+ "Adding data variable: 0x{:x}: {} {:?}",
+ address, &name.raw_name, real_type
+ )
+ });
+ self.debug_info
+ .add_data_variable_info(NamedDataVariableWithType::new(
+ address,
+ real_type.clone(),
+ name.full_name.unwrap_or(name.raw_name),
+ true,
+ ));
+ }
+ s => {
+ self.log(|| format!("Not adding non-data symbol {:?}", s));
+ }
+ }
+ }
+
+ for sym in functions {
+ match sym {
+ ParsedSymbol::Procedure(ParsedProcedure {
+ address,
+ name,
+ type_,
+ locals: _,
+ ..
+ }) => {
+ self.log(|| {
+ format!(
+ "Adding function: 0x{:x}: {} {:?}",
+ address, &name.raw_name, type_
+ )
+ });
+ self.debug_info.add_function(DebugFunctionInfo::new(
+ Some(name.short_name.unwrap_or(name.raw_name.clone())),
+ Some(name.full_name.unwrap_or(name.raw_name.clone())),
+ Some(name.raw_name),
+ type_.clone().and_then(|conf| {
+ // TODO: When DebugInfo support confidence on function types, remove this
+ if conf.confidence == 0 {
+ None
+ } else {
+ Some(conf.contents)
+ }
+ }),
+ Some(address),
+ Some(self.platform.clone()),
+ vec![], // TODO : Components
+ vec![], //TODO: local variables
+ ));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn collect_name(
+ &self,
+ name: &NamedTypeReference,
+ unknown_names: &mut HashMap<QualifiedName, NamedTypeReferenceClass>,
+ ) {
+ let used_name = name.name();
+ if let Some(&found) = unknown_names.get(&used_name) {
+ if found != name.class() {
+ // Interesting case, not sure we care
+ self.log(|| {
+ format!(
+ "Mismatch unknown NTR class for {}: {} ?",
+ &used_name,
+ name.class() as u32
+ )
+ });
+ }
+ } else {
+ self.log(|| format!("Found new unused name: {}", &used_name));
+ unknown_names.insert(used_name, name.class());
+ }
+ }
+
+ fn collect_names(
+ &self,
+ ty: &Type,
+ unknown_names: &mut HashMap<QualifiedName, NamedTypeReferenceClass>,
+ ) {
+ match ty.type_class() {
+ TypeClass::StructureTypeClass => {
+ if let Some(structure) = ty.get_structure() {
+ for member in structure.members() {
+ self.collect_names(member.ty.contents.as_ref(), unknown_names);
+ }
+ for base in structure.base_structures() {
+ self.collect_name(base.ty.as_ref(), unknown_names);
+ }
+ }
+ }
+ TypeClass::PointerTypeClass => {
+ if let Some(target) = ty.target() {
+ self.collect_names(target.contents.as_ref(), unknown_names);
+ }
+ }
+ TypeClass::ArrayTypeClass => {
+ if let Some(element_type) = ty.element_type() {
+ self.collect_names(element_type.contents.as_ref(), unknown_names);
+ }
+ }
+ TypeClass::FunctionTypeClass => {
+ if let Some(return_value) = ty.return_value() {
+ self.collect_names(return_value.contents.as_ref(), unknown_names);
+ }
+ if let Some(params) = ty.parameters() {
+ for param in params {
+ self.collect_names(param.ty.contents.as_ref(), unknown_names);
+ }
+ }
+ }
+ TypeClass::NamedTypeReferenceClass => {
+ if let Some(ntr) = ty.get_named_type_reference() {
+ self.collect_name(ntr.as_ref(), unknown_names);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn resolve_missing_ntrs(
+ &mut self,
+ symbols: &Vec<ParsedSymbol>,
+ progress: Box<dyn Fn(usize, usize) -> Result<()> + '_>,
+ ) -> Result<()> {
+ let mut unknown_names = HashMap::new();
+ let mut known_names = self
+ .bv
+ .types()
+ .iter()
+ .map(|qnat| qnat.name)
+ .collect::<HashSet<_>>();
+
+ for ty in &self.named_types {
+ known_names.insert(ty.0.clone());
+ }
+
+ let count = symbols.len();
+ for (i, sym) in symbols.iter().enumerate() {
+ match sym {
+ ParsedSymbol::Data(ParsedDataSymbol {
+ type_: Some(type_), ..
+ }) => {
+ self.collect_names(type_.contents.as_ref(), &mut unknown_names);
+ }
+ ParsedSymbol::Procedure(ParsedProcedure {
+ type_: Some(type_),
+ locals,
+ ..
+ }) => {
+ self.collect_names(type_.contents.as_ref(), &mut unknown_names);
+ for l in locals {
+ if let Some(ltype) = &l.type_ {
+ self.collect_names(ltype.contents.as_ref(), &mut unknown_names);
+ }
+ }
+ }
+ _ => {}
+ }
+ progress(i, count)?;
+ }
+
+ for (name, class) in unknown_names.into_iter() {
+ if known_names.contains(&name) {
+ self.log(|| format!("Found referenced name and ignoring: {}", &name));
+ continue;
+ }
+ self.log(|| format!("Adding referenced but unknown type {} (likely due to demangled name and stripped type)", &name));
+ match class {
+ NamedTypeReferenceClass::UnknownNamedTypeClass
+ | NamedTypeReferenceClass::TypedefNamedTypeClass => {
+ self.debug_info.add_type(&name, Type::void().as_ref(), &[]);
+ // TODO : Components
+ }
+ NamedTypeReferenceClass::ClassNamedTypeClass
+ | NamedTypeReferenceClass::StructNamedTypeClass
+ | NamedTypeReferenceClass::UnionNamedTypeClass => {
+ let mut structure = StructureBuilder::new();
+ match class {
+ NamedTypeReferenceClass::ClassNamedTypeClass => {
+ structure.structure_type(StructureType::ClassStructureType);
+ }
+ NamedTypeReferenceClass::StructNamedTypeClass => {
+ structure.structure_type(StructureType::StructStructureType);
+ }
+ NamedTypeReferenceClass::UnionNamedTypeClass => {
+ structure.structure_type(StructureType::UnionStructureType);
+ }
+ _ => {}
+ }
+ structure.width(1);
+ structure.alignment(1);
+
+ self.debug_info.add_type(
+ &name,
+ Type::structure(structure.finalize().as_ref()).as_ref(),
+ &[], // TODO : Components
+ );
+ }
+ NamedTypeReferenceClass::EnumNamedTypeClass => {
+ let enumeration = EnumerationBuilder::new();
+ self.debug_info.add_type(
+ &name,
+ Type::enumeration(
+ enumeration.finalize().as_ref(),
+ self.arch.default_integer_size().try_into()?,
+ false,
+ )
+ .as_ref(),
+ &[], // TODO : Components
+ );
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Lazy logging function that prints like 20MB of messages
+ pub(crate) fn log<F: FnOnce() -> D, D: Display>(&self, msg: F) {
+ static MEM: OnceLock<bool> = OnceLock::new();
+ let debug_pdb = MEM.get_or_init(|| env::var("BN_DEBUG_PDB").is_ok());
+ if *debug_pdb {
+ let space = "\t".repeat(self.type_stack.len()) + &"\t".repeat(self.symbol_stack.len());
+ let msg = format!("{}", msg());
+ debug!(
+ "{}{}",
+ space,
+ msg.replace("\n", &("\n".to_string() + &space))
+ );
+ }
+ }
+
+ pub(crate) fn split_progress<'b, F: Fn(usize, usize) -> Result<()> + 'b>(
+ original_fn: F,
+ subpart: usize,
+ subpart_weights: &[f64],
+ ) -> Box<dyn Fn(usize, usize) -> Result<()> + 'b> {
+ // Normalize weights
+ let weight_sum: f64 = subpart_weights.iter().sum();
+ if weight_sum < 0.0001 {
+ return Box::new(|_, _| Ok(()));
+ }
+
+ // Keep a running count of weights for the start
+ let mut subpart_starts = vec![];
+ let mut start = 0f64;
+ for w in subpart_weights {
+ subpart_starts.push(start);
+ start += *w;
+ }
+
+ let subpart_start = subpart_starts[subpart] / weight_sum;
+ let weight = subpart_weights[subpart] / weight_sum;
+
+ Box::new(move |cur: usize, max: usize| {
+ // Just use a large number for easy divisibility
+ let steps = 1000000f64;
+ let subpart_size = steps * weight;
+ let subpart_progress = ((cur as f64) / (max as f64)) * subpart_size;
+
+ original_fn(
+ (subpart_start * steps + subpart_progress) as usize,
+ steps as usize,
+ )
+ })
+ }
+}