From e3a77429b3c43114790c7082ea6d0605b4cd991c Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 11 Aug 2026 08:14:35 -0700 Subject: [PATCH 1/4] [DWARF] Build the anonymous function type placeholder only once Every anonymous subroutine type produces the same placeholder regardless of its return type, because a named type reference takes only its class, width and alignment from its target, and function types set neither. --- .../dwarf/dwarf_import/src/die_handlers.rs | 22 ++++++++++++++----- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 18 +++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/plugins/dwarf/dwarf_import/src/die_handlers.rs b/plugins/dwarf/dwarf_import/src/die_handlers.rs index a803afdac2..9750ca0fb7 100644 --- a/plugins/dwarf/dwarf_import/src/die_handlers.rs +++ b/plugins/dwarf/dwarf_import/src/die_handlers.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext, TypeUID}; +use crate::dwarfdebuginfo::{ + DebugInfoBuilder, DebugInfoBuilderContext, TypeUID, UNNAMED_FUNCTION_NAME, +}; use crate::types::get_type; use crate::{helpers::*, ReaderType}; @@ -333,11 +335,19 @@ pub(crate) fn handle_function( }; // Alias function type in the case that it contains itself - let name = debug_info_builder_context - .get_name(dwarf, unit, entry) - .unwrap_or("_unnamed_func".to_string()); - let ntr = - Type::named_type_from_type(&name, &Type::function(return_type.as_ref(), vec![], false)); + let (name, ntr) = match debug_info_builder_context.get_name(dwarf, unit, entry) { + Some(name) => { + let ntr = Type::named_type_from_type( + &name, + &Type::function(return_type.as_ref(), vec![], false), + ); + (name, ntr) + } + None => { + let ntr = debug_info_builder.unnamed_function_placeholder(return_type.as_ref()); + (UNNAMED_FUNCTION_NAME.to_string(), ntr) + } + }; debug_info_builder.add_type(get_uid(dwarf, unit, entry), name, ntr, false, None); let mut parameters: Vec = vec![]; diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index d2f90a8b6f..3b96b143da 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -101,6 +101,9 @@ impl FunctionInfoBuilder { ////////////////////// // DebugInfoBuilder +// The name given to a subroutine type that has none of its own. +pub(crate) const UNNAMED_FUNCTION_NAME: &str = "_unnamed_func"; + // TODO : Don't make this pub...fix the value thing pub(crate) struct DebugType { pub name: String, @@ -218,6 +221,7 @@ pub(crate) struct DebugInfoBuilder { types: IndexMap, data_variables: HashMap, TypeUID)>, range_data_offsets: iset::IntervalMap, + unnamed_function_placeholder: Option>, } impl DebugInfoBuilder { @@ -229,9 +233,23 @@ impl DebugInfoBuilder { types: IndexMap::new(), data_variables: HashMap::new(), range_data_offsets: iset::IntervalMap::new(), + unnamed_function_placeholder: None, } } + // Function types carry no width or alignment of their own, so every anonymous subroutine + // produces the same placeholder whatever it returns. + pub(crate) fn unnamed_function_placeholder(&mut self, return_type: &Type) -> Ref { + self.unnamed_function_placeholder + .get_or_insert_with(|| { + Type::named_type_from_type( + UNNAMED_FUNCTION_NAME, + &Type::function(return_type, vec![], false), + ) + }) + .clone() + } + pub(crate) fn set_range_data_offsets(&mut self, offsets: iset::IntervalMap) { self.range_data_offsets = offsets } From a9c94dca72a92477d643f5a548a72e9930fc5051 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 11 Aug 2026 08:14:40 -0700 Subject: [PATCH 2/4] [DWARF] Reuse type placeholders across repeated declarations A placeholder is fully determined by its name plus the class, structure variant, width and alignment of its target, which are the only things `InitNamedType` reads from it. --- .../dwarf/dwarf_import/src/die_handlers.rs | 6 ++- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 54 ++++++++++++++++++- plugins/dwarf/dwarf_import/src/types.rs | 3 +- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/plugins/dwarf/dwarf_import/src/die_handlers.rs b/plugins/dwarf/dwarf_import/src/die_handlers.rs index 9750ca0fb7..7ea4a1b69c 100644 --- a/plugins/dwarf/dwarf_import/src/die_handlers.rs +++ b/plugins/dwarf/dwarf_import/src/die_handlers.rs @@ -175,8 +175,10 @@ pub(crate) fn handle_typedef( // This will fail in the case where we have a typedef to a type that doesn't exist (failed to parse, incomplete, etc) if let Some(entry_type_offset) = entry_type { if let Some(t) = debug_info_builder.get_type(entry_type_offset) { - let typedef_type = Type::named_type_from_type(typedef_name, &t.get_type()); - return (Some(typedef_type), typedef_name != t.name); + let target = t.get_type(); + let renames_target = typedef_name != t.name; + let typedef_type = debug_info_builder.typedef_placeholder(typedef_name, &target); + return (Some(typedef_type), renames_target); } } diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 3b96b143da..dee3d5e653 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -25,7 +25,7 @@ use binaryninja::{ platform::Platform, rc::*, symbol::SymbolType, - types::{FunctionParameter, Type}, + types::{FunctionParameter, StructureBuilder, StructureType, Type, TypeClass}, variable::NamedVariableWithType, }; @@ -221,6 +221,9 @@ pub(crate) struct DebugInfoBuilder { types: IndexMap, data_variables: HashMap, TypeUID)>, range_data_offsets: iset::IntervalMap, + structure_placeholders: HashMap<(String, StructureType, u64), Ref>, + typedef_placeholders: + HashMap<(String, TypeClass, Option, u64, usize), Ref>, unnamed_function_placeholder: Option>, } @@ -233,10 +236,59 @@ impl DebugInfoBuilder { types: IndexMap::new(), data_variables: HashMap::new(), range_data_offsets: iset::IntervalMap::new(), + structure_placeholders: HashMap::new(), + typedef_placeholders: HashMap::new(), unnamed_function_placeholder: None, } } + // A placeholder named type reference carries nothing but its name and the reference class, + // width and alignment that its target contributes, so the same declaration appearing in + // another compilation unit produces the same placeholder. Debug info repeats the common + // typedefs and structures in every unit that includes their header, so building each one once + // saves marshalling the name across the API, building the target, and asking the core for a + // reference, every time after the first. + pub(crate) fn structure_placeholder( + &mut self, + name: &str, + structure_type: StructureType, + size: u64, + ) -> Ref { + let key = (name.to_string(), structure_type, size); + if let Some(existing) = self.structure_placeholders.get(&key) { + return existing.clone(); + } + + let mut structure_builder = StructureBuilder::new(); + structure_builder + .packed(true) + .width(size) + .structure_type(structure_type); + let placeholder = + Type::named_type_from_type(name, &Type::structure(&structure_builder.finalize())); + self.structure_placeholders.insert(key, placeholder.clone()); + placeholder + } + + pub(crate) fn typedef_placeholder(&mut self, name: &str, target: &Type) -> Ref { + // A structure target gives the reference its class according to whether it is a struct, a + // union or a class, so which of those it is has to identify the placeholder as well. + let key = ( + name.to_string(), + target.type_class(), + target.get_structure().map(|s| s.structure_type()), + target.width(), + target.alignment(), + ); + if let Some(existing) = self.typedef_placeholders.get(&key) { + return existing.clone(); + } + + let placeholder = Type::named_type_from_type(name, target); + self.typedef_placeholders.insert(key, placeholder.clone()); + placeholder + } + // Function types carry no width or alignment of their own, so every anonymous subroutine // produces the same placeholder whatever it returns. pub(crate) fn unnamed_function_placeholder(&mut self, return_type: &Type) -> Ref { diff --git a/plugins/dwarf/dwarf_import/src/types.rs b/plugins/dwarf/dwarf_import/src/types.rs index 7aea966c9a..c5bd59c69a 100644 --- a/plugins/dwarf/dwarf_import/src/types.rs +++ b/plugins/dwarf/dwarf_import/src/types.rs @@ -166,8 +166,7 @@ fn do_structure_parse( // This reference type will be used by any children to grab while we're still building this type // it will also be how any other types refer to this struct if let Some(full_name) = &full_name { - let ntr = - Type::named_type_from_type(full_name, &Type::structure(&structure_builder.finalize())); + let ntr = debug_info_builder.structure_placeholder(full_name, structure_type, size); debug_info_builder.add_type( get_uid(dwarf, unit, entry), full_name.to_owned(), From 85cfa5b8eee24356baba2cfce6500428900c3472 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 11 Aug 2026 08:36:48 -0700 Subject: [PATCH 3/4] [DWARF] Record a type's name only where the type is committed The name was recorded after the branches that commit it, so it was also recorded on the two paths that log an error and commit nothing. That dated from when there was a single commit path and the two were the same event. A name left behind by a failed commit made a later type of that name get deconflicted against something that was never stored. --- plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index dee3d5e653..e81c2ab1ac 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -692,6 +692,7 @@ impl DebugInfoBuilder { if let Some(target_uid) = debug_type.target_type_uid { if let Some(target_type) = self.get_type(target_uid) { debug_info.add_type(&debug_type_name, &target_type.get_type(), &[]); + type_uids_by_name.insert(debug_type_name, *debug_type_uid); } else { tracing::error!( "Failed to find typedef {} target for uid {}", @@ -708,8 +709,8 @@ impl DebugInfoBuilder { } } else { debug_info.add_type(&debug_type_name, &debug_type.ty, &[]); + type_uids_by_name.insert(debug_type_name, *debug_type_uid); } - type_uids_by_name.insert(debug_type_name, *debug_type_uid); } } From 30df27fdcca34b0e539fe5c87eb2f4e0192e9c33 Mon Sep 17 00:00:00 2001 From: Mark Rowe Date: Tue, 11 Aug 2026 08:14:46 -0700 Subject: [PATCH 4/4] [DWARF] Commit each distinct debug info type once, not once per declaration Debug info describes a type in every compilation unit that includes its header. Committing each occurrence made the core walk the whole member tree and re-resolve every named reference in it. Only the first occurrence is committed now, and later ones describing the same definition are skipped. A typedef's own type is the self-referential placeholder that stands in for it while its children are built, so the comparison uses the target that gets committed instead. --- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index e81c2ab1ac..72115e9e09 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -636,6 +636,17 @@ impl DebugInfoBuilder { } } + // What committing a type actually stores. A typedef contributes its target, because its own + // type is the self-referential placeholder that stands in for it while its children are built. + fn committed_type(&self, debug_type: &DebugType) -> Option> { + if debug_type.get_type().get_named_type_reference().is_none() { + return Some(debug_type.get_type()); + } + + let target_uid = debug_type.target_type_uid?; + Some(self.get_type(target_uid)?.get_type()) + } + fn commit_types(&self, debug_info: &mut DebugInfo) { let mut type_uids_by_name: HashMap = HashMap::new(); @@ -653,6 +664,20 @@ impl DebugInfoBuilder { continue; }; + // This name already describes this definition. Debug info repeats a type in every + // compilation unit that includes its header, so committing it again would have the + // core walk and re-resolve every named reference in it for no gain. + let same_definition = match ( + self.committed_type(stored_debug_type), + self.committed_type(debug_type), + ) { + (Some(stored), Some(current)) => stored.as_ref() == current.as_ref(), + _ => false, + }; + if same_definition { + continue; + } + let mut skip_adding_type = false; if stored_debug_type.ty != debug_type.ty { // We already stored a type with this name and it's a different type, deconflict the name and try again