Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`]|<a id='analysis.database.purgeSnapshots'>analysis.database.purgeSnapshots</a>|
|analysis.database|Purge Undo History|When saving a database, purge current and existing undo history.|`boolean`|`False`|[`SettingsProjectScope`, `SettingsUserScope`]|<a id='analysis.database.purgeUndoHistory'>analysis.database.purgeUndoHistory</a>|
|analysis.database|Suppress Reanalysis|Disable function reanalysis on database load when the product version or analysis settings change.|`boolean`|`False`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.database.suppressReanalysis'>analysis.database.suppressReanalysis</a>|
|analysis.debugInfo|Debug File Directories|Paths to folder containing DWARF debug info stored by build id.|`array`|[]|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.debugInfo.debugDirectories'>analysis.debugInfo.debugDirectories</a>|
|analysis.debugInfo|Debug File Directories|Paths to search for DWARF debug info.|`array`|[]|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.debugInfo.debugDirectories'>analysis.debugInfo.debugDirectories</a>|
|analysis.debugInfo|Enable Debug File Directories|Enable searching local debug directories for DWARF debug info.|`boolean`|`True`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.debugInfo.enableDebugDirectories'>analysis.debugInfo.enableDebugDirectories</a>|
|analysis.debugInfo|External Debug Info File|Separate file to attempt to parse and import debug information from.|`string`| |[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.debugInfo.external'>analysis.debugInfo.external</a>|
|analysis.debugInfo|Import Debug Information|Attempt to parse and apply debug information from each file opened.|`boolean`|`True`|[`SettingsProjectScope`, `SettingsResourceScope`, `SettingsUserScope`]|<a id='analysis.debugInfo.internal'>analysis.debugInfo.internal</a>|
Expand Down
54 changes: 29 additions & 25 deletions plugins/dwarf/dwarf_import/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
pub(crate) fn find_local_debug_file_from_path(path: &PathBuf, view: &BinaryView) -> Option<String> {
// 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(
Expand All @@ -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<String> {
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,
Expand All @@ -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);
Expand Down
155 changes: 111 additions & 44 deletions plugins/dwarf/dwarf_import/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::{
Expand Down Expand Up @@ -516,11 +520,24 @@ fn get_supplementary_build_id(bv: &BinaryView) -> Option<String> {
}
}

fn parse_range_data_offsets(bv: &BinaryView) -> Result<IntervalMap<u64, i64>, 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<PathBuf> {
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<IntervalMap<u64, i64>, String> {
let dwo_file = file.section_by_name(".debug_info.dwo").is_some();
let endian = match file.endianness() {
object::Endianness::Little => gimli::RunTimeEndian::Little,
Expand Down Expand Up @@ -564,33 +581,21 @@ fn parse_range_data_offsets(bv: &BinaryView) -> Result<IntervalMap<u64, i64>, St

fn parse_dwarf(
bv: &BinaryView,
debug_bv: &BinaryView,
supplementary_bv: Option<&BinaryView>,
debug_file: &object::File,
supplementary_data: Option<&object::File>,
progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>,
) -> Result<DebugInfoBuilder, String> {
// 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,
Expand All @@ -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)
Expand Down Expand Up @@ -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::<String>(),
expected_build_id
.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>()
);
}
}
}
}

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
Expand Down Expand Up @@ -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" : []
}"#,
);
Expand Down
Loading