diff options
| author | Josh Ferrell <josh@vector35.com> | 2024-06-13 14:13:11 -0400 |
|---|---|---|
| committer | Josh Ferrell <josh@vector35.com> | 2024-06-13 14:13:11 -0400 |
| commit | d75066545afdc9b5d0a52bd2518e43f683c671a5 (patch) | |
| tree | bed26d348a726fc02c1d44cbdc36a8056b44baaf /rust/examples | |
| parent | f3a440248cda4d0c850cd32e3aa127ab29e0bb3e (diff) | |
Apply stack variables from DWARF
Diffstat (limited to 'rust/examples')
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/Cargo.toml | 1 | ||||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs | 88 | ||||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/functions.rs | 4 | ||||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/helpers.rs | 2 | ||||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/lib.rs | 104 | ||||
| -rw-r--r-- | rust/examples/dwarf/dwarf_import/src/types.rs | 37 | ||||
| -rw-r--r-- | rust/examples/pdb-ng/src/parser.rs | 2 |
7 files changed, 211 insertions, 27 deletions
diff --git a/rust/examples/dwarf/dwarf_import/Cargo.toml b/rust/examples/dwarf/dwarf_import/Cargo.toml index c8cea44a..715838a7 100644 --- a/rust/examples/dwarf/dwarf_import/Cargo.toml +++ b/rust/examples/dwarf/dwarf_import/Cargo.toml @@ -12,3 +12,4 @@ dwarfreader = { path = "../shared/" } binaryninja = { path = "../../../" } gimli = "0.28" log = "0.4.20" +iset = "0.2.2" diff --git a/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 0d4fda98..f3cc7b08 100644 --- a/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -21,12 +21,13 @@ use binaryninja::{ rc::*, symbol::SymbolType, templatesimplifier::simplify_str_to_fqn, - types::{Conf, FunctionParameter, Type}, + types::{Conf, FunctionParameter, NamedTypedVariable, Type, Variable}, + binaryninjacore_sys::BNVariableSourceType, }; use gimli::{DebuggingInformationEntry, Dwarf, Reader, Unit}; -use log::{error, warn}; +use log::{debug, error, warn}; use std::{ cmp::Ordering, collections::{hash_map::Values, HashMap}, @@ -48,6 +49,7 @@ pub(crate) struct FunctionInfoBuilder { pub(crate) parameters: Vec<Option<(String, TypeUID)>>, pub(crate) platform: Option<Ref<Platform>>, pub(crate) variable_arguments: bool, + pub(crate) stack_variables: Vec<NamedTypedVariable>, } impl FunctionInfoBuilder { @@ -172,6 +174,7 @@ pub(crate) struct DebugInfoBuilder { full_function_name_indices: HashMap<String, usize>, types: HashMap<TypeUID, DebugType>, data_variables: HashMap<u64, (Option<String>, TypeUID)>, + range_data_offsets: iset::IntervalMap<u64, i64> } impl DebugInfoBuilder { @@ -182,9 +185,14 @@ impl DebugInfoBuilder { full_function_name_indices: HashMap::new(), types: HashMap::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, @@ -194,7 +202,8 @@ impl DebugInfoBuilder { address: Option<u64>, parameters: &Vec<Option<(String, TypeUID)>>, variable_arguments: 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 @@ -222,7 +231,7 @@ impl DebugInfoBuilder { self.full_function_name_indices.insert(function.full_name.clone().unwrap(), *idx); } - return; + return Some(*idx); } } @@ -244,13 +253,13 @@ impl DebugInfoBuilder { self.raw_function_name_indices.insert(function.raw_name.clone().unwrap(), *idx); } - return; + return Some(*idx); } } if raw_name.is_none() && full_name.is_none() { error!("Function entry in DWARF without full or raw name. Please report this issue."); - return; + return None; } let function = FunctionInfoBuilder { @@ -261,6 +270,7 @@ impl DebugInfoBuilder { parameters: parameters.clone(), platform: None, variable_arguments, + stack_variables: vec![], }; if let Some(n) = &function.full_name { @@ -272,6 +282,7 @@ impl DebugInfoBuilder { } self.functions.push(function); + Some(self.functions.len()-1) } pub(crate) fn functions(&self) -> &[FunctionInfoBuilder] { @@ -321,6 +332,70 @@ impl DebugInfoBuilder { self.types.get(&type_uid).is_some() } + + pub(crate) fn add_stack_variable( + &mut self, + fn_idx: Option<usize>, + offset: i64, + name: Option<String>, + type_uid: Option<TypeUID>, + ) { + 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 t = match type_uid { + Some(uid) => Conf::new(self.get_type(uid).unwrap().1, 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(offset_adjustment) = 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; + }; + + let adjusted_offset = offset - offset_adjustment; + + if adjusted_offset > 0 { + // If we somehow end up with a positive sp offset + error!("Trying to add a local variable at positive storage offset {}. Please report this issue.", adjusted_offset); + return; + } + + let var = Variable::new(BNVariableSourceType::StackVariableSourceType, 0, adjusted_offset); + function.stack_variables.push(NamedTypedVariable::new(var, name, t, false)); + + } + pub(crate) fn add_data_variable( &mut self, address: u64, @@ -399,6 +474,7 @@ impl DebugInfoBuilder { function.address, function.platform.clone(), vec![], // TODO : Components + function.stack_variables.clone(), // TODO: local non-stack variables )); } } diff --git a/rust/examples/dwarf/dwarf_import/src/functions.rs b/rust/examples/dwarf/dwarf_import/src/functions.rs index de1400d2..fabcc613 100644 --- a/rust/examples/dwarf/dwarf_import/src/functions.rs +++ b/rust/examples/dwarf/dwarf_import/src/functions.rs @@ -67,7 +67,7 @@ pub(crate) fn parse_function_entry<R: Reader<Offset = usize>>( entry: &DebuggingInformationEntry<R>, debug_info_builder_context: &DebugInfoBuilderContext<R>, debug_info_builder: &mut DebugInfoBuilder, -) { +) -> Option<usize> { // Collect function properties (if they exist in this DIE) let full_name = debug_info_builder_context.get_name(unit, entry); let raw_name = get_raw_name(unit, entry, debug_info_builder_context); @@ -75,5 +75,5 @@ pub(crate) fn parse_function_entry<R: Reader<Offset = usize>>( let address = get_start_address(unit, entry, debug_info_builder_context); let (parameters, variable_arguments) = get_parameters(unit, entry, debug_info_builder_context, debug_info_builder); - debug_info_builder.insert_function(full_name, raw_name, return_type, address, ¶meters, variable_arguments); + debug_info_builder.insert_function(full_name, raw_name, return_type, address, ¶meters, variable_arguments) } diff --git a/rust/examples/dwarf/dwarf_import/src/helpers.rs b/rust/examples/dwarf/dwarf_import/src/helpers.rs index babe9713..14de90bf 100644 --- a/rust/examples/dwarf/dwarf_import/src/helpers.rs +++ b/rust/examples/dwarf/dwarf_import/src/helpers.rs @@ -296,7 +296,7 @@ pub(crate) fn get_expr_value<R: Reader<Offset = usize>>( Ok(Operation::UnsignedConstant { value }) => Some(value), Ok(Operation::Address { address: 0 }) => None, Ok(Operation::Address { address }) => Some(address), - _ => None, + _ => None } } else { None diff --git a/rust/examples/dwarf/dwarf_import/src/lib.rs b/rust/examples/dwarf/dwarf_import/src/lib.rs index 71367bfc..62b8c18a 100644 --- a/rust/examples/dwarf/dwarf_import/src/lib.rs +++ b/rust/examples/dwarf/dwarf_import/src/lib.rs @@ -18,11 +18,14 @@ mod functions; mod helpers; mod types; +use std::collections::HashMap; + use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext}; use crate::functions::parse_function_entry; use crate::helpers::{get_attr_die, get_name, get_uid, DieReference}; -use crate::types::parse_data_variable; +use crate::types::parse_variable; +use binaryninja::binaryview::BinaryViewBase; use binaryninja::{ binaryview::{BinaryView, BinaryViewExt}, debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, @@ -34,7 +37,7 @@ use dwarfreader::{ create_section_reader, get_endian, is_dwo_dwarf, is_non_dwo_dwarf, is_raw_dwo_dwarf, }; -use gimli::{constants, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, SectionId, Unit}; +use gimli::{constants, DebuggingInformationEntry, Dwarf, DwarfFileType, Reader, Section, SectionId, Unit, UnwindSection}; use log::{error, warn, LevelFilter}; @@ -188,9 +191,12 @@ fn parse_unit<R: Reader<Offset = usize>>( ) { let mut entries = unit.entries(); + let mut current_depth: isize = 0; + let mut functions_by_depth: Vec<(Option<usize>, isize)> = vec![]; + // Really all we care about as we iterate the entries in a given unit is how they modify state (our perception of the file) // There's a lot of junk we don't care about in DWARF info, so we choose a couple DIEs and mutate state (add functions (which adds the types it uses) and keep track of what namespace we're in) - while let Ok(Some((_, entry))) = entries.next_dfs() { + while let Ok(Some((depth_delta, entry))) = entries.next_dfs() { *current_die_number += 1; if (*progress)( *current_die_number, @@ -201,29 +207,100 @@ fn parse_unit<R: Reader<Offset = usize>>( return; // Parsing canceled } + current_depth = current_depth.saturating_add(depth_delta); + + loop { + if let Some((_fn_idx, depth)) = functions_by_depth.last() { + if current_depth <= *depth { + functions_by_depth.pop(); + } + else { + break + } + } + else { + break; + } + } + match entry.tag() { constants::DW_TAG_subprogram => { - parse_function_entry(unit, entry, debug_info_builder_context, debug_info_builder) + let fn_idx = parse_function_entry(unit, entry, debug_info_builder_context, debug_info_builder); + functions_by_depth.push((fn_idx, current_depth)); } constants::DW_TAG_variable => { - parse_data_variable(unit, entry, debug_info_builder_context, debug_info_builder) + let current_fn_idx = functions_by_depth.last().and_then(|x| x.0); + parse_variable(unit, entry, debug_info_builder_context, debug_info_builder, current_fn_idx) } _ => (), } } } +fn parse_eh_frame<R: Reader>( + view: &BinaryView, + mut eh_frame: gimli::EhFrame<R>, +) -> gimli::Result<iset::IntervalMap<u64, i64>> { + eh_frame.set_address_size(view.address_size() as u8); + + let mut bases = gimli::BaseAddresses::default(); + if let Ok(section) = view.section_by_name(".eh_frame_hdr").or(view.section_by_name("__eh_frame_hdr")) { + bases = bases.set_eh_frame_hdr(section.start()); + } + if let Ok(section) = view.section_by_name(".eh_frame").or(view.section_by_name("__eh_frame")) { + bases = bases.set_eh_frame(section.start()); + } + if let Ok(section) = view.section_by_name(".text").or(view.section_by_name("__text")) { + bases = bases.set_text(section.start()); + } + if let Ok(section) = view.section_by_name(".got").or(view.section_by_name("__got")) { + bases = bases.set_got(section.start()); + } + + let mut cies = HashMap::new(); + let mut cie_data_offsets = iset::IntervalMap::new(); + + let mut entries = eh_frame.entries(&bases); + loop { + match entries.next()? { + None => return Ok(cie_data_offsets), + Some(gimli::CieOrFde::Cie(_cie)) => { + // TODO: do we want to do anything with standalone CIEs? + } + Some(gimli::CieOrFde::Fde(partial)) => { + let fde = match partial.parse(|_, bases, o| { + cies.entry(o) + .or_insert_with(|| eh_frame.cie_from_offset(bases, o)) + .clone() + }) { + Ok(fde) => fde, + Err(e) => { + error!("Failed to parse FDE: {}", e); + continue; + } + }; + // Store CIE offset for FDE range + cie_data_offsets.insert( + fde.initial_address()..fde.initial_address()+fde.len(), + fde.cie().data_alignment_factor() + ); + } + } + } +} + fn parse_dwarf( bv: &BinaryView, + debug_bv: &BinaryView, progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, ) -> Result<DebugInfoBuilder, ()> { // Determine if this is a DWO // TODO : Make this more robust...some DWOs follow non-DWO conventions // Figure out if it's the given view or the raw view that has the dwarf info in it - let raw_view = &bv.raw_view().unwrap(); - let view = if is_dwo_dwarf(bv) || is_non_dwo_dwarf(bv) { - bv + let raw_view = &debug_bv.raw_view().unwrap(); + let view = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) { + debug_bv } else { raw_view }; @@ -239,11 +316,20 @@ fn parse_dwarf( dwarf.file_type = DwarfFileType::Dwo; } + let eh_frame_endian = get_endian(bv); + let mut eh_frame_section_reader = + |section_id: SectionId| -> _ { create_section_reader(section_id, bv, eh_frame_endian, dwo_file) }; + let eh_frame = gimli::EhFrame::load(&mut eh_frame_section_reader).unwrap(); + + let range_data_offsets = parse_eh_frame(bv, eh_frame) + .map_err(|e| println!("Error parsing .eh_frame: {}", e))?; + // Create debug info builder and recover name mapping first // Since DWARF is stored as a tree with arbitrary implicit edges among leaves, // it is not possible to correctly track namespaces while you're parsing "in order" without backtracking, // so we just do it up front let mut debug_info_builder = DebugInfoBuilder::new(); + debug_info_builder.set_range_data_offsets(range_data_offsets); if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, dwarf) { if !recover_names(&mut debug_info_builder_context, &progress) || debug_info_builder_context.total_die_count == 0 @@ -308,7 +394,7 @@ impl CustomDebugInfoParser for DWARFParser { None }; - match parse_dwarf(external_file.as_deref().unwrap_or(debug_file), progress) { + match parse_dwarf(bv, external_file.as_deref().unwrap_or(debug_file), progress) { Ok(mut builder) => { builder .post_process(bv, debug_info) diff --git a/rust/examples/dwarf/dwarf_import/src/types.rs b/rust/examples/dwarf/dwarf_import/src/types.rs index 38bd36ea..67d0110e 100644 --- a/rust/examples/dwarf/dwarf_import/src/types.rs +++ b/rust/examples/dwarf/dwarf_import/src/types.rs @@ -23,27 +23,46 @@ use binaryninja::{ }, }; -use gimli::{constants, DebuggingInformationEntry, Reader, Unit}; +use gimli::{constants, AttributeValue, DebuggingInformationEntry, Operation, Reader, Unit}; -use log::warn; +use log::{debug, error, warn}; -pub(crate) fn parse_data_variable<R: Reader<Offset = usize>>( +pub(crate) fn parse_variable<R: Reader<Offset = usize>>( unit: &Unit<R>, entry: &DebuggingInformationEntry<R>, debug_info_builder_context: &DebugInfoBuilderContext<R>, debug_info_builder: &mut DebugInfoBuilder, + function_index: Option<usize>, ) { let full_name = debug_info_builder_context.get_name(unit, entry); let type_uid = get_type(unit, entry, debug_info_builder_context, debug_info_builder); - let address = if let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) { - get_expr_value(unit, attr) - } else { - None + let Ok(Some(attr)) = entry.attr(constants::DW_AT_location) else { + return }; - if let (Some(address), Some(type_uid)) = (address, type_uid) { - debug_info_builder.add_data_variable(address, full_name, type_uid); + let AttributeValue::Exprloc(mut expression) = attr.value() else { + return + }; + + match Operation::parse(&mut expression.0, unit.encoding()) { + Ok(Operation::FrameOffset { offset }) => { + debug_info_builder.add_stack_variable(function_index, offset, full_name, type_uid); + }, + //Ok(Operation::RegisterOffset { register: _, offset: _, base_type: _ }) => { + // //TODO: look up register by index (binja register indexes don't match processor indexes?) + // //TODO: calculate absolute stack offset + // //TODO: add by absolute offset + //}, + Ok(Operation::Address { address }) => { + if let Some(uid) = type_uid { + debug_info_builder.add_data_variable(address, full_name, uid) + } + }, + Ok(op) => { + debug!("Unhandled operation type for variable: {:?}", op); + }, + Err(e) => error!("Error parsing operation type for variable {:?}: {}", full_name, e) } } diff --git a/rust/examples/pdb-ng/src/parser.rs b/rust/examples/pdb-ng/src/parser.rs index 11bad268..1fc5bc08 100644 --- a/rust/examples/pdb-ng/src/parser.rs +++ b/rust/examples/pdb-ng/src/parser.rs @@ -248,6 +248,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { address, name, type_, + locals, .. }) => { self.log(|| { @@ -271,6 +272,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { Some(address), Some(self.platform.clone()), vec![], // TODO : Components + vec![], //TODO: local variables )); } _ => {} |
