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" : [] }"#, );