diff options
| author | Josh Ferrell <josh@vector35.com> | 2025-10-16 12:19:57 -0400 |
|---|---|---|
| committer | Josh Ferrell <josh@vector35.com> | 2025-10-16 20:29:43 -0400 |
| commit | b6740d03abd22441e2e79e1d6a13c474e3fcba90 (patch) | |
| tree | b4b317e182f23a0b9f2f633b25c54bed814abb4b /plugins/dwarf | |
| parent | e75d64a86a128b9a8cb3cb606e5f9560e90a4758 (diff) | |
Apply relocations when parsing DWARF
Diffstat (limited to 'plugins/dwarf')
| -rw-r--r-- | plugins/dwarf/dwarf_import/Cargo.toml | 3 | ||||
| -rw-r--r-- | plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs | 4 | ||||
| -rw-r--r-- | plugins/dwarf/dwarf_import/src/lib.rs | 207 | ||||
| -rw-r--r-- | plugins/dwarf/shared/Cargo.toml | 1 | ||||
| -rw-r--r-- | plugins/dwarf/shared/src/lib.rs | 158 |
5 files changed, 162 insertions, 211 deletions
diff --git a/plugins/dwarf/dwarf_import/Cargo.toml b/plugins/dwarf/dwarf_import/Cargo.toml index 9527274a..ca06b11b 100644 --- a/plugins/dwarf/dwarf_import/Cargo.toml +++ b/plugins/dwarf/dwarf_import/Cargo.toml @@ -16,4 +16,5 @@ log = "0.4" iset = "0.2.2" cpp_demangle = "0.4.3" regex = "1" -indexmap = "2.5.0"
\ No newline at end of file +indexmap = "2.5.0" +object = "0.36" diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 0dc6b7fb..94f370a1 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -125,7 +125,7 @@ pub(crate) struct DebugInfoBuilderContext<R: ReaderType> { } impl<R: ReaderType> DebugInfoBuilderContext<R> { - pub(crate) fn new(view: &BinaryView, dwarf: &Dwarf<R>) -> Option<Self> { + pub(crate) fn new(default_address_size: usize, dwarf: &Dwarf<R>) -> Option<Self> { let mut units = vec![]; let mut iter = dwarf.units(); while let Ok(Some(header)) = iter.next() { @@ -154,7 +154,7 @@ impl<R: ReaderType> DebugInfoBuilderContext<R> { units, sup_units, names: HashMap::new(), - default_address_size: view.address_size(), + default_address_size, total_die_count: 0, total_unit_size_bytes: 0, }) diff --git a/plugins/dwarf/dwarf_import/src/lib.rs b/plugins/dwarf/dwarf_import/src/lib.rs index dcac4f0b..8ad5e442 100644 --- a/plugins/dwarf/dwarf_import/src/lib.rs +++ b/plugins/dwarf/dwarf_import/src/lib.rs @@ -32,9 +32,7 @@ use binaryninja::{ settings::Settings, template_simplifier::simplify_str_to_str, }; -use dwarfreader::{ - create_section_reader, get_endian, is_dwo_dwarf, is_non_dwo_dwarf, is_raw_dwo_dwarf, -}; +use dwarfreader::{create_section_reader_object, get_endian, is_dwo_dwarf, is_non_dwo_dwarf}; use functions::parse_lexical_block; use gimli::{ @@ -46,6 +44,7 @@ use binaryninja::logger::Logger; use helpers::{get_build_id, load_debug_info_for_build_id}; use iset::IntervalMap; use log::{debug, error, warn}; +use object::{Object, ObjectSection}; trait ReaderType: Reader<Offset = usize> {} impl<T: Reader<Offset = usize>> ReaderType for T {} @@ -388,7 +387,7 @@ fn parse_unit<R: ReaderType>( } fn parse_unwind_section<R: Reader, U: UnwindSection<R>>( - view: &BinaryView, + file: &object::File, unwind_section: U, ) -> gimli::Result<iset::IntervalMap<u64, i64>> where @@ -396,40 +395,22 @@ where { let mut bases = gimli::BaseAddresses::default(); - // DWARF info is stored relative to the original image base (0 for relocatable images), normalize entries to the original image base - let section_adjustment = view.original_image_base().wrapping_sub(view.image_base()); - - if let Some(section) = view - .section_by_name(".eh_frame_hdr") - .or(view.section_by_name("__eh_frame_hdr")) - { - bases = bases.set_eh_frame_hdr(section.start().wrapping_add(section_adjustment)); + if let Some(section) = file.section_by_name(".eh_frame_hdr") { + bases = bases.set_eh_frame_hdr(section.address()); } - if let Some(section) = view - .section_by_name(".eh_frame") - .or(view.section_by_name("__eh_frame")) - { - bases = bases.set_eh_frame(section.start().wrapping_add(section_adjustment)); - } else if let Some(section) = view - .section_by_name(".debug_frame") - .or(view.section_by_name("__debug_frame")) - { - bases = bases.set_eh_frame(section.start().wrapping_add(section_adjustment)); + if let Some(section) = file.section_by_name(".eh_frame") { + bases = bases.set_eh_frame(section.address()); + } else if let Some(section) = file.section_by_name(".debug_frame") { + bases = bases.set_eh_frame(section.address()); } - if let Some(section) = view - .section_by_name(".text") - .or(view.section_by_name("__text")) - { - bases = bases.set_text(section.start().wrapping_add(section_adjustment)); + if let Some(section) = file.section_by_name(".text") { + bases = bases.set_text(section.address()); } - if let Some(section) = view - .section_by_name(".got") - .or(view.section_by_name("__got")) - { - bases = bases.set_got(section.start().wrapping_add(section_adjustment)); + if let Some(section) = file.section_by_name(".got") { + bases = bases.set_got(section.address()); } let mut cies = HashMap::new(); @@ -535,58 +516,49 @@ fn get_supplementary_build_id(bv: &BinaryView) -> Option<String> { } } -fn parse_range_data_offsets( - bv: &BinaryView, - dwo_file: bool, -) -> Option<Result<IntervalMap<u64, i64>, ()>> { - if bv.section_by_name(".eh_frame").is_some() || bv.section_by_name("__eh_frame").is_some() { - let eh_frame_endian = get_endian(bv); - let eh_frame_section_reader = |section_id: SectionId| -> _ { - create_section_reader(section_id, bv, eh_frame_endian, dwo_file) - }; - let mut eh_frame = match gimli::EhFrame::load(eh_frame_section_reader) { - Ok(x) => x, - Err(e) => { - log::error!("Failed to load EH frame: {}", e); - return None; - } - }; - if let Some(view_arch) = bv.default_arch() { - if view_arch.name().as_str() == "aarch64" { - eh_frame.set_vendor(gimli::Vendor::AArch64); - } +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))?; + let dwo_file = file.section_by_name(".debug_info.dwo").is_some(); + let endian = match file.endianness() { + object::Endianness::Little => gimli::RunTimeEndian::Little, + object::Endianness::Big => gimli::RunTimeEndian::Big, + }; + + let section_reader = |section_id: SectionId| -> _ { + create_section_reader_object(section_id, &file, endian, dwo_file) + }; + + if file.section_by_name(".eh_frame").is_some() { + let mut eh_frame = gimli::EhFrame::load(section_reader) + .map_err(|e| format!("Failed to load EH frame: {}", e))?; + + if file.architecture() == object::Architecture::Aarch64 { + eh_frame.set_vendor(gimli::Vendor::AArch64); } - eh_frame.set_address_size(bv.address_size() as u8); - Some( - parse_unwind_section(bv, eh_frame) - .map_err(|e| error!("Error parsing .eh_frame: {}", e)), - ) - } else if bv.section_by_name(".debug_frame").is_some() - || bv.section_by_name("__debug_frame").is_some() - { - let debug_frame_endian = get_endian(bv); - let debug_frame_section_reader = |section_id: SectionId| -> _ { - create_section_reader(section_id, bv, debug_frame_endian, dwo_file) - }; - let mut debug_frame = match gimli::DebugFrame::load(debug_frame_section_reader) { - Ok(x) => x, - Err(e) => { - log::error!("Failed to load debug frame: {}", e); - return None; - } - }; - if let Some(view_arch) = bv.default_arch() { - if view_arch.name().as_str() == "aarch64" { - debug_frame.set_vendor(gimli::Vendor::AArch64); - } + + if let Some(address_size) = file.architecture().address_size() { + eh_frame.set_address_size(address_size.bytes()); } - debug_frame.set_address_size(bv.address_size() as u8); - Some( - parse_unwind_section(bv, debug_frame) - .map_err(|e| error!("Error parsing .debug_frame: {}", e)), - ) + + parse_unwind_section(&file, eh_frame).map_err(|e| format!("Error parsing .eh_frame: {}", e)) + } else if file.section_by_name(".debug_frame").is_some() { + let mut debug_frame = gimli::DebugFrame::load(section_reader) + .map_err(|e| format!("Failed to load debug frame: {}", e))?; + + if file.architecture() == object::Architecture::Aarch64 { + debug_frame.set_vendor(gimli::Vendor::AArch64); + } + + if let Some(address_size) = file.architecture().address_size() { + debug_frame.set_address_size(address_size.bytes()); + } + parse_unwind_section(&file, debug_frame) + .map_err(|e| format!("Error parsing .debug_frame: {}", e)) } else { - None + Ok(Default::default()) } } @@ -595,35 +567,43 @@ fn parse_dwarf( debug_bv: &BinaryView, supplementary_bv: Option<&BinaryView>, progress: Box<dyn Fn(usize, usize) -> Result<(), ()>>, -) -> Result<DebugInfoBuilder, ()> { +) -> 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(())?; - let view = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) { - debug_bv + let raw_view = &debug_bv + .raw_view() + .ok_or("Failed to get raw view for debug binary view".to_string())?; + + let address_size = if is_dwo_dwarf(debug_bv) || is_non_dwo_dwarf(debug_bv) { + debug_bv.address_size() } else { - raw_view + raw_view.address_size() }; - let dwo_file = is_dwo_dwarf(view) || is_raw_dwo_dwarf(view); + // Parse this early to reduce peak memory usage + let range_data_offsets = parse_range_data_offsets(bv).unwrap_or_default(); - // gimli setup - let endian = get_endian(view); - let mut section_reader = - |section_id: SectionId| -> _ { create_section_reader(section_id, view, endian, dwo_file) }; + // 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, + object::Endianness::Big => gimli::RunTimeEndian::Big, + }; - let mut dwarf = match Dwarf::load(&mut section_reader) { - Ok(x) => x, - Err(e) => { - error!("Failed to load DWARF info: {}", e); - return Err(()); - } + let mut section_reader = |section_id: SectionId| -> _ { + create_section_reader_object(section_id, &debug_file, endian, dwo_file) }; + let mut dwarf = Dwarf::load(&mut section_reader) + .map_err(|e| format!("Failed to load DWARF info: {}", e))?; + if dwo_file { dwarf.file_type = DwarfFileType::Dwo; } else { @@ -631,31 +611,22 @@ fn parse_dwarf( } 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); - let sup_dwo_file = is_dwo_dwarf(sup_bv) || is_raw_dwo_dwarf(sup_bv); + let sup_dwo_file = sup_file.section_by_name(".debug_info.dwo").is_some(); let sup_section_reader = |section_id: SectionId| -> _ { - create_section_reader(section_id, sup_bv, sup_endian, sup_dwo_file) + create_section_reader_object(section_id, &sup_file, sup_endian, sup_dwo_file) }; if let Err(e) = dwarf.load_sup(sup_section_reader) { error!("Failed to load supplementary file: {}", e); } } - let range_data_offsets = match parse_range_data_offsets(bv, dwo_file) { - Some(x) => x?, - None => { - if let Some(raw_view) = bv.raw_view() { - if let Some(offsets) = parse_range_data_offsets(&raw_view, dwo_file) { - offsets? - } else { - Default::default() - } - } else { - Default::default() - } - } - }; - // Create debug info builder and recover name mapping first // Since DWARF is stored as a tree with arbitrary implicit edges among leaves, // it is not possible to correctly track namespaces while you're parsing "in order" without backtracking, @@ -663,7 +634,8 @@ fn parse_dwarf( let mut debug_info_builder = DebugInfoBuilder::new(); debug_info_builder.set_range_data_offsets(range_data_offsets); - if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(view, &dwarf) { + if let Some(mut debug_info_builder_context) = DebugInfoBuilderContext::new(address_size, &dwarf) + { calculate_total_unit_bytes(&dwarf, &mut debug_info_builder_context); let progress_weights = [0.5, 0.5]; @@ -770,7 +742,10 @@ impl CustomDebugInfoParser for DWARFParser { builder.post_process(bv, debug_info).commit_info(debug_info); true } - Err(_) => false, + Err(e) => { + log::error!("Failed to parse DWARF: {}", e); + false + } }; if let (Some(ext), true) = (external_file, close_external) { diff --git a/plugins/dwarf/shared/Cargo.toml b/plugins/dwarf/shared/Cargo.toml index 83ecee93..19cb963e 100644 --- a/plugins/dwarf/shared/Cargo.toml +++ b/plugins/dwarf/shared/Cargo.toml @@ -11,3 +11,4 @@ binaryninjacore-sys.workspace = true gimli = "0.31" zstd = "0.13.2" thiserror = "2.0" +object = "0.36" diff --git a/plugins/dwarf/shared/src/lib.rs b/plugins/dwarf/shared/src/lib.rs index 7aa3b486..634aa784 100644 --- a/plugins/dwarf/shared/src/lib.rs +++ b/plugins/dwarf/shared/src/lib.rs @@ -13,6 +13,7 @@ // limitations under the License. use gimli::{EndianRcSlice, Endianity, RunTimeEndian, SectionId}; +use object::{Object, ObjectSection}; use binaryninja::{ binary_view::{BinaryView, BinaryViewBase, BinaryViewExt}, @@ -35,6 +36,9 @@ pub enum Error { #[error("{0}")] IoError(#[from] std::io::Error), + + #[error("{0}")] + ObjectError(#[from] object::Error), } pub fn is_non_dwo_dwarf(view: &BinaryView) -> bool { @@ -89,105 +93,75 @@ pub fn get_endian(view: &BinaryView) -> RunTimeEndian { } } -pub fn create_section_reader<'a, Endian: 'a + Endianity>( +pub fn create_section_reader<Endian: Endianity>( section_id: SectionId, - view: &'a BinaryView, + view: &BinaryView, endian: Endian, - dwo_file: bool, -) -> Result<EndianRcSlice<Endian>, Error> { - let section_name = if dwo_file && section_id.dwo_name().is_some() { - section_id.dwo_name().unwrap() - } else { - section_id.name() - }; + is_dwo: bool, +) -> Result<RelocateOwned<gimli::EndianRcSlice<Endian>>, Error> { + let raw_view = view.raw_view().unwrap(); + let view_data = raw_view.read_vec(0, raw_view.len() as usize); + let file = object::File::parse(&*view_data)?; + create_section_reader_object(section_id, &file, endian, is_dwo) +} - if let Some(section) = view.section_by_name(section_name) { - // TODO : This is kinda broke....should add rust wrappers for some of this - if let Some(symbol) = view - .symbols() - .iter() - .find(|symbol| symbol.full_name().to_string_lossy() == "__elf_section_headers") - { - if let Some(data_var) = view - .data_variables() - .iter() - .find(|var| var.address == symbol.address()) - { - // TODO : This should eventually be wrapped by some DataView sorta thingy thing, like how python does it - let data_type = &data_var.ty.contents; - let data = view.read_vec(data_var.address, data_type.width() as usize); - let element_type = data_type.element_type().unwrap().contents; +#[derive(Debug, Clone)] +pub struct RelocationMap(Rc<object::read::RelocationMap>); - if let Some(current_section_header) = data - .chunks(element_type.width() as usize) - .find(|section_header| { - if view.address_size() == 4 { - endian.read_u32(§ion_header[16..20]) as u64 == section.start() - } else { - endian.read_u64(§ion_header[24..32]) == section.start() - } - }) - { - let section_flags = if view.address_size() == 4 { - endian.read_u32(¤t_section_header[8..12]) as u64 - } else { - endian.read_u64(¤t_section_header[8..16]) - }; - // If the section has the compressed bit set - if (section_flags & 2048) != 0 { - // Get section, trim header, decompress, return - let compressed_header_size = view.address_size() * 3; +impl Default for RelocationMap { + fn default() -> Self { + Self(Rc::new(object::read::RelocationMap::default())) + } +} - let offset = section.start() + compressed_header_size as u64; - let len = section.len() - compressed_header_size; +impl RelocationMap { + fn add(&mut self, file: &object::File, section: &object::Section) -> Result<(), Error> { + let map = + Rc::get_mut(&mut self.0).expect("Failed to get mutable reference to RelocationMap"); + for (offset, relocation) in section.relocations() { + map.add(file, offset, relocation)? + } + Ok(()) + } +} - let ch_type_vec = view.read_vec(section.start(), 4); - let ch_type = endian.read_u32(&ch_type_vec); +impl gimli::read::Relocate for RelocationMap { + fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result<u64> { + Ok(self.0.relocate(offset as u64, value)) + } - if let Ok(buffer) = view.read_buffer(offset, len) { - match ch_type { - 1 => { - return Ok(EndianRcSlice::new( - buffer.zlib_decompress().get_data().into(), - endian, - )); - } - 2 => { - return Ok(EndianRcSlice::new( - zstd::decode_all(buffer.get_data())?.as_slice().into(), - endian, - )); - } - x => { - return Err(Error::UnknownCompressionMethod(x)); - } - } - } - } - } - } - } - let offset = section.start(); - let len = section.len(); - if len == 0 { - Ok(EndianRcSlice::new(Rc::from([]), endian)) - } else { - Ok(EndianRcSlice::new( - Rc::from(view.read_vec(offset, len).as_slice()), - endian, - )) - } + fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> { + <usize as gimli::ReaderOffset>::from_u64(self.0.relocate(offset as u64, value as u64)) } - // Truncate Mach-O section names to 16 bytes - else if let Some(section) = view.section_by_name(&format!( - "__{}", - §ion_name[1..section_name.len().min(15)] - )) { - Ok(EndianRcSlice::new( - Rc::from(view.read_vec(section.start(), section.len()).as_slice()), - endian, - )) +} + +type RelocateOwned<R> = gimli::RelocateReader<R, RelocationMap>; + +pub fn create_section_reader_object<Endian: gimli::Endianity>( + id: gimli::SectionId, + file: &object::File, + endian: Endian, + is_dwo: bool, +) -> Result<RelocateOwned<gimli::EndianRcSlice<Endian>>, Error> { + let mut relocations = RelocationMap::default(); + let name = if is_dwo { + id.dwo_name() + } else if file.format() == object::BinaryFormat::Xcoff { + id.xcoff_name() } else { - Ok(EndianRcSlice::new(Rc::from([]), endian)) - } + Some(id.name()) + }; + let data = match name.and_then(|name| file.section_by_name(name)) { + Some(ref section) => { + // DWO sections never have relocations, so don't bother. + if !is_dwo { + relocations.add(file, section)?; + } + section.uncompressed_data()?.into_owned() + } + // Use a non-zero capacity so that `ReaderOffsetId`s are unique. + None => Vec::with_capacity(1), + }; + let section = EndianRcSlice::new(Rc::from(data.into_boxed_slice()), endian); + Ok(gimli::RelocateReader::new(section, relocations)) } |
