summaryrefslogtreecommitdiff
path: root/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs')
-rw-r--r--plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs657
1 files changed, 657 insertions, 0 deletions
diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
new file mode 100644
index 00000000..6bf71489
--- /dev/null
+++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
@@ -0,0 +1,657 @@
+// Copyright 2021-2024 Vector 35 Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+ helpers::{get_uid, resolve_specification, DieReference},
+ ReaderType,
+};
+
+use binaryninja::{
+ binary_view::{BinaryView, BinaryViewBase, BinaryViewExt},
+ debuginfo::{DebugFunctionInfo, DebugInfo},
+ platform::Platform,
+ rc::*,
+ symbol::SymbolType,
+ template_simplifier::simplify_str_to_fqn,
+ types::{FunctionParameter, Type},
+ variable::NamedVariableWithType,
+};
+
+use gimli::{DebuggingInformationEntry, Dwarf, Unit};
+
+use binaryninja::confidence::Conf;
+use binaryninja::variable::{Variable, VariableSourceType};
+use indexmap::{map::Values, IndexMap};
+use log::{debug, error, warn};
+use std::{cmp::Ordering, collections::HashMap, hash::Hash};
+
+pub(crate) type TypeUID = usize;
+
+/////////////////////////
+// FunctionInfoBuilder
+
+// TODO : Function local variables
+#[derive(PartialEq, Eq, Hash)]
+pub(crate) struct FunctionInfoBuilder {
+ pub(crate) full_name: Option<String>,
+ pub(crate) raw_name: Option<String>,
+ pub(crate) return_type: Option<TypeUID>,
+ pub(crate) address: Option<u64>,
+ pub(crate) parameters: Vec<Option<(String, TypeUID)>>,
+ pub(crate) platform: Option<Ref<Platform>>,
+ pub(crate) variable_arguments: bool,
+ pub(crate) stack_variables: Vec<NamedVariableWithType>,
+ pub(crate) use_cfa: bool, //TODO actually store more info about the frame base
+}
+
+impl FunctionInfoBuilder {
+ pub(crate) fn update(
+ &mut self,
+ full_name: Option<String>,
+ raw_name: Option<String>,
+ return_type: Option<TypeUID>,
+ address: Option<u64>,
+ parameters: &Vec<Option<(String, TypeUID)>>,
+ ) {
+ if full_name.is_some() {
+ self.full_name = full_name;
+ }
+
+ if raw_name.is_some() {
+ self.raw_name = raw_name;
+ }
+
+ if return_type.is_some() {
+ self.return_type = return_type;
+ }
+
+ if address.is_some() {
+ self.address = address;
+ }
+
+ for (i, new_parameter) in parameters.iter().enumerate() {
+ match self.parameters.get(i) {
+ Some(None) => self.parameters[i] = new_parameter.clone(),
+ Some(Some(_)) => (),
+ // Some(Some((name, _))) if name.as_bytes().is_empty() => {
+ // self.parameters[i] = new_parameter
+ // }
+ // Some(Some((_, uid))) if *uid == 0 => self.parameters[i] = new_parameter, // TODO : This is a placebo....void types aren't actually UID 0
+ _ => self.parameters.push(new_parameter.clone()),
+ }
+ }
+ }
+}
+
+//////////////////////
+// DebugInfoBuilder
+
+// TODO : Don't make this pub...fix the value thing
+pub(crate) struct DebugType {
+ pub name: String,
+ pub ty: Ref<Type>,
+ pub commit: bool,
+}
+
+impl DebugType {
+ pub fn get_type(&self) -> Ref<Type> {
+ self.ty.clone()
+ }
+}
+
+pub(crate) struct DebugInfoBuilderContext<R: ReaderType> {
+ units: Vec<Unit<R>>,
+ sup_units: Vec<Unit<R>>,
+ names: HashMap<TypeUID, String>,
+ default_address_size: usize,
+ pub(crate) total_die_count: usize,
+ pub(crate) total_unit_size_bytes: usize,
+}
+
+impl<R: ReaderType> DebugInfoBuilderContext<R> {
+ pub(crate) fn new(view: &BinaryView, dwarf: &Dwarf<R>) -> Option<Self> {
+ let mut units = vec![];
+ let mut iter = dwarf.units();
+ while let Ok(Some(header)) = iter.next() {
+ if let Ok(unit) = dwarf.unit(header) {
+ units.push(unit);
+ } else {
+ error!("Unable to read DWARF information. File may be malformed or corrupted. Not applying debug info.");
+ return None;
+ }
+ }
+
+ let mut sup_units = vec![];
+ if let Some(sup_dwarf) = dwarf.sup() {
+ let mut sup_iter = sup_dwarf.units();
+ while let Ok(Some(header)) = sup_iter.next() {
+ if let Ok(unit) = sup_dwarf.unit(header) {
+ sup_units.push(unit);
+ } else {
+ error!("Unable to read supplementary DWARF information. File may be malformed or corrupted. Not applying debug info.");
+ return None;
+ }
+ }
+ }
+
+ Some(Self {
+ units,
+ sup_units,
+ names: HashMap::new(),
+ default_address_size: view.address_size(),
+ total_die_count: 0,
+ total_unit_size_bytes: 0,
+ })
+ }
+
+ pub(crate) fn units(&self) -> &[Unit<R>] {
+ &self.units
+ }
+
+ pub(crate) fn sup_units(&self) -> &[Unit<R>] {
+ &self.sup_units
+ }
+
+ pub(crate) fn default_address_size(&self) -> usize {
+ self.default_address_size
+ }
+
+ pub(crate) fn set_name(&mut self, die_uid: TypeUID, name: String) {
+ // die_uids need to be unique here
+ assert!(self.names.insert(die_uid, name).is_none());
+ }
+
+ pub(crate) fn get_name(
+ &self,
+ dwarf: &Dwarf<R>,
+ unit: &Unit<R>,
+ entry: &DebuggingInformationEntry<R>,
+ ) -> Option<String> {
+ match resolve_specification(dwarf, unit, entry, self) {
+ DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => self
+ .names
+ .get(&get_uid(
+ dwarf,
+ entry_unit,
+ &entry_unit.entry(entry_offset).unwrap(),
+ ))
+ .cloned(),
+ DieReference::Err => None,
+ }
+ }
+}
+
+// DWARF info is stored and displayed in a tree, but is really a graph
+// The purpose of this builder is to help resolve those graph edges by mapping partial function
+// info and types to one DIE's UID (T) before adding the completed info to BN's debug info
+pub(crate) struct DebugInfoBuilder {
+ functions: Vec<FunctionInfoBuilder>,
+ raw_function_name_indices: HashMap<String, usize>,
+ full_function_name_indices: HashMap<String, usize>,
+ types: IndexMap<TypeUID, DebugType>,
+ data_variables: HashMap<u64, (Option<String>, TypeUID)>,
+ range_data_offsets: iset::IntervalMap<u64, i64>,
+}
+
+impl DebugInfoBuilder {
+ pub(crate) fn new() -> Self {
+ Self {
+ functions: vec![],
+ raw_function_name_indices: HashMap::new(),
+ full_function_name_indices: HashMap::new(),
+ types: IndexMap::new(),
+ data_variables: HashMap::new(),
+ range_data_offsets: iset::IntervalMap::new(),
+ }
+ }
+
+ pub(crate) fn set_range_data_offsets(&mut self, offsets: iset::IntervalMap<u64, i64>) {
+ self.range_data_offsets = offsets
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) fn insert_function(
+ &mut self,
+ full_name: Option<String>,
+ raw_name: Option<String>,
+ return_type: Option<TypeUID>,
+ address: Option<u64>,
+ parameters: &Vec<Option<(String, TypeUID)>>,
+ variable_arguments: bool,
+ use_cfa: bool,
+ ) -> Option<usize> {
+ // Returns the index of the function
+ // Raw names should be the primary key, but if they don't exist, use the full name
+ // TODO : Consider further falling back on address/architecture
+
+ /*
+ If it has a raw_name and we know it, update it and return
+ Else if it has a full_name and we know it, update it and return
+ Else Add a new entry if we don't know the full_name or raw_name
+ */
+
+ if let Some(ident) = &raw_name {
+ // check if we already know about this raw name's index
+ // if we do, and the full name will change, remove the known full index if it exists
+ // update the function
+ // if the full name exists, update the stored index for the full name
+ if let Some(idx) = self.raw_function_name_indices.get(ident) {
+ let function = self.functions.get_mut(*idx).unwrap();
+
+ if function.full_name.is_some() && function.full_name != full_name {
+ self.full_function_name_indices
+ .remove(function.full_name.as_ref().unwrap());
+ }
+
+ function.update(full_name, raw_name, return_type, address, parameters);
+
+ if function.full_name.is_some() {
+ self.full_function_name_indices
+ .insert(function.full_name.clone().unwrap(), *idx);
+ }
+
+ return Some(*idx);
+ }
+ } else if let Some(ident) = &full_name {
+ // check if we already know about this full name's index
+ // if we do, and the raw name will change, remove the known raw index if it exists
+ // update the function
+ // if the raw name exists, update the stored index for the raw name
+ if let Some(idx) = self.full_function_name_indices.get(ident) {
+ let function = self.functions.get_mut(*idx).unwrap();
+
+ if function.raw_name.is_some() && function.raw_name != raw_name {
+ self.raw_function_name_indices
+ .remove(function.raw_name.as_ref().unwrap());
+ }
+
+ function.update(full_name, raw_name, return_type, address, parameters);
+
+ if function.raw_name.is_some() {
+ self.raw_function_name_indices
+ .insert(function.raw_name.clone().unwrap(), *idx);
+ }
+
+ return Some(*idx);
+ }
+ } else {
+ debug!("Function entry in DWARF without full or raw name.");
+ return None;
+ }
+
+ let function = FunctionInfoBuilder {
+ full_name,
+ raw_name,
+ return_type,
+ address,
+ parameters: parameters.clone(),
+ platform: None,
+ variable_arguments,
+ stack_variables: vec![],
+ use_cfa,
+ };
+
+ if let Some(n) = &function.full_name {
+ self.full_function_name_indices
+ .insert(n.clone(), self.functions.len());
+ }
+
+ if let Some(n) = &function.raw_name {
+ self.raw_function_name_indices
+ .insert(n.clone(), self.functions.len());
+ }
+
+ self.functions.push(function);
+ Some(self.functions.len() - 1)
+ }
+
+ pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] {
+ &self.functions
+ }
+
+ #[allow(dead_code)]
+ pub(crate) fn types(&self) -> Values<'_, TypeUID, DebugType> {
+ self.types.values()
+ }
+
+ pub(crate) fn add_type(&mut self, type_uid: TypeUID, name: String, t: Ref<Type>, commit: bool) {
+ if let Some(DebugType {
+ name: existing_name,
+ ty: existing_type,
+ commit: _,
+ }) = self.types.insert(
+ type_uid,
+ DebugType {
+ name: name.clone(),
+ ty: t.clone(),
+ commit,
+ },
+ ) {
+ if existing_type != t && commit {
+ warn!("DWARF info contains duplicate type definition. Overwriting type `{}` (named `{:?}`) with `{}` (named `{:?}`)",
+ existing_type,
+ existing_name,
+ t,
+ name
+ );
+ }
+ }
+ }
+
+ pub(crate) fn remove_type(&mut self, type_uid: TypeUID) {
+ self.types.swap_remove(&type_uid);
+ }
+
+ pub(crate) fn get_type(&self, type_uid: TypeUID) -> Option<&DebugType> {
+ self.types.get(&type_uid)
+ }
+
+ pub(crate) fn contains_type(&self, type_uid: TypeUID) -> bool {
+ self.types.contains_key(&type_uid)
+ }
+
+ pub(crate) fn add_stack_variable(
+ &mut self,
+ fn_idx: Option<usize>,
+ offset: i64,
+ name: Option<String>,
+ type_uid: Option<TypeUID>,
+ lexical_block: Option<&iset::IntervalSet<u64>>,
+ ) {
+ let name = match name {
+ Some(x) => {
+ if x.len() == 1 && x.chars().next() == Some('\x00') {
+ // Anonymous variable, generate name
+ format!("debug_var_{}", offset)
+ } else {
+ x
+ }
+ }
+ None => {
+ // Anonymous variable, generate name
+ format!("debug_var_{}", offset)
+ }
+ };
+
+ let Some(function_index) = fn_idx else {
+ // If we somehow lost track of what subprogram we're in or we're not actually in a subprogram
+ error!(
+ "Trying to add a local variable outside of a subprogram. Please report this issue."
+ );
+ return;
+ };
+
+ // Either get the known type or use a 0 confidence void type so we at least get the name applied
+ let ty = match type_uid {
+ Some(uid) => Conf::new(self.get_type(uid).unwrap().ty.clone(), 128),
+ None => Conf::new(Type::void(), 0),
+ };
+ let function = &mut self.functions[function_index];
+
+ // TODO: If we can't find a known offset can we try to guess somehow?
+
+ let Some(func_addr) = function.address else {
+ // If we somehow are processing a function's variables before the function is created
+ error!("Trying to add a local variable without a known function start. Please report this issue.");
+ return;
+ };
+
+ let Some(adjustment_at_variable_lifetime_start) = lexical_block
+ .and_then(|block_ranges| {
+ block_ranges
+ .unsorted_iter()
+ .find_map(|x| self.range_data_offsets.values_overlap(x.start).next())
+ })
+ .or_else(|| {
+ // Try using the offset at the adjustment 4 bytes after the function start, in case the function starts with a stack adjustment
+ // TODO: This is a decent heuristic but not perfect, since further adjustments could still be made
+ self.range_data_offsets.values_overlap(func_addr + 4).next()
+ })
+ .or_else(|| {
+ // If all else fails, use the function start address
+ self.range_data_offsets.values_overlap(func_addr).next()
+ })
+ else {
+ // Unknown why, but this is happening with MachO + external dSYM
+ debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CIE offset.", name, offset, func_addr);
+ return;
+ };
+
+ // TODO: handle non-sp frame bases
+ // TODO: if not in a lexical block these can be wrong, see https://github.com/Vector35/binaryninja-api/issues/5882#issuecomment-2406065057
+ let adjusted_offset = if function.use_cfa {
+ // Apply CFA offset to variable storage offset if DW_AT_frame_base is frame base is CFA
+ offset + adjustment_at_variable_lifetime_start
+ } else {
+ // If it's using SP, we know the SP offset is <SP offset> + (<entry SP CFA offset> - <SP CFA offset>)
+ let Some(adjustment_at_entry) =
+ self.range_data_offsets.values_overlap(func_addr).next()
+ else {
+ // Unknown why, but this is happening with MachO + external dSYM
+ debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CIE offset for function start.", name, offset, func_addr);
+ return;
+ };
+
+ offset + (adjustment_at_entry - adjustment_at_variable_lifetime_start)
+ };
+
+ if adjusted_offset > 0 {
+ // If we somehow end up with a positive sp offset
+ error!("Trying to add a local variable \"{}\" in function at {:#x} at positive storage offset {}. Please report this issue.", name, func_addr, adjusted_offset);
+ return;
+ }
+
+ let var = Variable::new(
+ VariableSourceType::StackVariableSourceType,
+ 0,
+ adjusted_offset,
+ );
+ function
+ .stack_variables
+ .push(NamedVariableWithType::new(var, ty, name, false));
+ }
+
+ pub(crate) fn add_data_variable(
+ &mut self,
+ address: u64,
+ name: Option<String>,
+ type_uid: TypeUID,
+ ) {
+ if let Some((_existing_name, existing_type_uid)) =
+ self.data_variables.insert(address, (name, type_uid))
+ {
+ let existing_type = self.get_type(existing_type_uid).unwrap().ty.as_ref();
+ let new_type = self.get_type(type_uid).unwrap().ty.as_ref();
+
+ if existing_type_uid != type_uid || existing_type != new_type {
+ warn!("DWARF info contains duplicate data variable definition. Overwriting data variable at 0x{:08x} (`{}`) with `{}`",
+ address,
+ existing_type,
+ new_type
+ );
+ }
+ }
+ }
+
+ fn commit_types(&self, debug_info: &mut DebugInfo) {
+ let mut type_uids_by_name: HashMap<String, TypeUID> = HashMap::new();
+
+ for (debug_type_uid, debug_type) in self.types.iter() {
+ if !debug_type.commit {
+ continue;
+ }
+
+ let mut debug_type_name = debug_type.name.clone();
+
+ // Prevent storing two types with the same name and differing definitions
+ if let Some(stored_uid) = type_uids_by_name.get(&debug_type_name) {
+ let Some(stored_debug_type) = self.types.get(stored_uid) else {
+ error!("Stored type name without storing a type! Please report this error. UID: {}, name: {}", stored_uid, debug_type_name);
+ 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
+ let mut i = 1;
+ loop {
+ if let Some(stored_uid) = type_uids_by_name.get(&debug_type_name) {
+ if debug_type_uid == stored_uid {
+ // We already have a type with this name but it's the same type so we're ok
+ skip_adding_type = true;
+ break;
+ }
+ if let Some(stored_debug_type) = self.types.get(stored_uid) {
+ if stored_debug_type.ty == debug_type.ty {
+ // We already have a type with this name but it's the same type so we're ok
+ skip_adding_type = true;
+ break;
+ }
+ }
+
+ debug_type_name = format!("{}_{}", debug_type.name, i);
+ i += 1;
+ } else {
+ // We found a unique name
+ break;
+ }
+ }
+ }
+
+ if skip_adding_type {
+ continue;
+ }
+ };
+
+ // TODO : Components
+ debug_info.add_type(&debug_type_name, &debug_type.ty, &[]);
+ type_uids_by_name.insert(debug_type_name, *debug_type_uid);
+ }
+ }
+
+ // TODO : Consume data?
+ fn commit_data_variables(&self, debug_info: &mut DebugInfo) {
+ for (&address, (name, type_uid)) in &self.data_variables {
+ assert!(debug_info.add_data_variable(
+ address,
+ &self.get_type(*type_uid).unwrap().ty,
+ name.clone(),
+ &[] // TODO : Components
+ ));
+ }
+ }
+
+ fn get_function_type(&self, function: &FunctionInfoBuilder) -> Ref<Type> {
+ let return_type = match function.return_type {
+ Some(return_type_id) => {
+ Conf::new(self.get_type(return_type_id).unwrap().ty.clone(), 128)
+ }
+ _ => Conf::new(Type::void(), 0),
+ };
+
+ let parameters: Vec<FunctionParameter> = function
+ .parameters
+ .iter()
+ .filter_map(|parameter| match parameter {
+ Some((name, 0)) => Some(FunctionParameter::new(Type::void(), name.clone(), None)),
+ Some((name, uid)) => Some(FunctionParameter::new(
+ self.get_type(*uid).unwrap().ty.clone(),
+ name.clone(),
+ None,
+ )),
+ _ => None,
+ })
+ .collect();
+
+ Type::function(&return_type, parameters, function.variable_arguments)
+ }
+
+ fn commit_functions(&self, debug_info: &mut DebugInfo) {
+ for function in self.functions() {
+ // let calling_convention: Option<Ref<CallingConvention<CoreArchitecture>>> = None;
+
+ debug_info.add_function(DebugFunctionInfo::new(
+ function.full_name.clone(),
+ function.full_name.clone(), // TODO : This should eventually be changed, but the "full_name" should probably be the unsimplified version, and the "short_name" should be the simplified version...currently the symbols view shows the full version, so changing it here too makes it look bad in the UI
+ function.raw_name.clone(),
+ Some(self.get_function_type(function)),
+ function.address,
+ function.platform.clone(),
+ vec![], // TODO : Components
+ function.stack_variables.clone(), // TODO: local non-stack variables
+ ));
+ }
+ }
+
+ pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self {
+ // When originally resolving names, we need to check:
+ // If there's already a name from binja that's "more correct" than what we found (has more namespaces)
+ // If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name
+ // This is no longer true, because DWARF doesn't provide platform information for functions, so we at least need to post-process thumb functions
+
+ for func in &mut self.functions {
+ // If the function's raw name already exists in the binary...
+ if let Some(raw_name) = &func.raw_name {
+ if let Some(symbol) = bv.symbol_by_raw_name(raw_name) {
+ // Link mangled names without addresses to existing symbols in the binary
+ if func.address.is_none() && func.raw_name.is_some() {
+ // DWARF doesn't contain GOT info, so remove any entries there...they will be wrong (relying on Binja's mechanisms for the GOT is good )
+ if symbol.sym_type() != SymbolType::ImportAddress {
+ func.address = Some(symbol.address());
+ }
+ }
+
+ if let Some(full_name) = &func.full_name {
+ let func_full_name = full_name;
+ let symbol_full_name = symbol.full_name();
+
+ // If our name has fewer namespaces than the existing name, assume we lost the namespace info
+ if simplify_str_to_fqn(func_full_name, true).items.len()
+ < simplify_str_to_fqn(symbol_full_name.clone(), true)
+ .items
+ .len()
+ {
+ func.full_name = Some(symbol_full_name.to_string());
+ }
+ }
+ }
+ }
+
+ if let Some(address) = func.address.as_mut() {
+ let (diff, overflowed) = bv.start().overflowing_sub(bv.original_image_base());
+ if !overflowed {
+ *address = (*address).overflowing_add(diff).0; // rebase the address
+ let existing_functions = bv.functions_at(*address);
+ match existing_functions.len().cmp(&1) {
+ Ordering::Greater => {
+ warn!("Multiple existing functions at address {address:08x}. One or more functions at this address may have the wrong platform information. Please report this binary.");
+ }
+ Ordering::Equal => {
+ func.platform = Some(existing_functions.get(0).platform())
+ }
+ Ordering::Less => {}
+ }
+ }
+ }
+ }
+
+ self
+ }
+
+ pub(crate) fn commit_info(&self, debug_info: &mut DebugInfo) {
+ self.commit_types(debug_info);
+ self.commit_data_variables(debug_info);
+ self.commit_functions(debug_info);
+ }
+}