diff --git a/Cargo.lock b/Cargo.lock index 4d45812..d517fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2279,6 +2279,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "msvc-demangler" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2588c982e3a7fbfbd73b21f824cacc43fc6392a1103c709ffd6001c0bf33fdb3" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "naga" version = "0.19.2" @@ -2993,6 +3002,7 @@ dependencies = [ "insta", "instant", "log", + "msvc-demangler", "num_cpus", "pdb", "rayon", diff --git a/resym/src/resym_app.rs b/resym/src/resym_app.rs index 2bb99ca..8250ecd 100644 --- a/resym/src/resym_app.rs +++ b/resym/src/resym_app.rs @@ -3,7 +3,8 @@ use eframe::egui; use memory_logger::blocking::MemoryLogger; use resym_core::{ backend::{Backend, BackendCommand, PDBSlot}, - frontend::{FrontendCommand, TypeIndex}, + frontend::FrontendCommand, + pdb_file::{SymbolIndex, TypeIndex}, }; #[cfg(target_arch = "wasm32")] @@ -18,8 +19,8 @@ use crate::{ module_tree::{ModuleInfo, ModulePath}, settings::ResymAppSettings, ui_components::{ - CodeViewComponent, ConsoleComponent, ModuleTreeComponent, SettingsComponent, - TextSearchComponent, TypeListComponent, TypeListOrdering, + CodeViewComponent, ConsoleComponent, IndexListComponent, IndexListOrdering, + ModuleTreeComponent, SettingsComponent, TextSearchComponent, }, }; @@ -44,6 +45,7 @@ impl From for PDBSlot { #[derive(PartialEq)] enum LeftPanelTab { TypeSearch, + SymbolSearch, ModuleBrowsing, } @@ -62,18 +64,21 @@ pub struct ResymApp { // Components used in the left-side panel left_panel_selected_tab: LeftPanelTab, type_search: TextSearchComponent, - type_list: TypeListComponent, + type_list: IndexListComponent, + selected_type_index: Option, + symbol_search: TextSearchComponent, + symbol_list: IndexListComponent, + selected_symbol_index: Option, module_search: TextSearchComponent, module_tree: ModuleTreeComponent, code_view: CodeViewComponent, // Components used in the bottom panel bottom_panel_selected_tab: BottomPanelTab, console: ConsoleComponent, - xref_to_list: TypeListComponent, - xref_from_list: TypeListComponent, + xref_to_list: IndexListComponent, + xref_from_list: IndexListComponent, // Other components settings: SettingsComponent, - selected_type_index: Option, #[cfg(feature = "http")] open_url: OpenURLComponent, frontend_controller: Arc, @@ -159,15 +164,18 @@ impl ResymApp { current_mode: ResymAppMode::Idle, left_panel_selected_tab: LeftPanelTab::TypeSearch, type_search: TextSearchComponent::new(), - type_list: TypeListComponent::new(TypeListOrdering::Alphabetical), + type_list: IndexListComponent::new(IndexListOrdering::Alphabetical), + selected_type_index: None, + symbol_search: TextSearchComponent::new(), + symbol_list: IndexListComponent::new(IndexListOrdering::Alphabetical), + selected_symbol_index: None, module_search: TextSearchComponent::new(), module_tree: ModuleTreeComponent::new(), code_view: CodeViewComponent::new(), bottom_panel_selected_tab: BottomPanelTab::Console, console: ConsoleComponent::new(logger), - xref_to_list: TypeListComponent::new(TypeListOrdering::Alphabetical), - xref_from_list: TypeListComponent::new(TypeListOrdering::Alphabetical), - selected_type_index: None, + xref_to_list: IndexListComponent::new(IndexListOrdering::Alphabetical), + xref_from_list: IndexListComponent::new(IndexListOrdering::Alphabetical), settings: SettingsComponent::new(app_settings), #[cfg(feature = "http")] open_url: OpenURLComponent::new(), @@ -209,6 +217,11 @@ impl ResymApp { LeftPanelTab::TypeSearch, "Search types", ); + ui.selectable_value( + &mut self.left_panel_selected_tab, + LeftPanelTab::SymbolSearch, + "Search symbols", + ); ui.selectable_value( &mut self.left_panel_selected_tab, LeftPanelTab::ModuleBrowsing, @@ -296,6 +309,84 @@ impl ResymApp { // Update the type list self.type_list.update(ui, &mut on_type_selected); } + + LeftPanelTab::SymbolSearch => { + // Callback run when the search query changes + let on_query_update = |search_query: &str| { + // Update filtered list if filter has changed + let result = if let ResymAppMode::Comparing(..) = self.current_mode { + self.backend.send_command(BackendCommand::ListSymbolsMerged( + vec![ + ResymPDBSlots::Main as usize, + ResymPDBSlots::Diff as usize, + ], + search_query.to_string(), + self.settings.app_settings.search_case_insensitive, + self.settings.app_settings.search_use_regex, + self.settings.app_settings.ignore_std_types, + )) + } else { + self.backend.send_command(BackendCommand::ListSymbols( + ResymPDBSlots::Main as usize, + search_query.to_string(), + self.settings.app_settings.search_case_insensitive, + self.settings.app_settings.search_use_regex, + self.settings.app_settings.ignore_std_types, + )) + }; + if let Err(err) = result { + log::error!("Failed to update type filter value: {}", err); + } + }; + + // Update the symbol search bar + ui.label("Search"); + self.symbol_search.update(ui, &on_query_update); + ui.separator(); + ui.add_space(4.0); + + // Callback run when a type is selected in the list + let mut on_symbol_selected = + |symbol_name: &str, symbol_index: SymbolIndex| { + // Update currently selected type index + self.selected_symbol_index = Some(symbol_index); + + match self.current_mode { + ResymAppMode::Browsing(..) => { + if let Err(err) = self.backend.send_command( + BackendCommand::ReconstructSymbolByIndex( + ResymPDBSlots::Main as usize, + symbol_index, + self.settings.app_settings.primitive_types_flavor, + self.settings.app_settings.print_header, + self.settings.app_settings.print_access_specifiers, + ), + ) { + log::error!("Failed to reconstruct type: {}", err); + } + } + ResymAppMode::Comparing(..) => { + if let Err(err) = self.backend.send_command( + BackendCommand::DiffSymbolByName( + ResymPDBSlots::Main as usize, + ResymPDBSlots::Diff as usize, + symbol_name.to_string(), + self.settings.app_settings.primitive_types_flavor, + self.settings.app_settings.print_header, + self.settings.app_settings.print_access_specifiers, + ), + ) { + log::error!("Failed to reconstruct type diff: {}", err); + } + } + _ => log::error!("Invalid application state"), + } + }; + + // Update the symbol list + self.symbol_list.update(ui, &mut on_symbol_selected); + } + LeftPanelTab::ModuleBrowsing => { // Callback run when the search query changes let on_query_update = |search_query: &str| match self.current_mode { @@ -332,6 +423,7 @@ impl ResymApp { module_info.pdb_index, self.settings.app_settings.primitive_types_flavor, self.settings.app_settings.print_header, + self.settings.app_settings.print_access_specifiers, ), ) { log::error!("Failed to reconstruct module: {}", err); @@ -346,6 +438,7 @@ impl ResymApp { module_path.to_string(), self.settings.app_settings.primitive_types_flavor, self.settings.app_settings.print_header, + self.settings.app_settings.print_access_specifiers, )) { log::error!("Failed to reconstruct type diff: {}", err); @@ -557,8 +650,8 @@ impl ResymApp { // Reset selected type self.selected_type_index = None; // Reset xref lists - self.xref_to_list.update_type_list(vec![]); - self.xref_from_list.update_type_list(vec![]); + self.xref_to_list.update_index_list(vec![]); + self.xref_from_list.update_index_list(vec![]); // Request a type list update if let Err(err) = self.backend.send_command(BackendCommand::ListTypes( @@ -570,6 +663,18 @@ impl ResymApp { )) { log::error!("Failed to update type filter value: {}", err); } + // Request a symbol list update + if let Err(err) = + self.backend.send_command(BackendCommand::ListSymbols( + ResymPDBSlots::Main as usize, + String::default(), + false, + false, + self.settings.app_settings.ignore_std_types, + )) + { + log::error!("Failed to update type filter value: {}", err); + } // Request a module list update if let Err(err) = self.backend.send_command(BackendCommand::ListModules( @@ -593,8 +698,8 @@ impl ResymApp { // Reset selected type self.selected_type_index = None; // Reset xref lists - self.xref_to_list.update_type_list(vec![]); - self.xref_from_list.update_type_list(vec![]); + self.xref_to_list.update_index_list(vec![]); + self.xref_from_list.update_index_list(vec![]); // Request a type list update if let Err(err) = @@ -653,22 +758,50 @@ impl ResymApp { ); // Update xref lists - self.xref_to_list.update_type_list(vec![]); - self.xref_from_list.update_type_list(xrefs_from); + self.xref_to_list.update_index_list(vec![]); + self.xref_from_list.update_index_list(xrefs_from); // Switch to the "xref from" tab self.bottom_panel_selected_tab = BottomPanelTab::XRefsFrom; } } } - FrontendCommand::UpdateModuleList(module_list_result) => match module_list_result { - Err(err) => { - log::error!("Failed to retrieve module list: {}", err); + FrontendCommand::ListModulesResult(module_list_result) => { + match module_list_result { + Err(err) => { + log::error!("Failed to retrieve module list: {}", err); + } + Ok(module_list) => { + self.module_tree.set_module_list(module_list); + } } - Ok(module_list) => { - self.module_tree.set_module_list(module_list); + } + + FrontendCommand::ReconstructSymbolResult(result) => { + match result { + Err(err) => { + let error_msg = format!("Failed to reconstruct symbol: {}", err); + log::error!("{}", &error_msg); + + // Show an empty "reconstruted" view + self.current_mode = + ResymAppMode::Browsing(Default::default(), 0, error_msg); + } + Ok(reconstructed_symbol) => { + let last_line_number = 1 + reconstructed_symbol.lines().count(); + let line_numbers = + (1..last_line_number).fold(String::default(), |mut acc, e| { + let _r = writeln!(&mut acc, "{e}"); + acc + }); + self.current_mode = ResymAppMode::Browsing( + line_numbers, + last_line_number, + reconstructed_symbol, + ); + } } - }, + } FrontendCommand::ReconstructModuleResult(module_reconstruction_result) => { match module_reconstruction_result { @@ -752,7 +885,12 @@ impl ResymApp { FrontendCommand::ListTypesResult(filtered_types) => { // Update type list component - self.type_list.update_type_list(filtered_types); + self.type_list.update_index_list(filtered_types); + } + + FrontendCommand::ListSymbolsResult(filtered_symbols) => { + // Update symbol list component + self.symbol_list.update_index_list(filtered_symbols); } FrontendCommand::ListTypeCrossReferencesResult(xref_list_result) => { @@ -765,7 +903,7 @@ impl ResymApp { log::info!("{xref_count} cross-references found!"); // Update xref list component - self.xref_to_list.update_type_list(xref_list); + self.xref_to_list.update_index_list(xref_list); // Switch to xref tab self.bottom_panel_selected_tab = BottomPanelTab::XRefsTo; } @@ -883,7 +1021,7 @@ impl ResymApp { fn list_xrefs_for_type(&self, type_index: TypeIndex) { log::info!( "Looking for cross-references for type #0x{:x}...", - type_index.0 + type_index ); if let Err(err) = self .backend @@ -894,7 +1032,7 @@ impl ResymApp { { log::error!( "Failed to list cross-references to type #0x{:x}: {err}", - type_index.0 + type_index ); } } diff --git a/resym/src/ui_components/type_list.rs b/resym/src/ui_components/index_list.rs similarity index 64% rename from resym/src/ui_components/type_list.rs rename to resym/src/ui_components/index_list.rs index a8346c0..0eb0d05 100644 --- a/resym/src/ui_components/type_list.rs +++ b/resym/src/ui_components/index_list.rs @@ -1,45 +1,40 @@ use eframe::egui::{self, ScrollArea, TextStyle}; -use resym_core::frontend::{TypeIndex, TypeList}; -pub struct TypeListComponent { - filtered_type_list: TypeList, +pub struct IndexListComponent { + index_list: Vec<(String, I)>, selected_row: usize, - list_ordering: TypeListOrdering, + list_ordering: IndexListOrdering, } -pub enum TypeListOrdering { +pub enum IndexListOrdering { /// Doesn't respect any particular order None, /// Orders types alphabetically Alphabetical, } -impl TypeListComponent { - pub fn new(ordering: TypeListOrdering) -> Self { +impl IndexListComponent { + pub fn new(ordering: IndexListOrdering) -> Self { Self { - filtered_type_list: vec![], + index_list: vec![], selected_row: usize::MAX, list_ordering: ordering, } } - pub fn update_type_list(&mut self, type_list: TypeList) { - self.filtered_type_list = type_list; + pub fn update_index_list(&mut self, index_list: Vec<(String, I)>) { + self.index_list = index_list; self.selected_row = usize::MAX; // Reorder list if needed - if let TypeListOrdering::Alphabetical = self.list_ordering { - self.filtered_type_list + if let IndexListOrdering::Alphabetical = self.list_ordering { + self.index_list .sort_unstable_by(|lhs, rhs| lhs.0.cmp(&rhs.0)); } } - pub fn update( - &mut self, - ui: &mut egui::Ui, - on_type_selected: &mut CB, - ) { - let num_rows = self.filtered_type_list.len(); + pub fn update(&mut self, ui: &mut egui::Ui, on_element_selected: &mut CB) { + let num_rows = self.index_list.len(); const TEXT_STYLE: TextStyle = TextStyle::Body; let row_height = ui.text_style_height(&TEXT_STYLE); ui.with_layout( @@ -55,14 +50,14 @@ impl TypeListComponent { .auto_shrink([false, false]) .show_rows(ui, row_height, num_rows, |ui, row_range| { for row_index in row_range { - let (type_name, type_index) = &self.filtered_type_list[row_index]; + let (type_name, type_index) = &self.index_list[row_index]; if ui .selectable_label(self.selected_row == row_index, type_name) .clicked() { self.selected_row = row_index; - on_type_selected(type_name, *type_index); + on_element_selected(type_name, *type_index); } } }); @@ -71,8 +66,8 @@ impl TypeListComponent { } } -impl Default for TypeListComponent { +impl Default for IndexListComponent { fn default() -> Self { - Self::new(TypeListOrdering::None) + Self::new(IndexListOrdering::None) } } diff --git a/resym/src/ui_components/mod.rs b/resym/src/ui_components/mod.rs index 3f10b59..2874d76 100644 --- a/resym/src/ui_components/mod.rs +++ b/resym/src/ui_components/mod.rs @@ -1,17 +1,17 @@ mod code_view; mod console; +mod index_list; mod module_tree; #[cfg(feature = "http")] mod open_url; mod settings; mod text_search; -mod type_list; pub use code_view::*; pub use console::*; +pub use index_list::*; pub use module_tree::*; #[cfg(feature = "http")] pub use open_url::*; pub use settings::*; pub use text_search::*; -pub use type_list::*; diff --git a/resym/src/ui_components/module_tree.rs b/resym/src/ui_components/module_tree.rs index 7149dc1..17c5520 100644 --- a/resym/src/ui_components/module_tree.rs +++ b/resym/src/ui_components/module_tree.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use eframe::egui::{self, ScrollArea}; -use resym_core::frontend::ModuleList; +use resym_core::pdb_file::ModuleList; use crate::{ module_tree::{ModuleInfo, ModulePath, ModuleTreeNode}, diff --git a/resym_core/Cargo.toml b/resym_core/Cargo.toml index 72d186b..32b9263 100644 --- a/resym_core/Cargo.toml +++ b/resym_core/Cargo.toml @@ -26,6 +26,7 @@ regex = "1.10" similar = "2.4" ehttp = { version = "0.5", optional = true } url = { version = "2.5", optional = true } +msvc-demangler = "0.10" # Web: [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/resym_core/src/backend.rs b/resym_core/src/backend.rs index 12df8ee..f6d31bd 100644 --- a/resym_core/src/backend.rs +++ b/resym_core/src/backend.rs @@ -4,10 +4,11 @@ use instant::Instant; #[cfg(feature = "rayon")] use rayon::{ iter::{IntoParallelRefIterator, ParallelIterator}, - slice::ParallelSliceMut, + prelude::ParallelSliceMut, ThreadPool, }; +use core::fmt; #[cfg(all(not(feature = "rayon"), not(target_arch = "wasm32")))] use std::thread::{self, JoinHandle}; use std::{ @@ -20,14 +21,12 @@ use std::{path::PathBuf, time::Instant}; #[cfg(all(not(feature = "rayon"), target_arch = "wasm32"))] use wasm_thread::{self as thread, JoinHandle}; -use crate::{diffing::diff_module_by_path, frontend::ReconstructedType, pdb_file::PDBDataSource}; use crate::{ - diffing::diff_type_by_name, + diffing::{diff_module_by_path, diff_symbol_by_name, diff_type_by_name}, error::{Result, ResymCoreError}, - frontend::FrontendCommand, - frontend::{FrontendController, ModuleList}, + frontend::{FrontendCommand, FrontendController, ReconstructedType}, par_iter_if_available, par_sort_by_if_available, - pdb_file::PdbFile, + pdb_file::{self, ModuleList, PDBDataSource, PdbFile, SymbolList, TypeList}, pdb_types::{include_headers_for_flavor, PrimitiveReconstructionFlavor}, PKG_VERSION, }; @@ -50,7 +49,7 @@ pub enum BackendCommand { /// Reconstruct a type given its type index for a given PDB. ReconstructTypeByIndex( PDBSlot, - pdb::TypeIndex, + pdb_file::TypeIndex, PrimitiveReconstructionFlavor, bool, bool, @@ -74,10 +73,27 @@ pub enum BackendCommand { /// Retrieve a list of types that match the given filter for multiple PDBs /// and merge the result. ListTypesMerged(Vec, String, bool, bool, bool), - /// Retrieve the list of all modules in a given PDB. + /// Retrieve a list of symbols that match the given filter for multiple PDBs + /// and merge the result. + ListSymbols(PDBSlot, String, bool, bool, bool), + /// Retrieve a list of symbols that match the given filter for multiple PDBs + /// and merge the result. + ListSymbolsMerged(Vec, String, bool, bool, bool), + /// Reconstruct a symbol given its index for a given PDB. + ReconstructSymbolByIndex( + PDBSlot, + pdb_file::SymbolIndex, + PrimitiveReconstructionFlavor, + bool, + bool, + ), + /// Reconstruct a symbol given its name for a given PDB. + ReconstructSymbolByName(PDBSlot, String, PrimitiveReconstructionFlavor, bool, bool), + /// Retrieve a list of modules that match the given filter for multiple PDBs + /// and merge the result. ListModules(PDBSlot, String, bool, bool), /// Reconstruct a module given its index for a given PDB. - ReconstructModuleByIndex(PDBSlot, usize, PrimitiveReconstructionFlavor, bool), + ReconstructModuleByIndex(PDBSlot, usize, PrimitiveReconstructionFlavor, bool, bool), /// Reconstruct the diff of a type given its name. DiffTypeByName( PDBSlot, @@ -89,6 +105,15 @@ pub enum BackendCommand { bool, bool, ), + /// Reconstruct the diff of a symbol given its name. + DiffSymbolByName( + PDBSlot, + PDBSlot, + String, + PrimitiveReconstructionFlavor, + bool, + bool, + ), /// Reconstruct the diff of a module given its path. DiffModuleByPath( PDBSlot, @@ -96,9 +121,10 @@ pub enum BackendCommand { String, PrimitiveReconstructionFlavor, bool, + bool, ), /// Retrieve a list of all types that reference the given type - ListTypeCrossReferences(PDBSlot, pdb::TypeIndex), + ListTypeCrossReferences(PDBSlot, pdb_file::TypeIndex), } /// Struct that represents the backend. The backend is responsible @@ -391,7 +417,7 @@ fn worker_thread_routine( // Collapse all type indices to `default`. When merging // type lists, we can only count on type names to // represent the types. - (s, pdb::TypeIndex::default()) + (s, Default::default()) })); } } @@ -400,11 +426,128 @@ fn worker_thread_routine( ))?; } + BackendCommand::ListSymbols( + pdb_slot, + search_filter, + case_insensitive_search, + use_regex, + ignore_std_types, + ) => { + if let Some(pdb_file) = pdb_files.get(&pdb_slot) { + let filtered_symbol_list = update_symbol_filter_command( + pdb_file, + &search_filter, + case_insensitive_search, + use_regex, + ignore_std_types, + ); + frontend_controller + .send_command(FrontendCommand::ListSymbolsResult(filtered_symbol_list))?; + } + } + + BackendCommand::ListSymbolsMerged( + pdb_slots, + search_filter, + case_insensitive_search, + use_regex, + ignore_std_types, + ) => { + let mut filtered_symbol_set = BTreeSet::default(); + for pdb_slot in pdb_slots { + if let Some(pdb_file) = pdb_files.get(&pdb_slot) { + let filtered_symbol_list = update_symbol_filter_command( + pdb_file, + &search_filter, + case_insensitive_search, + use_regex, + ignore_std_types, + ); + filtered_symbol_set.extend(filtered_symbol_list.into_iter().map( + |(s, _)| { + // Collapse all type indices to `default`. When merging + // type lists, we can only count on type names to + // represent the types. + (s, Default::default()) + }, + )); + } + } + frontend_controller.send_command(FrontendCommand::ListSymbolsResult( + filtered_symbol_set.into_iter().collect(), + ))?; + } + + BackendCommand::ReconstructSymbolByIndex( + pdb_slot, + symbol_index, + primitives_flavor, + print_header, + print_access_specifiers, + ) => { + if let Some(pdb_file) = pdb_files.get_mut(&pdb_slot) { + let result = reconstruct_symbol_by_index_command( + pdb_file, + symbol_index, + primitives_flavor, + print_header, + print_access_specifiers, + ); + frontend_controller + .send_command(FrontendCommand::ReconstructSymbolResult(result))?; + } + } + + BackendCommand::ReconstructSymbolByName( + pdb_slot, + symbol_name, + primitives_flavor, + print_header, + print_access_specifiers, + ) => { + if let Some(pdb_file) = pdb_files.get_mut(&pdb_slot) { + let result = reconstruct_symbol_by_name_command( + pdb_file, + symbol_name, + primitives_flavor, + print_header, + print_access_specifiers, + ); + frontend_controller + .send_command(FrontendCommand::ReconstructSymbolResult(result))?; + } + } + + BackendCommand::DiffSymbolByName( + pdb_from_slot, + pdb_to_slot, + symbol_name, + primitives_flavor, + print_header, + print_access_specifiers, + ) => { + if let Some(pdb_file_from) = pdb_files.get(&pdb_from_slot) { + if let Some(pdb_file_to) = pdb_files.get(&pdb_to_slot) { + let symbol_diff_result = diff_symbol_by_name( + pdb_file_from, + pdb_file_to, + &symbol_name, + primitives_flavor, + print_header, + print_access_specifiers, + ); + frontend_controller + .send_command(FrontendCommand::DiffResult(symbol_diff_result))?; + } + } + } + BackendCommand::ReconstructModuleByIndex( pdb_slot, module_index, primitives_flavor, print_header, + print_access_specifiers, ) => { if let Some(pdb_file) = pdb_files.get_mut(&pdb_slot) { let reconstructed_module_result = reconstruct_module_by_index_command( @@ -413,6 +556,7 @@ fn worker_thread_routine( primitives_flavor, false, print_header, + print_access_specifiers, ); frontend_controller.send_command(FrontendCommand::ReconstructModuleResult( reconstructed_module_result, @@ -434,7 +578,7 @@ fn worker_thread_routine( use_regex, ); frontend_controller - .send_command(FrontendCommand::UpdateModuleList(module_list))?; + .send_command(FrontendCommand::ListModulesResult(module_list))?; } } @@ -472,6 +616,7 @@ fn worker_thread_routine( module_path, primitives_flavor, print_header, + print_access_specifiers, ) => { if let Some(pdb_file_from) = pdb_files.get(&pdb_from_slot) { if let Some(pdb_file_to) = pdb_files.get(&pdb_to_slot) { @@ -481,6 +626,7 @@ fn worker_thread_routine( &module_path, primitives_flavor, print_header, + print_access_specifiers, ); frontend_controller .send_command(FrontendCommand::DiffResult(module_diff_result))?; @@ -503,7 +649,7 @@ fn worker_thread_routine( fn reconstruct_type_by_index_command<'p, T>( pdb_file: &PdbFile<'p, T>, - type_index: pdb::TypeIndex, + type_index: pdb_file::TypeIndex, primitives_flavor: PrimitiveReconstructionFlavor, print_header: bool, reconstruct_dependencies: bool, @@ -513,7 +659,7 @@ fn reconstruct_type_by_index_command<'p, T>( where T: io::Seek + io::Read + std::fmt::Debug + 'p, { - let (data, xrefs_from) = pdb_file.reconstruct_type_by_type_index( + let (data, xrefs_from) = pdb_file.reconstruct_type_by_index( type_index, primitives_flavor, reconstruct_dependencies, @@ -578,17 +724,68 @@ where } } +fn reconstruct_symbol_by_index_command<'p, T>( + pdb_file: &mut PdbFile<'p, T>, + symbol_index: pdb_file::SymbolIndex, + primitives_flavor: PrimitiveReconstructionFlavor, + print_header: bool, + print_access_specifiers: bool, +) -> Result +where + T: io::Seek + io::Read + std::fmt::Debug + 'p, +{ + let data = pdb_file.reconstruct_symbol_by_index( + symbol_index, + primitives_flavor, + print_access_specifiers, + )?; + if print_header { + let file_header = generate_file_header(pdb_file, primitives_flavor, true, false); + Ok(format!("{file_header}\n{data}")) + } else { + Ok(data) + } +} + +fn reconstruct_symbol_by_name_command<'p, T>( + pdb_file: &mut PdbFile<'p, T>, + symbol_name: String, + primitives_flavor: PrimitiveReconstructionFlavor, + print_header: bool, + print_access_specifiers: bool, +) -> Result +where + T: io::Seek + io::Read + std::fmt::Debug + 'p, +{ + let data = pdb_file.reconstruct_symbol_by_name( + &symbol_name, + primitives_flavor, + print_access_specifiers, + )?; + if print_header { + let file_header = generate_file_header(pdb_file, primitives_flavor, true, false); + Ok(format!("{file_header}\n{data}")) + } else { + Ok(data) + } +} + fn reconstruct_module_by_index_command<'p, T>( pdb_file: &mut PdbFile<'p, T>, - module_index: usize, + module_index: pdb_file::ModuleIndex, primitives_flavor: PrimitiveReconstructionFlavor, ignore_std_types: bool, print_header: bool, + print_access_specifiers: bool, ) -> Result where T: io::Seek + io::Read + std::fmt::Debug + 'p, { - let data = pdb_file.reconstruct_module_by_index(module_index, primitives_flavor)?; + let data = pdb_file.reconstruct_module_by_index( + module_index, + primitives_flavor, + print_access_specifiers, + )?; if print_header { let file_header = generate_file_header(pdb_file, primitives_flavor, true, ignore_std_types); Ok(format!("{file_header}\n{data}")) @@ -637,13 +834,13 @@ fn update_type_filter_command( use_regex: bool, ignore_std_types: bool, sort_by_index: bool, -) -> Vec<(String, pdb::TypeIndex)> +) -> TypeList where T: io::Seek + io::Read, { let filter_start = Instant::now(); - // Fitler out std types if needed + // Filter out std types if needed let filtered_type_list = if ignore_std_types { filter_std_types(&pdb_file.complete_type_list) } else { @@ -675,10 +872,10 @@ where /// Filter type list with a regular expression fn filter_types_regex( - type_list: &[(String, pdb::TypeIndex)], + type_list: &[(String, u32)], search_filter: &str, case_insensitive_search: bool, -) -> Vec<(String, pdb::TypeIndex)> { +) -> TypeList { match regex::RegexBuilder::new(search_filter) .case_insensitive(case_insensitive_search) .build() @@ -694,10 +891,10 @@ fn filter_types_regex( /// Filter type list with a plain (sub-)string fn filter_types_regular( - type_list: &[(String, pdb::TypeIndex)], + type_list: &[(String, u32)], search_filter: &str, case_insensitive_search: bool, -) -> Vec<(String, pdb::TypeIndex)> { +) -> TypeList { if case_insensitive_search { let search_filter = search_filter.to_lowercase(); par_iter_if_available!(type_list) @@ -713,13 +910,109 @@ fn filter_types_regular( } /// Filter type list to remove types in the `std` namespace -fn filter_std_types(type_list: &[(String, pdb::TypeIndex)]) -> Vec<(String, pdb::TypeIndex)> { +fn filter_std_types(type_list: &[(String, pdb_file::TypeIndex)]) -> TypeList { par_iter_if_available!(type_list) .filter(|r| !r.0.starts_with("std::")) .cloned() .collect() } +fn update_symbol_filter_command( + pdb_file: &PdbFile, + search_filter: &str, + case_insensitive_search: bool, + use_regex: bool, + ignore_std_symbols: bool, +) -> SymbolList +where + T: io::Seek + io::Read + fmt::Debug, +{ + let filter_start = Instant::now(); + + match pdb_file.symbol_list() { + Err(_) => SymbolList::default(), + Ok(symbol_list) => { + // Filter out std types if needed + let filtered_symbol_list = if ignore_std_symbols { + filter_std_symbols(&symbol_list) + } else { + symbol_list.clone() + }; + + let filtered_symbol_list = if search_filter.is_empty() { + // No need to filter + filtered_symbol_list + } else if use_regex { + filter_symbols_regex( + &filtered_symbol_list, + search_filter, + case_insensitive_search, + ) + } else { + filter_symbols_regular( + &filtered_symbol_list, + search_filter, + case_insensitive_search, + ) + }; + + log::debug!( + "Symbol filtering took {} ms", + filter_start.elapsed().as_millis() + ); + + filtered_symbol_list + } + } +} + +/// Filter symbol list to remove types in the `std` namespace +fn filter_std_symbols(symbol_list: &[(String, pdb_file::SymbolIndex)]) -> SymbolList { + par_iter_if_available!(symbol_list) + .filter(|r| !r.0.starts_with("std::")) + .cloned() + .collect() +} + +/// Filter type list with a regular expression +fn filter_symbols_regex( + symbol_list: &[(String, pdb_file::SymbolIndex)], + search_filter: &str, + case_insensitive_search: bool, +) -> SymbolList { + match regex::RegexBuilder::new(search_filter) + .case_insensitive(case_insensitive_search) + .build() + { + // In case of error, return an empty result + Err(_) => vec![], + Ok(regex) => par_iter_if_available!(symbol_list) + .filter(|r| regex.find(&r.0).is_some()) + .cloned() + .collect(), + } +} + +/// Filter type list with a plain (sub-)string +fn filter_symbols_regular( + symbol_list: &[(String, pdb_file::SymbolIndex)], + search_filter: &str, + case_insensitive_search: bool, +) -> SymbolList { + if case_insensitive_search { + let search_filter = search_filter.to_lowercase(); + par_iter_if_available!(symbol_list) + .filter(|r| r.0.to_lowercase().contains(&search_filter)) + .cloned() + .collect() + } else { + par_iter_if_available!(symbol_list) + .filter(|r| r.0.contains(search_filter)) + .cloned() + .collect() + } +} + fn list_modules_command<'p, T>( pdb_file: &PdbFile<'p, T>, search_filter: &str, @@ -797,8 +1090,8 @@ fn filter_modules_regular( fn list_type_xrefs_command<'p, T>( pdb_file: &PdbFile<'p, T>, - type_index: pdb::TypeIndex, -) -> Result> + type_index: pdb_file::TypeIndex, +) -> Result where T: io::Seek + io::Read + std::fmt::Debug + 'p, { diff --git a/resym_core/src/diffing.rs b/resym_core/src/diffing.rs index b46cfec..02ec977 100644 --- a/resym_core/src/diffing.rs +++ b/resym_core/src/diffing.rs @@ -92,6 +92,7 @@ pub fn diff_module_by_path<'p, T>( module_path: &str, primitives_flavor: PrimitiveReconstructionFlavor, print_header: bool, + print_access_specifiers: bool, ) -> Result where T: io::Seek + io::Read + std::fmt::Debug + 'p, @@ -111,10 +112,10 @@ where // Reconstruct modules from both PDBs { let reconstructed_type_from_tmp = pdb_file_from - .reconstruct_module_by_path(module_path, primitives_flavor) + .reconstruct_module_by_path(module_path, primitives_flavor, print_access_specifiers) .unwrap_or_default(); let reconstructed_type_to_tmp = pdb_file_to - .reconstruct_module_by_path(module_path, primitives_flavor) + .reconstruct_module_by_path(module_path, primitives_flavor, print_access_specifiers) .unwrap_or_default(); if reconstructed_type_from_tmp.is_empty() && reconstructed_type_to_tmp.is_empty() { // Make it obvious an error occured @@ -134,6 +135,53 @@ where Ok(diff) } +pub fn diff_symbol_by_name<'p, T>( + pdb_file_from: &PdbFile<'p, T>, + pdb_file_to: &PdbFile<'p, T>, + symbol_name: &str, + primitives_flavor: PrimitiveReconstructionFlavor, + print_header: bool, + print_access_specifiers: bool, +) -> Result +where + T: io::Seek + io::Read + std::fmt::Debug + 'p, +{ + let diff_start = Instant::now(); + + // Prepend header if needed + let (mut reconstructed_symbol_from, mut reconstructed_symbol_to) = if print_header { + let diff_header = generate_diff_header(pdb_file_from, pdb_file_to); + (diff_header.clone(), diff_header) + } else { + (String::default(), String::default()) + }; + + // Reconstruct modules from both PDBs + { + let reconstructed_symbol_from_tmp = pdb_file_from + .reconstruct_symbol_by_name(symbol_name, primitives_flavor, print_access_specifiers) + .unwrap_or_default(); + let reconstructed_symbol_to_tmp = pdb_file_to + .reconstruct_symbol_by_name(symbol_name, primitives_flavor, print_access_specifiers) + .unwrap_or_default(); + if reconstructed_symbol_from_tmp.is_empty() && reconstructed_symbol_to_tmp.is_empty() { + // Make it obvious an error occured + return Err(ResymCoreError::SymbolNotFoundError(symbol_name.to_owned())); + } + reconstructed_symbol_from.push_str(&reconstructed_symbol_from_tmp); + reconstructed_symbol_to.push_str(&reconstructed_symbol_to_tmp); + } + + // Diff reconstructed representations + let diff = generate_diff(&reconstructed_symbol_from, &reconstructed_symbol_to)?; + log::debug!( + "Symbol diffing took {} ms", + diff_start.elapsed().as_millis() + ); + + Ok(diff) +} + fn generate_diff_header<'p, T>( pdb_file_from: &PdbFile<'p, T>, pdb_file_to: &PdbFile<'p, T>, diff --git a/resym_core/src/error.rs b/resym_core/src/error.rs index 6fab68f..09c607a 100644 --- a/resym_core/src/error.rs +++ b/resym_core/src/error.rs @@ -45,6 +45,11 @@ pub enum ResymCoreError { #[error("type not found: {0}")] TypeNameNotFoundError(String), + /// Error returned when querying for a symbol, that isn't present in + /// the PDB file. + #[error("symbol not found: {0}")] + SymbolNotFoundError(String), + /// Error returned when querying for a module by path, that isn't present in /// the PDB file. #[error("module not found: {0}")] diff --git a/resym_core/src/frontend.rs b/resym_core/src/frontend.rs index 2479eae..a81eb43 100644 --- a/resym_core/src/frontend.rs +++ b/resym_core/src/frontend.rs @@ -1,8 +1,10 @@ -use crate::{backend::PDBSlot, diffing::Diff, error::Result}; +use crate::{ + backend::PDBSlot, + diffing::Diff, + error::Result, + pdb_file::{ModuleList, SymbolList, TypeList}, +}; -pub type TypeIndex = pdb::TypeIndex; -pub type TypeList = Vec<(String, TypeIndex)>; -pub type ModuleList = Vec<(String, usize)>; /// Tuple containing the reconstructed type as a `String` /// and the list of directly referenced types as a `TypeList` pub type ReconstructedType = (String, TypeList); @@ -12,11 +14,22 @@ pub enum FrontendCommand { /// Send result from `LoadURL` backend command. /// Contains last path segment (i.e., file name) as a `String` and data as `Vec`. LoadURLResult(Result<(PDBSlot, String, Vec)>), + + // Types ListTypesResult(TypeList), ReconstructTypeResult(Result), + + // Symbols + ListSymbolsResult(SymbolList), + ReconstructSymbolResult(Result), + + // Modules + ListModulesResult(Result), ReconstructModuleResult(Result), - UpdateModuleList(Result), + + // Diff DiffResult(Result), + // Xrefs ListTypeCrossReferencesResult(Result), } diff --git a/resym_core/src/pdb_file.rs b/resym_core/src/pdb_file.rs index 11046b6..1955d30 100644 --- a/resym_core/src/pdb_file.rs +++ b/resym_core/src/pdb_file.rs @@ -6,7 +6,7 @@ use pdb::FallibleIterator; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::{ - collections::{BTreeMap, HashMap, HashSet, VecDeque}, + collections::{BTreeMap, BinaryHeap, HashMap, HashSet, VecDeque}, io::{self, Read, Seek}, path::PathBuf, sync::{Arc, RwLock}, @@ -16,13 +16,23 @@ use std::{fs::File, path::Path, time::Instant}; use crate::{ error::{Result, ResymCoreError}, - frontend::{ModuleList, ReconstructedType, TypeList}, + frontend::ReconstructedType, par_iter_if_available, pdb_types::{ self, is_unnamed_type, type_name, DataFormatConfiguration, PrimitiveReconstructionFlavor, }, }; +pub type TypeIndex = u32; +pub type TypeList = Vec<(String, TypeIndex)>; +/// `SymbolIndex` have two parts: a module index and a symbol index +pub type SymbolIndex = (ModuleIndex, u32); +pub type SymbolList = Vec<(String, SymbolIndex)>; +pub type ModuleIndex = usize; +pub type ModuleList = Vec<(String, ModuleIndex)>; + +const GLOBAL_MODULE_INDEX: usize = usize::MAX; + /// Wrapper for different buffer types processed by `resym` #[derive(Debug)] pub enum PDBDataSource { @@ -51,17 +61,39 @@ impl Read for PDBDataSource { } } +/// Struct used in binary heaps, to prioritize certain symbol kind over others +#[derive(PartialEq, Eq)] +struct PrioritizedSymbol { + priority: u16, + index: SymbolIndex, + name: String, +} + +impl PartialOrd for PrioritizedSymbol { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PrioritizedSymbol { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.priority.cmp(&other.priority) + } +} + pub struct PdbFile<'p, T> where T: io::Seek + io::Read + 'p, { - pub complete_type_list: Vec<(String, pdb::TypeIndex)>, + pub complete_type_list: Vec<(String, TypeIndex)>, pub forwarder_to_complete_type: Arc>, pub machine_type: pdb::MachineType, pub type_information: pdb::TypeInformation<'p>, pub debug_information: pdb::DebugInformation<'p>, + pub global_symbols: pdb::SymbolTable<'p>, + pub sections: Vec, pub file_path: PathBuf, - pub xref_to_map: RwLock>>, + pub xref_to_map: RwLock>>, pdb: RwLock>, } @@ -73,6 +105,8 @@ impl<'p> PdbFile<'p, File> { let mut pdb = pdb::PDB::open(file)?; let type_information = pdb.type_information()?; let debug_information = pdb.debug_information()?; + let global_symbols = pdb.global_symbols()?; + let sections = pdb.sections().unwrap_or_default().unwrap_or_default(); let machine_type = pdb.debug_information()?.machine_type()?; let mut pdb_file = PdbFile { @@ -81,6 +115,8 @@ impl<'p> PdbFile<'p, File> { machine_type, type_information, debug_information, + global_symbols, + sections, file_path: pdb_file_path.to_owned(), xref_to_map: DashMap::default().into(), pdb: pdb.into(), @@ -101,6 +137,8 @@ impl<'p> PdbFile<'p, PDBDataSource> { let mut pdb = pdb::PDB::open(reader)?; let type_information = pdb.type_information()?; let debug_information = pdb.debug_information()?; + let global_symbols = pdb.global_symbols()?; + let sections = pdb.sections().unwrap_or_default().unwrap_or_default(); let machine_type = pdb.debug_information()?.machine_type()?; let mut pdb_file = PdbFile { @@ -109,6 +147,8 @@ impl<'p> PdbFile<'p, PDBDataSource> { machine_type, type_information, debug_information, + global_symbols, + sections, file_path: pdb_file_name.into(), xref_to_map: DashMap::default().into(), pdb: pdb.into(), @@ -127,6 +167,8 @@ impl<'p> PdbFile<'p, PDBDataSource> { let mut pdb = pdb::PDB::open(reader)?; let type_information = pdb.type_information()?; let debug_information = pdb.debug_information()?; + let global_symbols = pdb.global_symbols()?; + let sections = pdb.sections().unwrap_or_default().unwrap_or_default(); let machine_type = pdb.debug_information()?.machine_type()?; let mut pdb_file = PdbFile { @@ -135,6 +177,8 @@ impl<'p> PdbFile<'p, PDBDataSource> { machine_type, type_information, debug_information, + global_symbols, + sections, file_path: pdb_file_name.into(), xref_to_map: DashMap::default().into(), pdb: pdb.into(), @@ -178,7 +222,7 @@ where if is_unnamed_type(&class_name) { class_name = format!("_unnamed_{type_index}"); } - self.complete_type_list.push((class_name, type_index)); + self.complete_type_list.push((class_name, type_index.0)); } pdb::TypeData::Union(data) => { let mut class_name = data.name.to_string().into_owned(); @@ -194,7 +238,7 @@ where if is_unnamed_type(&class_name) { class_name = format!("_unnamed_{type_index}"); } - self.complete_type_list.push((class_name, type_index)); + self.complete_type_list.push((class_name, type_index.0)); } pdb::TypeData::Enumeration(data) => { let mut class_name = data.name.to_string().into_owned(); @@ -210,7 +254,7 @@ where if is_unnamed_type(&class_name) { class_name = format!("_unnamed_{type_index}"); } - self.complete_type_list.push((class_name, type_index)); + self.complete_type_list.push((class_name, type_index.0)); } _ => {} } @@ -245,7 +289,7 @@ where ignore_std_types: bool, ) -> Result { // Populate our `TypeFinder` and find the right type index - let mut type_index = pdb::TypeIndex::default(); + let mut type_index = TypeIndex::default(); let mut type_finder = self.type_information.finder(); { let mut type_iter = self.type_information.iter(); @@ -265,13 +309,13 @@ where let class_name = data.name.to_string(); if is_unnamed_type(&class_name) { if type_name == format!("_unnamed_{item_type_index}") { - type_index = item_type_index; + type_index = item_type_index.0; } } else if class_name == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } else if let Some(unique_name) = data.unique_name { if unique_name.to_string() == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } } } @@ -285,13 +329,13 @@ where let union_name = data.name.to_string(); if is_unnamed_type(&union_name) { if type_name == format!("_unnamed_{item_type_index}") { - type_index = item_type_index; + type_index = item_type_index.0; } } else if data.name.to_string() == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } else if let Some(unique_name) = data.unique_name { if unique_name.to_string() == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } } } @@ -305,13 +349,13 @@ where let enum_name = data.name.to_string(); if is_unnamed_type(&enum_name) { if type_name == format!("_unnamed_{item_type_index}") { - type_index = item_type_index; + type_index = item_type_index.0; } } else if data.name.to_string() == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } else if let Some(unique_name) = data.unique_name { if unique_name.to_string() == type_name { - type_index = item_type_index; + type_index = item_type_index.0; } } } @@ -322,7 +366,7 @@ where } } - if type_index == pdb::TypeIndex::default() { + if type_index == TypeIndex::default() { Err(ResymCoreError::TypeNameNotFoundError(type_name.to_owned())) } else { self.reconstruct_type_by_type_index_internal( @@ -336,9 +380,9 @@ where } } - pub fn reconstruct_type_by_type_index( + pub fn reconstruct_type_by_index( &self, - type_index: pdb::TypeIndex, + type_index: TypeIndex, primitives_flavor: PrimitiveReconstructionFlavor, reconstruct_dependencies: bool, print_access_specifiers: bool, @@ -363,6 +407,61 @@ where ) } + pub fn symbol_list(&self) -> Result { + let mut symbol_heap: BinaryHeap = BinaryHeap::new(); + + // Modules' private symbols + { + let mut modules = self.debug_information.modules()?.enumerate(); + let mut pdb = self.pdb.write().expect("lock shouldn't be poisoned"); + while let Some((module_index, module)) = modules.next()? { + let module_info = match pdb.module_info(&module)? { + Some(info) => info, + None => { + continue; + } + }; + + let mut module_symbols = module_info.symbols()?; + while let Some(symbol) = module_symbols.next()? { + if let Some(symbol_name) = get_symbol_name(&symbol) { + symbol_heap.push(PrioritizedSymbol { + priority: symbol_priority(&symbol), + index: (module_index, symbol.index().0), + name: symbol_name.clone(), + }); + } + } + } + } + + // Global symbols + let mut symbol_table = self.global_symbols.iter(); + while let Some(symbol) = symbol_table.next()? { + if let Some(symbol_name) = get_symbol_name(&symbol) { + symbol_heap.push(PrioritizedSymbol { + priority: symbol_priority(&symbol), + index: (GLOBAL_MODULE_INDEX, symbol.index().0), + name: symbol_name.clone(), + }); + } + } + + let mut symbol_names = HashSet::new(); + Ok(symbol_heap + .into_sorted_vec() + .into_iter() + .filter_map(|s| { + if !symbol_names.contains(&s.name) { + symbol_names.insert(s.name.clone()); + Some((s.name, s.index)) + } else { + None + } + }) + .collect()) + } + pub fn module_list(&self) -> Result { let module_list = self .debug_information @@ -373,10 +472,131 @@ where Ok(module_list.collect()?) } + pub fn reconstruct_symbol_by_index( + &self, + symbol_index: SymbolIndex, + primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifiers: bool, + ) -> Result { + // Populate our `TypeFinder` + let mut type_finder = self.type_information.finder(); + { + let mut type_iter = self.type_information.iter(); + while (type_iter.next()?).is_some() { + type_finder.update(&type_iter); + } + } + + // Check which module the symbol is from + if symbol_index.0 == GLOBAL_MODULE_INDEX { + // Global symbols + let mut symbol_table = self.global_symbols.iter(); + while let Some(symbol) = symbol_table.next()? { + if symbol.index().0 == symbol_index.1 { + return Ok(self + .reconstruct_symbol( + &type_finder, + &symbol, + primitives_flavor, + print_access_specifiers, + ) + .unwrap_or_default()); + } + } + } else if let Some(module) = self.debug_information.modules()?.nth(symbol_index.0)? { + // Modules' private symbols + let mut pdb = self.pdb.write().expect("lock shouldn't be poisoned"); + if let Some(module_info) = pdb.module_info(&module)? { + let mut module_symbols = module_info.symbols_at(symbol_index.1.into())?; + while let Some(symbol) = module_symbols.next()? { + if symbol.index().0 == symbol_index.1 { + return Ok(self + .reconstruct_symbol( + &type_finder, + &symbol, + primitives_flavor, + print_access_specifiers, + ) + .unwrap_or_default()); + } + } + } + } + + Err(ResymCoreError::SymbolNotFoundError(format!( + "Symbol #{:?} not found", + symbol_index + ))) + } + + pub fn reconstruct_symbol_by_name( + &self, + symbol_name: &str, + primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifiers: bool, + ) -> Result { + // Populate our `TypeFinder` + let mut type_finder = self.type_information.finder(); + { + let mut type_iter = self.type_information.iter(); + while (type_iter.next()?).is_some() { + type_finder.update(&type_iter); + } + } + + // Global symbols + let mut symbol_table = self.global_symbols.iter(); + while let Some(symbol) = symbol_table.next()? { + if let Some(current_symbol_name) = get_symbol_name(&symbol) { + if current_symbol_name == symbol_name { + return Ok(self + .reconstruct_symbol( + &type_finder, + &symbol, + primitives_flavor, + print_access_specifiers, + ) + .unwrap_or_default()); + } + } + } + + // Modules' private symbols + { + let mut pdb = self.pdb.write().expect("lock shouldn't be poisoned"); + let mut modules = self.debug_information.modules()?; + while let Some(module) = modules.next()? { + if let Some(module_info) = pdb.module_info(&module)? { + let mut module_symbols = module_info.symbols()?; + while let Some(symbol) = module_symbols.next()? { + if let Some(current_symbol_name) = get_symbol_name(&symbol) { + if current_symbol_name == symbol_name { + return Ok(self + .reconstruct_symbol( + &type_finder, + &symbol, + primitives_flavor, + print_access_specifiers, + ) + .unwrap_or_default()); + } + } + } + } + } + } + + Err(ResymCoreError::SymbolNotFoundError(format!( + "Symbol '{}' not found", + symbol_name + ))) + } + pub fn reconstruct_module_by_path( &self, module_path: &str, primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifiers: bool, ) -> Result { // Find index for module let mut modules = self.debug_information.modules()?; @@ -387,7 +607,11 @@ where "Module '{}' not found", module_path ))), - Some(module_index) => self.reconstruct_module_by_index(module_index, primitives_flavor), + Some(module_index) => self.reconstruct_module_by_index( + module_index, + primitives_flavor, + print_access_specifiers, + ), } } @@ -395,6 +619,7 @@ where &self, module_index: usize, primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifiers: bool, ) -> Result { let mut modules = self.debug_information.modules()?; let module = modules.nth(module_index)?.ok_or_else(|| { @@ -424,80 +649,15 @@ where let mut result = String::default(); module_info.symbols()?.for_each(|symbol| { - let mut needed_types = pdb_types::NeededTypeSet::new(); - - match symbol.parse()? { - pdb::SymbolData::UserDefinedType(udt) => { - if let Ok(type_name) = type_name( - &type_finder, - &self.forwarder_to_complete_type, - udt.type_index, - &primitives_flavor, - &mut needed_types, - ) { - if type_name.0 == "..." { - // No type - result += - format!("{}; // Missing type information\n", udt.name).as_str(); - } else { - result += - format!("using {} = {}{};\n", udt.name, type_name.0, type_name.1) - .as_str(); - } - } - } - pdb::SymbolData::Procedure(procedure) => { - if let Ok(type_name) = type_name( - &type_finder, - &self.forwarder_to_complete_type, - procedure.type_index, - &primitives_flavor, - &mut needed_types, - ) { - if type_name.0 == "..." { - // No type - result += format!( - "void {}(); // CodeSize={} (missing type information)\n", - procedure.name, procedure.len - ) - .as_str(); - } else { - result += format!( - "{}{}{}; // CodeSize={}\n", - type_name.0, procedure.name, type_name.1, procedure.len - ) - .as_str(); - } - } - } - pdb::SymbolData::Data(data) => { - if let Ok(type_name) = type_name( - &type_finder, - &self.forwarder_to_complete_type, - data.type_index, - &primitives_flavor, - &mut needed_types, - ) { - if type_name.0 == "..." { - // No type - result += - format!("{}; // Missing type information\n", data.name).as_str(); - } else { - result += - format!("{} {}{};\n", type_name.0, data.name, type_name.1).as_str(); - } - } - } - pdb::SymbolData::UsingNamespace(namespace) => { - result += format!("using namespace {};\n", namespace.name).as_str(); - } - pdb::SymbolData::AnnotationReference(annotation) => { - // TODO(ergrelet): update when support for annotations - // (symbol kind 0x1019) has been implemented in `pdb` - result += format!("__annotation(); // {}\n", annotation.name).as_str(); - } - // Ignore - _ => {} + let reconstructed_symbol = self.reconstruct_symbol( + &type_finder, + &symbol, + primitives_flavor, + print_access_specifiers, + ); + if let Some(reconstructed_symbol) = reconstructed_symbol { + result += &reconstructed_symbol; + result.push('\n'); } Ok(()) @@ -509,7 +669,7 @@ where fn reconstruct_type_by_type_index_internal( &self, type_finder: &pdb::TypeFinder, - type_index: pdb::TypeIndex, + type_index: TypeIndex, primitives_flavor: PrimitiveReconstructionFlavor, reconstruct_dependencies: bool, print_access_specifiers: bool, @@ -526,7 +686,7 @@ where type_data.add( type_finder, &self.forwarder_to_complete_type, - type_index, + type_index.into(), &primitives_flavor, &mut needed_types, )?; @@ -537,7 +697,7 @@ where &Default::default(), &mut reconstruction_output, )?; - let needed_types: Vec = needed_types.into_iter().map(|e| e.0).collect(); + let needed_types: Vec = needed_types.into_iter().map(|e| e.0 .0).collect(); let xrefs_from = self.type_list_from_type_indices(&needed_types); return Ok((reconstruction_output, xrefs_from)); @@ -545,13 +705,12 @@ where let mut xrefs_from = vec![]; // Add all the needed types iteratively until we're done - let mut type_dependency_map: HashMap> = - HashMap::new(); + let mut type_dependency_map: HashMap> = HashMap::new(); { let dep_start = Instant::now(); // Add the requested type first - let mut types_to_process: VecDeque = VecDeque::from([type_index]); + let mut types_to_process: VecDeque = VecDeque::from([type_index]); let mut processed_type_set = HashSet::new(); // Keep processing new types until there's nothing to process while let Some(needed_type_index) = types_to_process.pop_front() { @@ -565,34 +724,35 @@ where type_data.add( type_finder, &self.forwarder_to_complete_type, - needed_type_index, + needed_type_index.into(), &primitives_flavor, &mut needed_types, )?; // Initialize only once, the first time (i.e., for the requested type) if xrefs_from.is_empty() { - let needed_types: Vec = - needed_types.iter().map(|e| e.0).collect(); + let needed_types: Vec = + needed_types.iter().map(|e| e.0 .0).collect(); xrefs_from = self.type_list_from_type_indices(&needed_types); } - for pair in &needed_types { + for (type_index, is_pointer) in &needed_types { // Add forward declaration for types referenced by pointers - if pair.1 { - type_data.add_as_forward_declaration(type_finder, pair.0)?; + if *is_pointer { + type_data.add_as_forward_declaration(type_finder, *type_index)?; } // Update type dependency map if let Some(type_dependency) = type_dependency_map.get_mut(&needed_type_index) { - type_dependency.push(*pair); + type_dependency.push((type_index.0, *is_pointer)); } else { - type_dependency_map.insert(needed_type_index, vec![*pair]); + type_dependency_map + .insert(needed_type_index, vec![(type_index.0, *is_pointer)]); } } // Update the set of processed types processed_type_set.insert(needed_type_index); // Update the queue of type to process - types_to_process.extend(needed_types.into_iter().map(|pair| pair.0)); + types_to_process.extend(needed_types.into_iter().map(|pair| pair.0 .0)); } log::debug!( @@ -622,8 +782,7 @@ where ) -> Result { let mut type_data = pdb_types::Data::new(ignore_std_types); let mut processed_types = Vec::new(); - let mut type_dependency_map: HashMap> = - HashMap::new(); + let mut type_dependency_map: HashMap> = HashMap::new(); { let mut type_finder = self.type_information.finder(); // Populate our `TypeFinder` @@ -665,20 +824,21 @@ where } } else { // Handle success - processed_types.push(complete_type_index); - for pair in &needed_types { + processed_types.push(complete_type_index.0); + for (type_index, is_pointer) in &needed_types { // Add forward declaration for types referenced by pointers - if pair.1 { - type_data.add_as_forward_declaration(&type_finder, pair.0)?; + if *is_pointer { + type_data.add_as_forward_declaration(&type_finder, *type_index)?; } // Update type dependency map if let Some(type_dependency) = - type_dependency_map.get_mut(&complete_type_index) + type_dependency_map.get_mut(&complete_type_index.0) { - type_dependency.push(*pair); + type_dependency.push((type_index.0, *is_pointer)); } else { - type_dependency_map.insert(complete_type_index, vec![*pair]); + type_dependency_map + .insert(complete_type_index.0, vec![(type_index.0, *is_pointer)]); } } } @@ -700,10 +860,7 @@ where Ok(reconstruction_output) } - pub fn get_xrefs_for_type( - &self, - type_index: pdb::TypeIndex, - ) -> Result> { + pub fn get_xrefs_for_type(&self, type_index: TypeIndex) -> Result { // Generate xref cache if empty if self .xref_to_map @@ -721,7 +878,7 @@ where } // Iterate through all types - let xref_map: DashMap> = DashMap::default(); + let xref_map: DashMap> = DashMap::default(); let mut type_iter = self.type_information.iter(); while let Some(type_item) = type_iter.next()? { let current_type_index = type_item.index(); @@ -751,10 +908,10 @@ where } par_iter_if_available!(needed_types).for_each(|(t, _)| { - if let Some(mut xref_list) = xref_map.get_mut(t) { - xref_list.push(current_type_index); + if let Some(mut xref_list) = xref_map.get_mut(&t.0) { + xref_list.push(current_type_index.0); } else { - xref_map.insert(*t, vec![current_type_index]); + xref_map.insert(t.0, vec![current_type_index.0]); } }); } @@ -782,7 +939,7 @@ where } } - fn type_list_from_type_indices(&self, type_indices: &[pdb::TypeIndex]) -> TypeList { + fn type_list_from_type_indices(&self, type_indices: &[TypeIndex]) -> TypeList { par_iter_if_available!(self.complete_type_list) .filter_map(|(type_name, type_index)| { if type_indices.contains(type_index) { @@ -793,18 +950,174 @@ where }) .collect() } + + fn reconstruct_symbol( + &self, + type_finder: &pdb::ItemFinder<'_, pdb::TypeIndex>, + symbol: &pdb::Symbol<'_>, + primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifiers: bool, + ) -> Option { + let mut needed_types = pdb_types::NeededTypeSet::new(); + match symbol.parse().ok()? { + pdb::SymbolData::UserDefinedType(udt) => { + if let Ok(type_name) = type_name( + type_finder, + &self.forwarder_to_complete_type, + udt.type_index, + &primitives_flavor, + &mut needed_types, + ) { + if type_name.0 == "..." { + // No type + Some(format!("char {}; // (missing type information)", udt.name)) + } else { + Some(format!( + "using {} = {}{};", + udt.name, type_name.0, type_name.1 + )) + } + } else { + None + } + } + + // Functions and methods + pdb::SymbolData::Procedure(procedure) => { + let symbol_rva = symbol_rva(&procedure.offset, &self.sections) + .map(|offset| format!("RVA=0x{:x} ", offset)) + .unwrap_or_default(); + if let Ok(type_name) = type_name( + type_finder, + &self.forwarder_to_complete_type, + procedure.type_index, + &primitives_flavor, + &mut needed_types, + ) { + let static_prefix = if procedure.global { "" } else { "static " }; + if type_name.0 == "..." { + // No type + Some(format!( + "{}void {}(); // {}CodeSize=0x{:x} (missing type information)", + static_prefix, procedure.name, symbol_rva, procedure.len, + )) + } else { + Some(format!( + "{}{}{}{}; // {}CodeSize=0x{:x}", + static_prefix, + type_name.0, + procedure.name, + type_name.1, + symbol_rva, + procedure.len, + )) + } + } else { + None + } + } + + // Global variables + pdb::SymbolData::Data(data) => { + let symbol_rva = symbol_rva(&data.offset, &self.sections) + .map(|offset| format!("RVA=0x{:x} ", offset)) + .unwrap_or_default(); + if let Ok(type_name) = type_name( + type_finder, + &self.forwarder_to_complete_type, + data.type_index, + &primitives_flavor, + &mut needed_types, + ) { + let static_prefix = if data.global { "" } else { "static " }; + if let Some(demangled_symbol) = + demangle_symbol_name(data.name.to_string(), print_access_specifiers) + { + Some(format!( + "{}{}; // {}", + static_prefix, demangled_symbol, symbol_rva, + )) + } else if type_name.0 == "..." { + // No type + Some(format!( + "{}char {}; // {}(missing type information)", + static_prefix, data.name, symbol_rva, + )) + } else { + Some(format!( + "{}{} {}{}; // {}", + static_prefix, type_name.0, data.name, type_name.1, symbol_rva, + )) + } + } else { + None + } + } + + pdb::SymbolData::UsingNamespace(namespace) => { + Some(format!("using namespace {};", namespace.name)) + } + + pdb::SymbolData::AnnotationReference(annotation) => { + // TODO(ergrelet): update when support for annotations + // (symbol kind 0x1019) has been implemented in `pdb` + Some(format!("__annotation(); // {}", annotation.name)) + } + + // Public symbols + pdb::SymbolData::Public(data) => { + let symbol_rva = symbol_rva(&data.offset, &self.sections) + .map(|offset| format!("RVA=0x{:x} ", offset)) + .unwrap_or_default(); + Some( + if let Some(demangled_symbol) = + demangle_symbol_name(data.name.to_string(), print_access_specifiers) + { + format!("{}; // {}", demangled_symbol, symbol_rva) + } else if data.function { + // Add parenthese to distinguish functions from global variables + format!( + "void {}(); // {}(no type information)", + data.name, symbol_rva, + ) + } else { + format!("char {}; // {}(no type information)", data.name, symbol_rva,) + }, + ) + } + + // Exported symbols + pdb::SymbolData::Export(data) => Some( + if let Some(demangled_symbol) = + demangle_symbol_name(data.name.to_string(), print_access_specifiers) + { + format!("{};", demangled_symbol) + } else if data.flags.data { + format!("char {}; // Exported (no type information)", data.name) + } else { + // Add parenthese to distinguish functions from exported variables + format!("void {}(); // Exported (no type information)", data.name) + }, + ), + + _ => { + // ignore everything else + None + } + } + } } fn compute_type_depth_map( - type_dependency_map: &HashMap>, - root_types: &[pdb::TypeIndex], + type_dependency_map: &HashMap>, + root_types: &[TypeIndex], ) -> BTreeMap> { let depth_start = Instant::now(); - let mut type_depth_map: HashMap = + let mut type_depth_map: HashMap = HashMap::from_iter(root_types.iter().map(|elem| (*elem, 0))); // Perform depth-first search to determine the "depth" of each type - let mut types_to_visit: VecDeque<(usize, pdb::TypeIndex)> = + let mut types_to_visit: VecDeque<(usize, TypeIndex)> = VecDeque::from_iter(root_types.iter().map(|elem| (0, *elem))); while let Some((current_type_depth, current_type_index)) = types_to_visit.pop_back() { if let Some(type_dependencies) = type_dependency_map.get(¤t_type_index) { @@ -828,9 +1141,9 @@ fn compute_type_depth_map( .into_iter() .fold(BTreeMap::new(), |mut acc, (type_index, type_depth)| { if let Some(type_indices) = acc.get_mut(&type_depth) { - type_indices.push(type_index); + type_indices.push(type_index.into()); } else { - acc.insert(type_depth, vec![type_index]); + acc.insert(type_depth, vec![type_index.into()]); } acc @@ -843,3 +1156,80 @@ fn compute_type_depth_map( inverted_type_depth_map } + +fn get_symbol_name(symbol: &pdb::Symbol) -> Option { + match symbol.parse().ok()? { + pdb::SymbolData::UserDefinedType(udt) => Some(udt.name.to_string().to_string()), + + // Functions and methods + pdb::SymbolData::Procedure(procedure) => Some(procedure.name.to_string().to_string()), + + // Global variables + pdb::SymbolData::Data(data) => Some(data.name.to_string().to_string()), + + // Public symbols + pdb::SymbolData::Public(data) => Some(data.name.to_string().to_string()), + + // Exported symbols + pdb::SymbolData::Export(data) => Some(data.name.to_string().to_string()), + + _ => { + // ignore everything else + None + } + } +} + +fn symbol_rva( + symbol_offset: &pdb::PdbInternalSectionOffset, + sections: &[pdb::ImageSectionHeader], +) -> Option { + if symbol_offset.section == 0 { + None + } else { + let section_offset = (symbol_offset.section - 1) as usize; + + sections + .get(section_offset) + .map(|section_header| section_header.virtual_address + symbol_offset.offset) + } +} + +fn demangle_symbol_name( + symbol_name: impl AsRef, + print_access_specifiers: bool, +) -> Option { + const CXX_ACCESS_SPECIFIERS: [&str; 3] = ["public: ", "protected: ", "private: "]; + msvc_demangler::demangle(symbol_name.as_ref(), msvc_demangler::DemangleFlags::llvm()) + .map(|mut s| { + if !print_access_specifiers { + // Strip access specifiers + CXX_ACCESS_SPECIFIERS.iter().for_each(|specifier| { + if let Some(stripped_s) = s.strip_prefix(specifier) { + s = stripped_s.to_string(); + } + }); + } + + s + }) + .ok() +} + +fn symbol_priority(symbol: &pdb::Symbol) -> u16 { + if let Ok(symbol) = symbol.parse() { + match symbol { + // Functions and methods, user types, global variables + pdb::SymbolData::Procedure(_) + | pdb::SymbolData::UserDefinedType(_) + | pdb::SymbolData::Data(_) => 0, + // Public symbols + pdb::SymbolData::Public(_) => 1, + // Exported symbols + pdb::SymbolData::Export(_) => 2, + _ => 10, + } + } else { + 0 + } +} diff --git a/resym_core/src/rayon_utils.rs b/resym_core/src/rayon_utils.rs index 827490b..5045155 100644 --- a/resym_core/src/rayon_utils.rs +++ b/resym_core/src/rayon_utils.rs @@ -14,6 +14,22 @@ macro_rules! par_iter_if_available { }; } +/// Macro used to switch between iterators depending on rayon's availability +#[macro_export] +#[cfg(not(feature = "rayon"))] +macro_rules! into_par_iter_if_available { + ($expression:expr) => { + $expression.into_iter() + }; +} +#[macro_export] +#[cfg(feature = "rayon")] +macro_rules! into_par_iter_if_available { + ($expression:expr) => { + $expression.into_par_iter() + }; +} + /// Macro used to switch between functions depending on rayon's availability #[macro_export] #[cfg(not(feature = "rayon"))] diff --git a/resym_core/tests/module_diffing.rs b/resym_core/tests/module_diffing.rs index 7671a5d..0aaed97 100644 --- a/resym_core/tests/module_diffing.rs +++ b/resym_core/tests/module_diffing.rs @@ -23,6 +23,7 @@ fn test_module_diffing_by_path() { TEST_MODULE_PATH, PrimitiveReconstructionFlavor::Portable, true, + true, ) .unwrap_or_else(|err| panic!("module diffing failed: {err}")); diff --git a/resym_core/tests/module_dumping.rs b/resym_core/tests/module_dumping.rs index 30fcaf9..bbd7d86 100644 --- a/resym_core/tests/module_dumping.rs +++ b/resym_core/tests/module_dumping.rs @@ -11,7 +11,11 @@ fn test_module_dumping_by_path_portable() { let pdb_file = PdbFile::load_from_file(Path::new(TEST_PDB_FILE_PATH)).expect("load test.pdb"); let module_dump = pdb_file - .reconstruct_module_by_path(TEST_MODULE_PATH, PrimitiveReconstructionFlavor::Portable) + .reconstruct_module_by_path( + TEST_MODULE_PATH, + PrimitiveReconstructionFlavor::Portable, + true, + ) .unwrap_or_else(|err| panic!("module dumping failed: {err}")); insta::assert_snapshot!("module_dumping_by_path_portable", module_dump); @@ -23,6 +27,7 @@ fn test_module_dumping_by_index_portable() { "module_dumping_by_index_portable", TEST_MODULE_INDEX, PrimitiveReconstructionFlavor::Portable, + true, ); } @@ -32,6 +37,7 @@ fn test_module_dumping_by_index_microsoft() { "module_dumping_by_index_microsoft", TEST_MODULE_INDEX, PrimitiveReconstructionFlavor::Microsoft, + true, ); } @@ -41,6 +47,7 @@ fn test_module_dumping_by_index_raw() { "module_dumping_by_index_raw", TEST_MODULE_INDEX, PrimitiveReconstructionFlavor::Raw, + true, ); } @@ -48,11 +55,12 @@ fn test_module_dumping_by_index_internal( snapshot_name: &str, module_index: usize, primitives_flavor: PrimitiveReconstructionFlavor, + print_access_specifier: bool, ) { let pdb_file = PdbFile::load_from_file(Path::new(TEST_PDB_FILE_PATH)).expect("load test.pdb"); let module_dump = pdb_file - .reconstruct_module_by_index(module_index, primitives_flavor) + .reconstruct_module_by_index(module_index, primitives_flavor, print_access_specifier) .unwrap_or_else(|_| panic!("module dumping")); insta::assert_snapshot!(snapshot_name, module_dump); diff --git a/resym_core/tests/snapshots/module_diffing__module_diffing_by_path.snap b/resym_core/tests/snapshots/module_diffing__module_diffing_by_path.snap index d533dbb..1c547df 100644 --- a/resym_core/tests/snapshots/module_diffing__module_diffing_by_path.snap +++ b/resym_core/tests/snapshots/module_diffing__module_diffing_by_path.snap @@ -15,21 +15,21 @@ expression: module_diff.data // using namespace std; - int32_t (* pre_c_initializer)(); - int32_t (* post_pgo_initializer)(); - void (* pre_cpp_initializer)(); + static int32_t (* pre_c_initializer)(); // RVA=0x19440 + static int32_t (* post_pgo_initializer)(); // RVA=0x19550 + static void (* pre_cpp_initializer)(); // RVA=0x19110 using PUWSTR_C = const wchar_t*; using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3; - int32_t (__scrt_common_main)(); // CodeSize=19 - int32_t (__scrt_common_main_seh)(); // CodeSize=414 - void `__scrt_common_main_seh'::`1'::filt$0(); // CodeSize=48 (missing type information) - int32_t (__scrt_narrow_argv_policy::__scrt_narrow_argv_policy::configure_argv)(); // CodeSize=21 - int32_t (__scrt_narrow_environment_policy::__scrt_narrow_environment_policy::initialize_environment)(); // CodeSize=14 - int32_t (invoke_main)(); // CodeSize=62 - int32_t (post_pgo_initialization)(); // CodeSize=16 - int32_t (pre_c_initialization)(); // CodeSize=178 - void (pre_cpp_initialization)(); // CodeSize=26 - void (__scrt_main_policy::__scrt_main_policy::set_app_type)(); // CodeSize=19 - void (__scrt_file_policy::__scrt_file_policy::set_commode)(); // CodeSize=29 - void (__scrt_file_policy::__scrt_file_policy::set_fmode)(); // CodeSize=21 - uint32_t (mainCRTStartup)(void*); // CodeSize=19 + static int32_t (__scrt_common_main)(); // RVA=0x11e80 CodeSize=0x13 + static int32_t (__scrt_common_main_seh)(); // RVA=0x11ea0 CodeSize=0x19e + static void `__scrt_common_main_seh'::`1'::filt$0(); // RVA=0x17bd0 CodeSize=0x30 (missing type information) + int32_t (__scrt_narrow_argv_policy::__scrt_narrow_argv_policy::configure_argv)(); // RVA=0x120b0 CodeSize=0x15 + int32_t (__scrt_narrow_environment_policy::__scrt_narrow_environment_policy::initialize_environment)(); // RVA=0x120d0 CodeSize=0xe + static int32_t (invoke_main)(); // RVA=0x120f0 CodeSize=0x3e + static int32_t (post_pgo_initialization)(); // RVA=0x11e40 CodeSize=0x10 + static int32_t (pre_c_initialization)(); // RVA=0x11d60 CodeSize=0xb2 + static void (pre_cpp_initialization)(); // RVA=0x11e60 CodeSize=0x1a + void (__scrt_main_policy::__scrt_main_policy::set_app_type)(); // RVA=0x12140 CodeSize=0x13 + void (__scrt_file_policy::__scrt_file_policy::set_commode)(); // RVA=0x12160 CodeSize=0x1d + void (__scrt_file_policy::__scrt_file_policy::set_fmode)(); // RVA=0x12190 CodeSize=0x15 + uint32_t (mainCRTStartup)(void*); // RVA=0x121b0 CodeSize=0x13 diff --git a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_microsoft.snap b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_microsoft.snap index 955e218..ae452f1 100644 --- a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_microsoft.snap +++ b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_microsoft.snap @@ -5,6 +5,6 @@ expression: module_dump using namespace std; using PUWSTR_C = const WCHAR*; using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3; -PULONGLONG (__local_stdio_scanf_options)(); // CodeSize=8 -ULONGLONG _OptionsStorage; -VOID (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69 +PULONGLONG (__local_stdio_scanf_options)(); // RVA=0x14670 CodeSize=0x8 +static ULONGLONG _OptionsStorage; // RVA=0x1eb28 +VOID (__scrt_initialize_default_local_stdio_options)(); // RVA=0x14680 CodeSize=0x45 diff --git a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_portable.snap b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_portable.snap index 2c7db53..872b8e6 100644 --- a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_portable.snap +++ b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_portable.snap @@ -5,6 +5,6 @@ expression: module_dump using namespace std; using PUWSTR_C = const wchar_t*; using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3; -uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8 -uint64_t _OptionsStorage; -void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69 +uint64_t* (__local_stdio_scanf_options)(); // RVA=0x14670 CodeSize=0x8 +static uint64_t _OptionsStorage; // RVA=0x1eb28 +void (__scrt_initialize_default_local_stdio_options)(); // RVA=0x14680 CodeSize=0x45 diff --git a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_raw.snap b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_raw.snap index 2a91309..f3e69e0 100644 --- a/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_raw.snap +++ b/resym_core/tests/snapshots/module_dumping__module_dumping_by_index_raw.snap @@ -5,6 +5,6 @@ expression: module_dump using namespace std; using PUWSTR_C = const wchar_t*; using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3; -unsigned long long int* (__local_stdio_scanf_options)(); // CodeSize=8 -unsigned long long int _OptionsStorage; -void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69 +unsigned long long int* (__local_stdio_scanf_options)(); // RVA=0x14670 CodeSize=0x8 +static unsigned long long int _OptionsStorage; // RVA=0x1eb28 +void (__scrt_initialize_default_local_stdio_options)(); // RVA=0x14680 CodeSize=0x45 diff --git a/resym_core/tests/snapshots/module_dumping__module_dumping_by_path_portable.snap b/resym_core/tests/snapshots/module_dumping__module_dumping_by_path_portable.snap index 2c7db53..872b8e6 100644 --- a/resym_core/tests/snapshots/module_dumping__module_dumping_by_path_portable.snap +++ b/resym_core/tests/snapshots/module_dumping__module_dumping_by_path_portable.snap @@ -5,6 +5,6 @@ expression: module_dump using namespace std; using PUWSTR_C = const wchar_t*; using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3; -uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8 -uint64_t _OptionsStorage; -void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69 +uint64_t* (__local_stdio_scanf_options)(); // RVA=0x14670 CodeSize=0x8 +static uint64_t _OptionsStorage; // RVA=0x1eb28 +void (__scrt_initialize_default_local_stdio_options)(); // RVA=0x14680 CodeSize=0x45 diff --git a/resymc/src/main.rs b/resymc/src/main.rs index 61dab5f..56c6d92 100644 --- a/resymc/src/main.rs +++ b/resymc/src/main.rs @@ -116,12 +116,14 @@ fn main() -> Result<()> { output_file_path, primitive_types_flavor, print_header, + print_access_specifiers, highlight_syntax, } => app.dump_module_command( pdb_path, module_id, primitive_types_flavor.unwrap_or(DEFAULT_PRIMITIVE_FLAVOR), print_header, + print_access_specifiers, highlight_syntax, output_file_path, ), @@ -132,6 +134,7 @@ fn main() -> Result<()> { output_file_path, primitive_types_flavor, print_header, + print_access_specifiers, highlight_syntax, } => app.diff_module_command( from_pdb_path, @@ -139,6 +142,58 @@ fn main() -> Result<()> { module_path, primitive_types_flavor.unwrap_or(DEFAULT_PRIMITIVE_FLAVOR), print_header, + print_access_specifiers, + highlight_syntax, + output_file_path, + ), + ResymcOptions::ListSymbols { + pdb_path, + symbol_name_filter, + output_file_path, + case_insensitive, + use_regex, + ignore_std_types, + } => app.list_symbols_command( + pdb_path, + symbol_name_filter, + case_insensitive, + use_regex, + ignore_std_types, + output_file_path, + ), + ResymcOptions::DumpSymbol { + pdb_path, + symbol_name, + output_file_path, + primitive_types_flavor, + print_header, + print_access_specifiers, + highlight_syntax, + } => app.dump_symbol_command( + pdb_path, + Some(symbol_name), + primitive_types_flavor.unwrap_or(DEFAULT_PRIMITIVE_FLAVOR), + print_header, + print_access_specifiers, + highlight_syntax, + output_file_path, + ), + ResymcOptions::DiffSymbol { + from_pdb_path, + to_pdb_path, + symbol_name, + output_file_path, + primitive_types_flavor, + print_header, + print_access_specifiers, + highlight_syntax, + } => app.diff_symbol_command( + from_pdb_path, + to_pdb_path, + symbol_name, + primitive_types_flavor.unwrap_or(DEFAULT_PRIMITIVE_FLAVOR), + print_header, + print_access_specifiers, highlight_syntax, output_file_path, ), diff --git a/resymc/src/resymc_app.rs b/resymc/src/resymc_app.rs index 076d7f0..91e0c00 100644 --- a/resymc/src/resymc_app.rs +++ b/resymc/src/resymc_app.rs @@ -73,7 +73,7 @@ impl ResymcApp { if let Some(output_file_path) = output_file_path { let mut output_file = File::create(output_file_path)?; for (type_name, _) in type_list { - writeln!(output_file, "{}", &type_name)?; + writeln!(output_file, "{type_name}")?; } } else { for (type_name, _) in type_list { @@ -284,7 +284,7 @@ impl ResymcApp { use_regex, ))?; // Wait for the backend to finish listing modules - if let FrontendCommand::UpdateModuleList(module_list_result) = + if let FrontendCommand::ListModulesResult(module_list_result) = self.frontend_controller.rx_ui.recv()? { // Dump output @@ -306,12 +306,14 @@ impl ResymcApp { } } + #[allow(clippy::too_many_arguments)] pub fn dump_module_command( &self, pdb_path: PathBuf, module_id: usize, primitive_types_flavor: PrimitiveReconstructionFlavor, print_header: bool, + print_access_specifiers: bool, highlight_syntax: bool, output_file_path: Option, ) -> Result<()> { @@ -334,6 +336,7 @@ impl ResymcApp { module_id, primitive_types_flavor, print_header, + print_access_specifiers, ))?; // Wait for the backend to finish filtering types if let FrontendCommand::ReconstructModuleResult(reconstructed_module) = @@ -368,6 +371,7 @@ impl ResymcApp { module_path: String, primitive_types_flavor: PrimitiveReconstructionFlavor, print_header: bool, + print_access_specifiers: bool, highlight_syntax: bool, output_file_path: Option, ) -> Result<()> { @@ -414,6 +418,7 @@ impl ResymcApp { module_path, primitive_types_flavor, print_header, + print_access_specifiers, ))?; // Wait for the backend to finish if let FrontendCommand::DiffResult(reconstructed_module_diff_result) = @@ -450,6 +455,225 @@ impl ResymcApp { Err(anyhow!("Invalid response received from the backend?")) } } + + pub fn list_symbols_command( + &self, + pdb_path: PathBuf, + symbol_name_filter: String, + case_insensitive: bool, + use_regex: bool, + ignore_std_types: bool, + output_file_path: Option, + ) -> Result<()> { + // Request the backend to load the PDB + self.backend + .send_command(BackendCommand::LoadPDBFromPath(PDB_MAIN_SLOT, pdb_path))?; + // Wait for the backend to finish loading the PDB + if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? { + if let Err(err) = result { + return Err(anyhow!("Failed to load PDB: {}", err)); + } + } else { + return Err(anyhow!( + "LoadPDBResult expected. Invalid response received from the backend?" + )); + } + + // Queue a request for the backend to return the list of all modules + self.backend.send_command(BackendCommand::ListSymbols( + PDB_MAIN_SLOT, + symbol_name_filter, + case_insensitive, + use_regex, + ignore_std_types, + ))?; + // Wait for the backend to finish listing modules + if let FrontendCommand::ListSymbolsResult(symbol_list) = + self.frontend_controller.rx_ui.recv()? + { + // Dump output + if let Some(output_file_path) = output_file_path { + let mut output_file = File::create(output_file_path)?; + for (symbol_name, _) in symbol_list { + writeln!(output_file, "{symbol_name}")?; + } + } else { + for (symbol_name, _) in symbol_list { + println!("{symbol_name}"); + } + } + + Ok(()) + } else { + Err(anyhow!( + "ListSymbolsResult expected. Invalid response received from the backend?" + )) + } + } + + #[allow(clippy::too_many_arguments)] + pub fn dump_symbol_command( + &self, + pdb_path: PathBuf, + symbol_name: Option, + primitive_types_flavor: PrimitiveReconstructionFlavor, + print_header: bool, + print_access_specifiers: bool, + highlight_syntax: bool, + output_file_path: Option, + ) -> Result<()> { + // Request the backend to load the PDB + self.backend + .send_command(BackendCommand::LoadPDBFromPath(PDB_MAIN_SLOT, pdb_path))?; + // Wait for the backend to finish loading the PDB + if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? { + if let Err(err) = result { + return Err(anyhow!("Failed to load PDB: {}", err)); + } + } else { + return Err(anyhow!( + "LoadPDBFromPath expected. Invalid response received from the backend?" + )); + } + + // Queue a request for the backend to reconstruct the given module + if let Some(symbol_name) = symbol_name { + self.backend + .send_command(BackendCommand::ReconstructSymbolByName( + PDB_MAIN_SLOT, + symbol_name, + primitive_types_flavor, + print_header, + print_access_specifiers, + ))?; + } else { + // TODO: dump all symbols + } + // Wait for the backend to finish filtering types + if let FrontendCommand::ReconstructSymbolResult(reconstructed_symbol_result) = + self.frontend_controller.rx_ui.recv()? + { + let reconstructed_symbol = reconstructed_symbol_result?; + // Dump output + if let Some(output_file_path) = output_file_path { + let mut output_file = File::create(output_file_path)?; + output_file.write_all(reconstructed_symbol.as_bytes())?; + } else if highlight_syntax { + let theme = CodeTheme::default(); + if let Some(colorized_reconstructed_type) = + highlight_code(&theme, &reconstructed_symbol, None) + { + println!("{colorized_reconstructed_type}"); + } + } else { + println!("{reconstructed_symbol}"); + } + Ok(()) + } else { + Err(anyhow!( + "ReconstructSymbolResult expected. Invalid response received from the backend?" + )) + } + } + + #[allow(clippy::too_many_arguments)] + pub fn diff_symbol_command( + &self, + from_pdb_path: PathBuf, + to_pdb_path: PathBuf, + symbol_name: String, + primitive_types_flavor: PrimitiveReconstructionFlavor, + print_header: bool, + print_access_specifiers: bool, + highlight_syntax: bool, + output_file_path: Option, + ) -> Result<()> { + // Request the backend to load the first PDB + self.backend.send_command(BackendCommand::LoadPDBFromPath( + PDB_MAIN_SLOT, + from_pdb_path.clone(), + ))?; + // Wait for the backend to finish loading the PDB + if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? { + if let Err(err) = result { + return Err(anyhow!( + "Failed to load PDB '{}': {}", + from_pdb_path.display(), + err + )); + } + } else { + return Err(anyhow!( + "LoadPDBResult expected. Invalid response received from the backend?" + )); + } + + // Request the backend to load the second PDB + self.backend.send_command(BackendCommand::LoadPDBFromPath( + PDB_DIFF_TO_SLOT, + to_pdb_path.clone(), + ))?; + // Wait for the backend to finish loading the PDB + if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? { + if let Err(err) = result { + return Err(anyhow!( + "Failed to load PDB '{}': {}", + to_pdb_path.display(), + err + )); + } + } else { + return Err(anyhow!( + "LoadPDBResult expected. Invalid response received from the backend?" + )); + } + + // Queue a request for the backend to diff the given module + self.backend.send_command(BackendCommand::DiffSymbolByName( + PDB_MAIN_SLOT, + PDB_DIFF_TO_SLOT, + symbol_name, + primitive_types_flavor, + print_header, + print_access_specifiers, + ))?; + // Wait for the backend to finish + if let FrontendCommand::DiffResult(reconstructed_symbol_diff_result) = + self.frontend_controller.rx_ui.recv()? + { + let reconstructed_symbol_diff = reconstructed_symbol_diff_result?; + // Dump output + if let Some(output_file_path) = output_file_path { + let mut output_file = File::create(output_file_path)?; + output_file.write_all(reconstructed_symbol_diff.data.as_bytes())?; + } else if highlight_syntax { + let theme = CodeTheme::default(); + let line_descriptions = + reconstructed_symbol_diff + .metadata + .iter() + .fold(vec![], |mut acc, e| { + acc.push(e.1); + acc + }); + if let Some(colorized_reconstructed_module) = highlight_code( + &theme, + &reconstructed_symbol_diff.data, + Some(line_descriptions), + ) { + println!("{colorized_reconstructed_module}"); + } + } else { + println!("{}", reconstructed_symbol_diff.data); + } + + Ok(()) + } else { + Err(anyhow!( + "DiffResult expected. Invalid response received from the backend?" + )) + } + } } #[cfg(test)] @@ -756,6 +980,7 @@ mod tests { PrimitiveReconstructionFlavor::Microsoft, false, false, + false, None ) .is_err()); @@ -773,6 +998,7 @@ mod tests { PrimitiveReconstructionFlavor::Microsoft, true, true, + true, None ) .is_ok()); @@ -793,6 +1019,7 @@ mod tests { PrimitiveReconstructionFlavor::Portable, false, false, + false, Some(output_path.clone()), ) .is_ok()); @@ -805,9 +1032,9 @@ mod tests { "using namespace std;\n", "using PUWSTR_C = const wchar_t*;\n", "using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;\n", - "uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8\n", - "uint64_t _OptionsStorage;\n", - "void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69\n", + "uint64_t* (__local_stdio_scanf_options)(); // RVA=0x14670 CodeSize=0x8\n", + "static uint64_t _OptionsStorage; // RVA=0x1eb28 \n", + "void (__scrt_initialize_default_local_stdio_options)(); // RVA=0x14680 CodeSize=0x45\n", ) ); } @@ -828,6 +1055,7 @@ mod tests { PrimitiveReconstructionFlavor::Microsoft, false, false, + false, None ) .is_err()); @@ -847,7 +1075,7 @@ mod tests { "d:\\a01\\_work\\43\\s\\Intermediate\\vctools\\msvcrt.nativeproj_607447030\\objd\\amd64\\exe_main.obj".to_string(), PrimitiveReconstructionFlavor::Microsoft, true, - true, + true,true, None ) .is_ok()); @@ -872,6 +1100,7 @@ mod tests { PrimitiveReconstructionFlavor::Portable, false, false, + false, Some(output_path.clone()), ) .is_ok()); @@ -884,9 +1113,9 @@ mod tests { " using namespace std;\n", " using PUWSTR_C = const wchar_t*;\n", " using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;\n", - " uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8\n", - " uint64_t _OptionsStorage;\n", - " void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69\n", + " uint64_t* (__local_stdio_scanf_options)(); // RVA=0x13c30 CodeSize=0x8\n", + " static uint64_t _OptionsStorage; // RVA=0x1c898 \n", + " void (__scrt_initialize_default_local_stdio_options)(); // RVA=0x13c40 CodeSize=0x45\n", ) ); } diff --git a/resymc/src/resymc_options.rs b/resymc/src/resymc_options.rs index a705b5d..93129d5 100644 --- a/resymc/src/resymc_options.rs +++ b/resymc/src/resymc_options.rs @@ -136,6 +136,9 @@ pub enum ResymcOptions { /// Print header #[structopt(short = "h", long)] print_header: bool, + /// Print C++ access specifiers + #[structopt(short = "a", long)] + print_access_specifiers: bool, /// Highlight C++ output #[structopt(short = "H", long)] highlight_syntax: bool, @@ -156,6 +159,71 @@ pub enum ResymcOptions { /// Print header #[structopt(short = "h", long)] print_header: bool, + /// Print C++ access specifiers + #[structopt(short = "a", long)] + print_access_specifiers: bool, + /// Highlight C++ output and add/deleted lines + #[structopt(short = "H", long)] + highlight_syntax: bool, + }, + /// List symbols from a given PDB file + ListSymbols { + /// Path to the PDB file + pdb_path: PathBuf, + /// Search filter + symbol_name_filter: String, + /// Path of the output file + output_file_path: Option, + /// Do not match case + #[structopt(short = "i", long)] + case_insensitive: bool, + /// Use regular expressions + #[structopt(short = "r", long)] + use_regex: bool, + /// Filter out types in the `std` namespace + #[structopt(short = "s", long)] + ignore_std_types: bool, + }, + /// Dump symbol from a given PDB file + DumpSymbol { + /// Path to the PDB file + pdb_path: PathBuf, + /// Name of the symbol to dump + symbol_name: String, + /// Path of the output file + output_file_path: Option, + /// Representation of primitive types + #[structopt(short = "f", long)] + primitive_types_flavor: Option, + /// Print header + #[structopt(short = "h", long)] + print_header: bool, + /// Print C++ access specifiers + #[structopt(short = "a", long)] + print_access_specifiers: bool, + /// Highlight C++ output + #[structopt(short = "H", long)] + highlight_syntax: bool, + }, + /// Compute diff for a symbol between two given PDB files + DiffSymbol { + /// Path of the PDB file to compute the diff from + from_pdb_path: PathBuf, + /// Path of the PDB file to compute the diff to + to_pdb_path: PathBuf, + /// Name of the module to diff + symbol_name: String, + /// Path of the output file + output_file_path: Option, + /// Representation of primitive types + #[structopt(short = "f", long)] + primitive_types_flavor: Option, + /// Print header + #[structopt(short = "h", long)] + print_header: bool, + /// Print C++ access specifiers + #[structopt(short = "a", long)] + print_access_specifiers: bool, /// Highlight C++ output and add/deleted lines #[structopt(short = "H", long)] highlight_syntax: bool,