// Copyright 2021-2024 Vector 35 Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::path::PathBuf; use std::{ collections::HashMap, ops::Deref, sync::mpsc, str::FromStr }; use crate::{DebugInfoBuilderContext, ReaderType}; use binaryninja::binaryview::BinaryViewBase; use binaryninja::filemetadata::FileMetadata; use binaryninja::Endianness; use binaryninja::{binaryview::{BinaryView, BinaryViewExt}, downloadprovider::{DownloadInstanceInputOutputCallbacks, DownloadProvider}, rc::Ref, settings::Settings}; use gimli::Dwarf; use gimli::{ constants, Attribute, AttributeValue, AttributeValue::{DebugInfoRef, DebugInfoRefSup, UnitRef}, DebuggingInformationEntry, Operation, Unit, UnitOffset, UnitSectionOffset, }; use log::warn; pub(crate) fn get_uid( dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, ) -> usize { // We set a large gap between supplementary and main entries let adj = dwarf.sup().map_or(0, |_| 0x1000000000000000); let entry_offset = match entry.offset().to_unit_section_offset(unit) { UnitSectionOffset::DebugInfoOffset(o) => o.0, UnitSectionOffset::DebugTypesOffset(o) => o.0, }; entry_offset + adj } //////////////////////////////////// // DIE attr convenience functions pub(crate) enum DieReference<'a, R: ReaderType> { UnitAndOffset((&'a Dwarf, &'a Unit, UnitOffset)), Err, } pub(crate) fn get_attr_die<'a, R: ReaderType>( dwarf: &'a Dwarf, unit: &'a Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &'a DebugInfoBuilderContext, attr: constants::DwAt, ) -> Option> { match entry.attr_value(attr) { Ok(Some(UnitRef(offset))) => Some(DieReference::UnitAndOffset((dwarf, unit, offset))), Ok(Some(DebugInfoRef(offset))) => { if dwarf.sup().is_some() { for source_unit in debug_info_builder_context.units() { if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); } } } else { // This could either have no supplementary file because it is one or because it just doesn't have one // operate on supplementary file if dwarf is a supplementary file, else self // It's possible this is a reference in the supplementary file to itself for source_unit in debug_info_builder_context.sup_units() { if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); } } // ... or it just doesn't have a supplementary file for source_unit in debug_info_builder_context.units() { if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { return Some(DieReference::UnitAndOffset((dwarf, source_unit, new_offset))); } } } None }, Ok(Some(DebugInfoRefSup(offset))) => { for source_unit in debug_info_builder_context.sup_units() { if let Some(new_offset) = offset.to_unit_offset(&source_unit.header) { return Some(DieReference::UnitAndOffset((dwarf.sup().unwrap(), source_unit, new_offset))); } } warn!("Failed to fetch DIE. Supplementary debug information may be incomplete."); None }, _ => None, } } pub(crate) fn resolve_specification<'a, R: ReaderType>( dwarf: &'a Dwarf, unit: &'a Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &'a DebugInfoBuilderContext, ) -> DieReference<'a, R> { if let Some(die_reference) = get_attr_die( dwarf, unit, entry, debug_info_builder_context, constants::DW_AT_specification, ) { match die_reference { DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { if let Ok(entry) = entry_unit.entry(entry_offset) { resolve_specification(dwarf, entry_unit, &entry, debug_info_builder_context) } else { warn!("Failed to fetch DIE for attr DW_AT_specification. Debug information may be incomplete."); DieReference::Err } } DieReference::Err => DieReference::Err, } } else if let Some(die_reference) = get_attr_die( dwarf, unit, entry, debug_info_builder_context, constants::DW_AT_abstract_origin, ) { match die_reference { DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { if entry_offset == entry.offset() && unit.header.offset() == entry_unit.header.offset() { warn!("DWARF information is invalid (infinite abstract origin reference cycle). Debug information may be incomplete."); DieReference::Err } else if let Ok(new_entry) = entry_unit.entry(entry_offset) { resolve_specification(dwarf, entry_unit, &new_entry, debug_info_builder_context) } else { warn!("Failed to fetch DIE for attr DW_AT_abstract_origin. Debug information may be incomplete."); DieReference::Err } } DieReference::Err => DieReference::Err, } } else { DieReference::UnitAndOffset((dwarf, unit, entry.offset())) } } // Get name from DIE, or referenced dependencies pub(crate) fn get_name( dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, debug_info_builder_context: &DebugInfoBuilderContext, ) -> Option { match resolve_specification(dwarf, unit, entry, debug_info_builder_context) { DieReference::UnitAndOffset((dwarf, entry_unit, entry_offset)) => { if let Ok(Some(attr_val)) = entry_unit .entry(entry_offset) .unwrap() .attr_value(constants::DW_AT_name) { if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val.clone()) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } else if let Some(dwarf) = &dwarf.sup { if let Ok(attr_string) = dwarf.attr_string(entry_unit, attr_val) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } } } // if let Some(raw_name) = get_raw_name(unit, entry, debug_info_builder_context) { // if let Some(arch) = debug_info_builder_context.default_architecture() { // if let Ok((_, names)) = demangle_gnu3(&arch, raw_name, true) { // return Some(names.join("::")); // } // } // } None } DieReference::Err => None, } } // Get raw name from DIE, or referenced dependencies pub(crate) fn get_raw_name( dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_linkage_name) { if let Ok(attr_string) = dwarf.attr_string(unit, attr_val.clone()) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } else if let Some(dwarf) = dwarf.sup() { if let Ok(attr_string) = dwarf.attr_string(unit, attr_val) { if let Ok(attr_string) = attr_string.to_string() { return Some(attr_string.to_string()); } } } } None } // Get the size of an object as a usize pub(crate) fn get_size_as_usize( entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { get_attr_as_usize(attr) } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { get_attr_as_usize(attr).map(|attr_value| attr_value / 8) } else { None } } // Get the size of an object as a u64 pub(crate) fn get_size_as_u64( entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_byte_size) { get_attr_as_u64(&attr) } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_bit_size) { get_attr_as_u64(&attr).map(|attr_value| attr_value / 8) } else { None } } // Get the size of a subrange as a u64 pub(crate) fn get_subrange_size( entry: &DebuggingInformationEntry, ) -> u64 { if let Ok(Some(attr)) = entry.attr(constants::DW_AT_upper_bound) { get_attr_as_u64(&attr).map_or(0, |v| v + 1) } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_count) { get_attr_as_u64(&attr).unwrap_or(0) } else if let Ok(Some(attr)) = entry.attr(constants::DW_AT_lower_bound) { get_attr_as_u64(&attr).map_or(0, |v| v + 1) } else { 0 } } // Get the start address of a function pub(crate) fn get_start_address( dwarf: &Dwarf, unit: &Unit, entry: &DebuggingInformationEntry, ) -> Option { if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_low_pc) { match dwarf.attr_address(unit, attr_val) { Ok(Some(val)) => Some(val), _ => None, } } else if let Ok(Some(attr_val)) = entry.attr_value(constants::DW_AT_entry_pc) { match dwarf.attr_address(unit, attr_val) { Ok(Some(val)) => Some(val), _ => None, } } else if let Ok(Some(attr_value)) = entry.attr_value(constants::DW_AT_ranges) { if let Ok(Some(ranges_offset)) = dwarf.attr_ranges_offset(unit, attr_value) { if let Ok(mut ranges) = dwarf.ranges(unit, ranges_offset) { if let Ok(Some(range)) = ranges.next() { return Some(range.begin); } } } return None; } else { None } } // Get an attribute value as a u64 if it can be coerced pub(crate) fn get_attr_as_u64(attr: &Attribute) -> Option { if let Some(value) = attr.udata_value() { Some(value) } else if let Some(value) = attr.sdata_value() { Some(value as u64) } else if let AttributeValue::Block(mut data) = attr.value() { match data.len() { 1 => data.read_u8().map(u64::from).ok(), 2 => data.read_u16().map(u64::from).ok(), 4 => data.read_u32().map(u64::from).ok(), 8 => data.read_u64().ok(), _ => None } } else { None } } // Get an attribute value as a usize if it can be coerced pub(crate) fn get_attr_as_usize(attr: Attribute) -> Option { if let Some(value) = attr.u8_value() { Some(value.into()) } else if let Some(value) = attr.u16_value() { Some(value.into()) } else if let Some(value) = attr.udata_value() { Some(value as usize) } else { attr.sdata_value().map(|value| value as usize) } } // Get an attribute value as a usize if it can be coerced // Parses DW_OP_address, DW_OP_const pub(crate) fn get_expr_value( unit: &Unit, attr: Attribute, ) -> Option { if let AttributeValue::Exprloc(mut expression) = attr.value() { match Operation::parse(&mut expression.0, unit.encoding()) { Ok(Operation::PlusConstant { value }) => Some(value), Ok(Operation::UnsignedConstant { value }) => Some(value), Ok(Operation::Address { address: 0 }) => None, Ok(Operation::Address { address }) => Some(address), _ => None } } else { None } } pub(crate) fn get_build_id(view: &BinaryView) -> Result { let mut build_id: Option = None; if let Ok(raw_view) = view.raw_view() { if let Ok(build_id_section) = raw_view.section_by_name(".note.gnu.build-id") { // Name size - 4 bytes // Desc size - 4 bytes // Type - 4 bytes // Name - n bytes // Desc - n bytes let build_id_bytes = raw_view.read_vec(build_id_section.start(), build_id_section.len()); if build_id_bytes.len() < 12 { return Err("Build id section must be at least 12 bytes".to_string()); } let name_len: u32; let desc_len: u32; let note_type: u32; match raw_view.default_endianness() { Endianness::LittleEndian => { name_len = u32::from_le_bytes(build_id_bytes[0..4].try_into().unwrap()); desc_len = u32::from_le_bytes(build_id_bytes[4..8].try_into().unwrap()); note_type = u32::from_le_bytes(build_id_bytes[8..12].try_into().unwrap()); }, Endianness::BigEndian => { name_len = u32::from_be_bytes(build_id_bytes[0..4].try_into().unwrap()); desc_len = u32::from_be_bytes(build_id_bytes[4..8].try_into().unwrap()); note_type = u32::from_be_bytes(build_id_bytes[8..12].try_into().unwrap()); } }; if note_type != 3 { return Err(format!("Build id section has wrong type: {}", note_type)); } let expected_len = (12 + name_len + desc_len) as usize; if build_id_bytes.len() < expected_len { return Err(format!("Build id section not expected length: expected {}, got {}", expected_len, build_id_bytes.len())); } let desc: &[u8] = &build_id_bytes[(12+name_len as usize)..expected_len]; build_id = Some(desc.iter().map(|b| format!("{:02x}", b)).collect()); } } if let Some(x) = build_id { Ok(x) } else { Err("Failed to get build id".to_string()) } } pub(crate) fn download_debug_info(build_id: &String, view: &BinaryView) -> Result, String> { let settings = Settings::new(""); let debug_server_urls = settings.get_string_list("network.debuginfodServers", Some(view), None); for debug_server_url in debug_server_urls.iter() { let artifact_url = format!("{}/buildid/{}/debuginfo", debug_server_url, build_id); // Download from remote let (tx, rx) = mpsc::channel(); let write = move |data: &[u8]| -> usize { if let Ok(_) = tx.send(Vec::from(data)) { data.len() } else { 0 } }; let dp = DownloadProvider::try_default().map_err(|_| "No default download provider")?; let mut inst = dp .create_instance() .map_err(|_| "Couldn't create download instance")?; let result = inst .perform_custom_request( "GET", artifact_url, HashMap::::new(), DownloadInstanceInputOutputCallbacks { read: None, write: Some(Box::new(write)), progress: None, }, ) .map_err(|e| e.to_string())?; if result.status_code != 200 { continue; } let mut expected_length = None; for (k, v) in result.headers.iter() { if k.to_lowercase() == "content-length" { expected_length = Some(usize::from_str(v).map_err(|e| e.to_string())?); } } let mut data = vec![]; while let Ok(packet) = rx.try_recv() { data.extend(packet.into_iter()); } if let Some(length) = expected_length { if data.len() != length { return Err(format!( "Bad length: expected {} got {}", length, data.len() )); } } let options = "{\"analysis.debugInfo.internal\": false}"; let bv = BinaryView::from_data(FileMetadata::new().deref(), &data) .map_err(|_| "Unable to create binary view from downloaded data".to_string())?; return binaryninja::load_view(bv.deref(), false, Some(options)) .ok_or("Unable to load binary view from downloaded data".to_string()); } return Err("Could not find a server with debug info for this file".to_string()); } pub(crate) fn find_local_debug_file(build_id: &String, view: &BinaryView) -> Option { let settings = Settings::new(""); let debug_info_paths = settings.get_string_list("analysis.debugInfo.debugDirectories", Some(view), None); if debug_info_paths.is_empty() { return None } for debug_info_path in debug_info_paths.into_iter() { if let Ok(path) = PathBuf::from_str(&debug_info_path.to_string()) { 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())); } } None } pub(crate) fn load_debug_info_for_build_id(build_id: &String, view: &BinaryView) -> (Option>, bool) { if let Some(debug_file_path) = find_local_debug_file(build_id, view) { return ( binaryninja::load_with_options( debug_file_path, false, Some("{\"analysis.debugInfo.internal\": false}") ), false ); } else if Settings::new("").get_bool("network.enableDebuginfod", Some(view), None) { return ( download_debug_info(build_id, view).ok(), true ); } (None, false) }