summaryrefslogtreecommitdiff
path: root/rust/examples
diff options
context:
space:
mode:
authorJosh Ferrell <josh@vector35.com>2024-06-13 14:13:11 -0400
committerJosh Ferrell <josh@vector35.com>2024-06-13 14:13:11 -0400
commitd75066545afdc9b5d0a52bd2518e43f683c671a5 (patch)
treebed26d348a726fc02c1d44cbdc36a8056b44baaf /rust/examples
parentf3a440248cda4d0c850cd32e3aa127ab29e0bb3e (diff)
Apply stack variables from DWARF
Diffstat (limited to 'rust/examples')
-rw-r--r--rust/examples/dwarf/dwarf_import/Cargo.toml1
-rw-r--r--rust/examples/dwarf/dwarf_import/src/dwarfdebuginfo.rs88
-rw-r--r--rust/examples/dwarf/dwarf_import/src/functions.rs4
-rw-r--r--rust/examples/dwarf/dwarf_import/src/helpers.rs2
-rw-r--r--rust/examples/dwarf/dwarf_import/src/lib.rs104
-rw-r--r--rust/examples/dwarf/dwarf_import/src/types.rs37
-rw-r--r--rust/examples/pdb-ng/src/parser.rs2
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, &parameters, variable_arguments);
+ debug_info_builder.insert_function(full_name, raw_name, return_type, address, &parameters, 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
));
}
_ => {}