diff --git a/docs/guide/settings.md b/docs/guide/settings.md
index 32f08013a..aa418a422 100644
--- a/docs/guide/settings.md
+++ b/docs/guide/settings.md
@@ -123,7 +123,7 @@ The setting will appear under the Quick Settings section in the Function Setting
|analysis.database|Purge Snapshots|When saving a database, purge old snapshots keeping only the current snapshot.|`boolean`|`False`|[`SettingsProjectScope`, `SettingsUserScope`]|analysis.database.purgeSnapshots|
|analysis.database|Purge Undo History|When saving a database, purge current and existing undo history.|`boolean`|`False`|[`SettingsProjectScope`, `SettingsUserScope`]|analysis.database.purgeUndoHistory|
|analysis.database|Suppress Reanalysis|Disable function reanalysis on database load when the product version or analysis settings change.|`boolean`|`False`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.database.suppressReanalysis|
-|analysis.debugInfo|Debug File Directories|Paths to folder containing DWARF debug info stored by build id.|`array`|[]|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.debugInfo.debugDirectories|
+|analysis.debugInfo|Debug File Directories|Paths to search for DWARF debug info.|`array`|[]|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.debugInfo.debugDirectories|
|analysis.debugInfo|Enable Debug File Directories|Enable searching local debug directories for DWARF debug info.|`boolean`|`True`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.debugInfo.enableDebugDirectories|
|analysis.debugInfo|External Debug Info File|Separate file to attempt to parse and import debug information from.|`string`| |[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.debugInfo.external|
|analysis.debugInfo|Import Debug Information|Attempt to parse and apply debug information from each file opened.|`boolean`|`True`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|analysis.debugInfo.internal|
diff --git a/plugins/dwarf/dwarf_import/src/helpers.rs b/plugins/dwarf/dwarf_import/src/helpers.rs
index 63dcb542a..cd750ceef 100644
--- a/plugins/dwarf/dwarf_import/src/helpers.rs
+++ b/plugins/dwarf/dwarf_import/src/helpers.rs
@@ -517,10 +517,16 @@ pub(crate) fn download_debug_info(
Err("Could not find a server with debug info for this file".to_string())
}
-pub(crate) fn find_local_debug_file_for_build_id(
- build_id: &str,
- view: &BinaryView,
-) -> Option {
+pub(crate) fn find_local_debug_file_from_path(path: &PathBuf, view: &BinaryView) -> Option {
+ // Search debug directories for path (or None if setting disabled/empty), return the first one that exists
+ // TODO: put absolute paths behind setting?
+ if path.is_absolute() {
+ if !path.exists() {
+ return None;
+ }
+ return path.to_str().map(|s| s.to_string());
+ }
+
let mut settings_query_opts = QueryOptions::new_with_view(view);
let settings = Settings::new();
let debug_dirs_enabled = settings.get_bool_with_opts(
@@ -537,31 +543,29 @@ pub(crate) fn find_local_debug_file_for_build_id(
&mut settings_query_opts,
);
- if debug_info_paths.is_empty() {
- return None;
- }
-
for debug_info_path in debug_info_paths.into_iter() {
- let path = PathBuf::from(debug_info_path);
- let elf_path = path.join(&build_id[..2]).join(&build_id[2..]).join("elf");
-
- let debug_ext_path = path
- .join(&build_id[..2])
- .join(format!("{}.debug", &build_id[2..]));
-
- let final_path = if debug_ext_path.exists() {
- debug_ext_path
- } else if elf_path.exists() {
- elf_path
- } else {
- // No paths exist in this dir, try the next one
- continue;
- };
- return final_path.to_str().and_then(|x| Some(x.to_string()));
+ let final_path = PathBuf::from(debug_info_path).join(path);
+ if final_path.exists() {
+ return final_path.to_str().map(|s| s.to_string());
+ }
}
None
}
+pub(crate) fn find_local_debug_file_for_build_id(
+ build_id: &str,
+ view: &BinaryView,
+) -> Option {
+ let debug_ext_path = PathBuf::from(&build_id[..2]).join(format!("{}.debug", &build_id[2..]));
+
+ let elf_path = PathBuf::from(&build_id[..2])
+ .join(&build_id[2..])
+ .join("elf");
+
+ find_local_debug_file_from_path(&debug_ext_path, view)
+ .or_else(|| find_local_debug_file_from_path(&elf_path, view))
+}
+
pub(crate) fn load_debug_info_for_build_id(
build_id: &str,
view: &BinaryView,
@@ -575,7 +579,7 @@ pub(crate) fn load_debug_info_for_build_id(
false,
Some("{\"analysis.debugInfo.internal\": false}"),
),
- false,
+ true,
);
} else if settings.get_bool_with_opts("network.enableDebuginfod", &mut settings_query_opts) {
return (download_debug_info(build_id, view).ok(), true);
diff --git a/plugins/dwarf/dwarf_import/src/lib.rs b/plugins/dwarf/dwarf_import/src/lib.rs
index 6e9626aac..838a36124 100644
--- a/plugins/dwarf/dwarf_import/src/lib.rs
+++ b/plugins/dwarf/dwarf_import/src/lib.rs
@@ -19,10 +19,14 @@ mod helpers;
mod types;
use std::collections::HashMap;
+use std::path::PathBuf;
+use std::str::FromStr;
use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext};
use crate::functions::parse_function_entry;
-use crate::helpers::{get_attr_die, get_name, get_uid, DieReference};
+use crate::helpers::{
+ find_local_debug_file_from_path, get_attr_die, get_name, get_uid, DieReference,
+};
use crate::types::parse_variable;
use binaryninja::binary_view::BinaryViewBase;
@@ -32,7 +36,7 @@ use binaryninja::{
settings::Settings,
template_simplifier::simplify_str_to_str,
};
-use dwarfreader::{create_section_reader_object, get_endian, is_dwo_dwarf, is_non_dwo_dwarf};
+use dwarfreader::create_section_reader_object;
use functions::parse_lexical_block;
use gimli::{
@@ -516,11 +520,24 @@ fn get_supplementary_build_id(bv: &BinaryView) -> Option {
}
}
-fn parse_range_data_offsets(bv: &BinaryView) -> Result, String> {
- let raw_view = bv.raw_view().unwrap();
- let raw_view_data = raw_view.read_vec(0, raw_view.len() as usize);
- let file =
- object::File::parse(&*raw_view_data).map_err(|e| format!("Failed to parse bv: {}", e))?;
+fn get_supplementary_file_path(bv: &BinaryView) -> Option {
+ let raw_view = bv.raw_view()?;
+ if let Some(section) = raw_view.section_by_name(".gnu_debugaltlink") {
+ let start = section.start();
+ let len = section.len();
+
+ raw_view
+ .read_vec(start, len)
+ .splitn(2, |x| *x == 0)
+ .next()
+ .and_then(|a| String::from_utf8(a.to_vec()).ok())
+ .and_then(|p| PathBuf::from_str(&p).ok())
+ } else {
+ None
+ }
+}
+
+fn parse_range_data_offsets(file: &object::File) -> Result, String> {
let dwo_file = file.section_by_name(".debug_info.dwo").is_some();
let endian = match file.endianness() {
object::Endianness::Little => gimli::RunTimeEndian::Little,
@@ -564,33 +581,21 @@ fn parse_range_data_offsets(bv: &BinaryView) -> Result, St
fn parse_dwarf(
bv: &BinaryView,
- debug_bv: &BinaryView,
- supplementary_bv: Option<&BinaryView>,
+ debug_file: &object::File,
+ supplementary_data: Option<&object::File>,
progress: Box Result<(), ()>>,
) -> Result {
- // TODO: warn if no supplementary file and .gnu_debugaltlink section present
-
- // 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 = &debug_bv
- .raw_view()
- .ok_or("Failed to get raw view for debug binary view".to_string())?;
+ if debug_file.section_by_name(".gnu_debugaltlink").is_some() && supplementary_data.is_none() {
+ log::warn!(".gnu_debugaltlink section present but no supplementary data provided. DWARF parsing may fail.")
+ }
- let address_size = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) {
- debug_bv.address_size()
- } else {
- raw_view.address_size()
+ let address_size = match debug_file.architecture().address_size() {
+ Some(x) => x.bytes() as usize,
+ None => bv.address_size(),
};
- // Parse this early to reduce peak memory usage
- let range_data_offsets = parse_range_data_offsets(bv).unwrap_or_default();
+ let range_data_offsets = parse_range_data_offsets(debug_file).unwrap_or_default();
- // Read the raw view to an object::File so relocations get handled for us
- let raw_view_data = raw_view.read_vec(0, raw_view.len() as usize);
- let debug_file =
- object::File::parse(&*raw_view_data).map_err(|e| format!("Failed to parse bv: {}", e))?;
let dwo_file = debug_file.section_by_name(".debug_info.dwo").is_some();
let endian = match debug_file.endianness() {
object::Endianness::Little => gimli::RunTimeEndian::Little,
@@ -610,14 +615,12 @@ fn parse_dwarf(
dwarf.file_type = DwarfFileType::Main;
}
- if let Some(sup_bv) = supplementary_bv {
- let sup_raw_view = sup_bv
- .raw_view()
- .ok_or_else(|| format!("Failed to get raw view for supplementary bv"))?;
- let sup_view_data = sup_raw_view.read_vec(0, sup_raw_view.len() as usize);
- let sup_file = object::File::parse(&*sup_view_data)
- .map_err(|e| format!("Failed to parse supplementary bv: {}", e))?;
- let sup_endian = get_endian(sup_bv);
+ if let Some(sup_file) = supplementary_data {
+ let sup_endian = match sup_file.endianness() {
+ object::Endianness::Little => gimli::RunTimeEndian::Little,
+ object::Endianness::Big => gimli::RunTimeEndian::Big,
+ };
+
let sup_dwo_file = sup_file.section_by_name(".debug_info.dwo").is_some();
let sup_section_reader = |section_id: SectionId| -> _ {
create_section_reader_object(section_id, &sup_file, sup_endian, sup_dwo_file)
@@ -725,19 +728,83 @@ impl CustomDebugInfoParser for DWARFParser {
(None, false)
};
- let sup_bv = get_supplementary_build_id(external_file.as_deref().unwrap_or(debug_file))
+ let debug_bv = external_file.as_deref().unwrap_or(debug_file);
+
+ // Read the raw view to an object::File so relocations get handled for us
+ let Some(raw_view) = debug_bv.raw_view() else {
+ log::error!("Failed to get raw view for debug bv");
+ return false;
+ };
+ let raw_view_data = raw_view.read_vec(0, raw_view.len() as usize);
+ let debug_file = match object::File::parse(&*raw_view_data) {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("Failed to parse bv: {}", e);
+ return false;
+ }
+ };
+
+ // TODO: allow passing a supplementary file path as a setting?
+ // Try to load supplementary file from build id, falling back to file path
+ let sup_view_data = get_supplementary_build_id(debug_bv)
.and_then(|build_id| {
load_debug_info_for_build_id(&build_id, bv)
.0
.map(|x| x.raw_view().expect("Failed to get raw view"))
+ })
+ .or_else(|| {
+ get_supplementary_file_path(debug_bv).and_then(|sup_file_path_suggestion| {
+ find_local_debug_file_from_path(&sup_file_path_suggestion, debug_bv).and_then(
+ |sup_file_path| {
+ binaryninja::load_with_options(
+ sup_file_path,
+ false,
+ Some("{\"analysis.debugInfo.internal\": false}"),
+ )
+ },
+ )
+ })
+ })
+ .and_then(|sup_bv| {
+ let sup_raw_view = sup_bv.raw_view()?;
+ let sup_raw_data = sup_raw_view.read_vec(0, sup_raw_view.len() as usize);
+ sup_raw_view.file().close();
+ Some(sup_raw_data)
});
- let result = match parse_dwarf(
- bv,
- external_file.as_deref().unwrap_or(debug_file),
- sup_bv.as_deref(),
- progress,
- ) {
+ let sup_file =
+ sup_view_data
+ .as_ref()
+ .and_then(|data| match object::File::parse(data.as_slice()) {
+ Ok(x) => Some(x),
+ Err(e) => {
+ log::error!("Failed to parse supplementary bv: {}", e);
+ None
+ }
+ });
+
+ // If we have a sup file, verify its build id with the expected build id, else warn
+ if let Some(sup_file) = &sup_file {
+ if let Ok(Some((_, expected_build_id))) = debug_file.gnu_debugaltlink() {
+ if let Ok(Some(loaded_sup_build_id)) = sup_file.build_id() {
+ if loaded_sup_build_id != expected_build_id {
+ log::warn!(
+ "Supplementary debug info build id ({}) does not match expected ({})",
+ loaded_sup_build_id
+ .iter()
+ .map(|b| format!("{:02x}", b))
+ .collect::(),
+ expected_build_id
+ .iter()
+ .map(|b| format!("{:02x}", b))
+ .collect::()
+ );
+ }
+ }
+ }
+ }
+
+ let result = match parse_dwarf(bv, &debug_file, sup_file.as_ref(), progress) {
Ok(mut builder) => {
builder.post_process(bv, debug_info).commit_info(debug_info);
true
@@ -802,7 +869,7 @@ fn plugin_init() {
"type" : "array",
"sorted" : true,
"default" : [],
- "description" : "Paths to folder containing DWARF debug info stored by build id.",
+ "description" : "Paths to search for DWARF debug info.",
"ignore" : []
}"#,
);