summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Ferrell <josh@vector35.com>2025-07-10 10:35:37 -0400
committerJosh Ferrell <josh@vector35.com>2025-07-10 10:37:15 -0400
commit29bf331ec1c142de30cf9f4f277d9e121b1cd96a (patch)
tree9b00e878c797f2ba7b93a2c9b8a28f975d51b721
parentf3e2a872d8cbf45428e750cffb3b1d220b331d5f (diff)
Improve DWARF local variable recovery
-rw-r--r--plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs120
-rw-r--r--plugins/dwarf/dwarf_import/src/functions.rs22
2 files changed, 87 insertions, 55 deletions
diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
index 8122a6ef..9f4f8c03 100644
--- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
+++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::{
+ functions::FrameBase,
helpers::{get_uid, resolve_specification, DieReference},
ReaderType,
};
@@ -52,7 +53,7 @@ pub(crate) struct FunctionInfoBuilder {
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
+ pub(crate) frame_base: Option<FrameBase>,
}
impl FunctionInfoBuilder {
@@ -63,6 +64,7 @@ impl FunctionInfoBuilder {
return_type: Option<TypeUID>,
address: Option<u64>,
parameters: &Vec<Option<(String, TypeUID)>>,
+ frame_base: Option<FrameBase>,
) {
if full_name.is_some() {
self.full_name = full_name;
@@ -91,6 +93,10 @@ impl FunctionInfoBuilder {
_ => self.parameters.push(new_parameter.clone()),
}
}
+
+ if frame_base.is_some() {
+ self.frame_base = frame_base;
+ }
}
}
@@ -229,7 +235,7 @@ impl DebugInfoBuilder {
address: Option<u64>,
parameters: &Vec<Option<(String, TypeUID)>>,
variable_arguments: bool,
- use_cfa: bool,
+ frame_base: Option<FrameBase>,
) -> 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
@@ -254,7 +260,14 @@ impl DebugInfoBuilder {
.remove(function.full_name.as_ref().unwrap());
}
- function.update(full_name, raw_name, return_type, address, parameters);
+ function.update(
+ full_name,
+ raw_name,
+ return_type,
+ address,
+ parameters,
+ frame_base,
+ );
if function.full_name.is_some() {
self.full_function_name_indices
@@ -276,7 +289,14 @@ impl DebugInfoBuilder {
.remove(function.raw_name.as_ref().unwrap());
}
- function.update(full_name, raw_name, return_type, address, parameters);
+ function.update(
+ full_name,
+ raw_name,
+ return_type,
+ address,
+ parameters,
+ frame_base,
+ );
if function.raw_name.is_some() {
self.raw_function_name_indices
@@ -299,7 +319,7 @@ impl DebugInfoBuilder {
platform: None,
variable_arguments,
stack_variables: vec![],
- use_cfa,
+ frame_base,
};
if let Some(n) = &function.full_name {
@@ -367,7 +387,7 @@ impl DebugInfoBuilder {
offset: i64,
name: Option<String>,
type_uid: Option<TypeUID>,
- lexical_block: Option<&iset::IntervalSet<u64>>,
+ _lexical_block: Option<&iset::IntervalSet<u64>>,
) {
let name = match name {
Some(x) => {
@@ -377,11 +397,11 @@ impl DebugInfoBuilder {
} else {
x
}
- }
+ },
None => {
// Anonymous variable, generate name
format!("debug_var_{}", offset)
- }
+ },
};
let Some(function_index) = fn_idx else {
@@ -407,50 +427,56 @@ impl DebugInfoBuilder {
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().cloned())
- })
- .or_else(|| {
- // Try using the offset at the adjustment 5 bytes after the function start, in case the function starts with a stack adjustment
- // Calculate final offset as (offset after initial stack adjustment) - (entry offset)
- // TODO: This is a decent heuristic but not perfect, since further adjustments could still be made
- if let Some(offset_after_stack_adjust) = self.range_data_offsets.values_overlap(func_addr + 5).next() {
- if let Some(entry_offset) = self.range_data_offsets.values_overlap(func_addr).next() {
- return Some(offset_after_stack_adjust - entry_offset);
- }
- }
- // No entry offset or no initial stack adjustment, fall through
- None
- })
- .or_else(|| {
- // If all else fails, use the function start address
- self.range_data_offsets.values_overlap(func_addr).next().cloned()
+ let Some(frame_base) = &function.frame_base else {
+ error!("Trying to add a local variable ({}) to a function ({:#x}) without a frame base. Please report this issue.", name, func_addr);
+ return;
+ };
+
+ // TODO: use lexical block information when we support stack variable lifetimes
+ /*
+ let lexical_block_adjustment = lexical_block.and_then(|block_ranges| {
+ block_ranges.unsorted_iter().find_map(|x| {
+ self.range_data_offsets
+ .values_overlap(x.start)
+ .next()
+ .cloned()
})
+ });
+ */
+
+ let Some(entry_cfa_offset) = self
+ .range_data_offsets
+ .values_overlap(func_addr)
+ .next()
+ .cloned()
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);
+ debug!("Refusing to add a local variable ({}@{}) to function at {} without a known CFA adjustment.", name, offset, func_addr);
return;
};
// TODO: handle non-sp-based locations
- // 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 {
+ let adjusted_offset = match frame_base {
// Apply CFA offset to variable storage offset if DW_AT_frame_base is DW_OP_call_frame_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;
- };
+ FrameBase::CFA => offset + entry_cfa_offset,
+ FrameBase::Register(_reg) => {
+ // TODO: if not using SP, do not add var
+ // TODO: if not in a lexical block this can be wrong, see https://github.com/Vector35/binaryninja-api/issues/5882#issuecomment-2406065057
+ // If it's using SP, we know the SP offset is <SP offset> + (<entry SP CFA offset> - <SP CFA offset>)
- offset + (adjustment_at_entry - adjustment_at_variable_lifetime_start)
+ // Try using the offset at the adjustment 5 bytes after the function start, in case the function starts with a stack adjustment
+ // Calculate final offset as (offset after initial stack adjustment) - (entry offset)
+ // TODO: This is a decent heuristic but not perfect, since further adjustments could still be made
+ let guessed_sp_adjustment = self
+ .range_data_offsets
+ .values_overlap(func_addr + 5)
+ .next()
+ .and_then(|cfa_offset_after_stack_adjust| {
+ Some(entry_cfa_offset - cfa_offset_after_stack_adjust)
+ });
+
+ offset + guessed_sp_adjustment.unwrap_or(0)
+ },
};
if adjusted_offset > 0 {
@@ -563,7 +589,7 @@ impl DebugInfoBuilder {
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),
};
@@ -643,11 +669,11 @@ impl DebugInfoBuilder {
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 => {}
+ },
+ Ordering::Less => {},
}
}
}
diff --git a/plugins/dwarf/dwarf_import/src/functions.rs b/plugins/dwarf/dwarf_import/src/functions.rs
index 6277c6b5..49e16274 100644
--- a/plugins/dwarf/dwarf_import/src/functions.rs
+++ b/plugins/dwarf/dwarf_import/src/functions.rs
@@ -24,6 +24,12 @@ use gimli::{constants, AttributeValue, DebuggingInformationEntry, Dwarf, Operati
use log::{debug, error};
use regex::Regex;
+#[derive(PartialEq, Eq, Hash)]
+pub enum FrameBase {
+ Register(gimli::Register),
+ CFA,
+}
+
fn get_parameters<R: ReaderType>(
dwarf: &Dwarf<R>,
unit: &Unit<R>,
@@ -65,7 +71,7 @@ fn get_parameters<R: ReaderType>(
} else {
result.push(None)
}
- }
+ },
constants::DW_TAG_unspecified_parameters => variable_arguments = true,
_ => (),
}
@@ -136,17 +142,17 @@ pub(crate) fn parse_function_entry<R: ReaderType>(
return None;
}
- let use_cfa;
+ let frame_base;
if let Ok(Some(AttributeValue::Exprloc(mut expression))) =
entry.attr_value(constants::DW_AT_frame_base)
{
- use_cfa = match Operation::parse(&mut expression.0, unit.encoding()) {
- Ok(Operation::Register { register: _ }) => false, // TODO: handle register-relative encodings later
- Ok(Operation::CallFrameCFA) => true,
- _ => false,
+ frame_base = match Operation::parse(&mut expression.0, unit.encoding()) {
+ Ok(Operation::Register { register: reg }) => Some(FrameBase::Register(reg)),
+ Ok(Operation::CallFrameCFA) => Some(FrameBase::CFA),
+ _ => None, // TODO: warn?
};
} else {
- use_cfa = false;
+ frame_base = None;
}
debug_info_builder.insert_function(
@@ -156,7 +162,7 @@ pub(crate) fn parse_function_entry<R: ReaderType>(
address,
&parameters,
variable_arguments,
- use_cfa,
+ frame_base,
)
}