From 72b51a97e7f16f66b6cd58565f9615b5eba5ab19 Mon Sep 17 00:00:00 2001 From: Joe Rozner Date: Sun, 11 Oct 2026 08:35:23 +0000 Subject: [PATCH 1/3] [DWARF Import] Rebase data variable addresses like function addresses post_process rebases every function address by the view's start minus the file's original image base, but it left data variables at the addresses from DW_OP_addr/DW_OP_addrx. When the view isn't at the original image base (a PIE, which is loaded at 0x400000, or any file opened with a non-default loader.imageBase), every data variable was added at an unmapped address off by exactly that amount, and the ELF symbols at the right addresses got no DWARF type. Rebase data variables by the same amount, through a helper shared with the function rebase. Relocatable objects are left as they were: their addresses are offsets into sections that the view lays out one after another, so the rebase alone would move their data variables from unmapped addresses onto other sections' contents, such as code. --- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 24 +++++++++++++++---- plugins/dwarf/dwarf_import/src/helpers.rs | 5 ++++ plugins/dwarf/dwarf_import/src/lib.rs | 4 +++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 993e035991..464799f83f 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -14,7 +14,7 @@ use crate::{ functions::FrameBase, - helpers::{get_uid, resolve_specification, DieReference}, + helpers::{get_uid, rebase_offset, resolve_specification, DieReference}, ReaderType, }; @@ -30,6 +30,7 @@ use binaryninja::{ }; use gimli::{DebuggingInformationEntry, Dwarf, Unit}; +use object::ObjectKind; use binaryninja::confidence::{Conf, MAX_CONFIDENCE}; use binaryninja::variable::{Variable, VariableSourceType}; @@ -829,7 +830,12 @@ impl DebugInfoBuilder { } } - pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self { + pub(crate) fn post_process( + &mut self, + bv: &BinaryView, + _debug_info: &mut DebugInfo, + debug_file_kind: ObjectKind, + ) -> &Self { // When originally resolving names, we need to check: // If there's already a name from binja that's "more correct" than what we found (has more namespaces) // If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name @@ -867,8 +873,7 @@ impl DebugInfoBuilder { } if let Some(address) = func.address.as_mut() { - let (diff, overflowed) = bv.start().overflowing_sub(bv.original_image_base()); - if !overflowed { + if let Some(diff) = rebase_offset(bv) { *address = (*address).overflowing_add(diff).0; // rebase the address let existing_functions = bv.functions_at(*address); match existing_functions.len().cmp(&1) { @@ -884,6 +889,17 @@ impl DebugInfoBuilder { } } + // Addresses in a relocatable object are offsets into its sections, not addresses in the + // view, so rebasing wouldn't put its data variables on their symbols. Leave them be. + if debug_file_kind != ObjectKind::Relocatable { + if let Some(diff) = rebase_offset(bv).filter(|&diff| diff != 0) { + self.data_variables = std::mem::take(&mut self.data_variables) + .into_iter() + .map(|(address, variable)| (address.wrapping_add(diff), variable)) + .collect(); + } + } + self } diff --git a/plugins/dwarf/dwarf_import/src/helpers.rs b/plugins/dwarf/dwarf_import/src/helpers.rs index abffc6d8f5..96545b0aed 100644 --- a/plugins/dwarf/dwarf_import/src/helpers.rs +++ b/plugins/dwarf/dwarf_import/src/helpers.rs @@ -307,6 +307,11 @@ pub(crate) fn get_subrange_size(entry: &DebuggingInformationEntry } } +/// How far `bv` is rebased from the image base of the file, if not below it. +pub(crate) fn rebase_offset(bv: &BinaryView) -> Option { + bv.start().checked_sub(bv.original_image_base()) +} + // Get the start address of a function pub(crate) fn get_start_address( dwarf: &Dwarf, diff --git a/plugins/dwarf/dwarf_import/src/lib.rs b/plugins/dwarf/dwarf_import/src/lib.rs index 8d9068b92b..15acd0f3bd 100644 --- a/plugins/dwarf/dwarf_import/src/lib.rs +++ b/plugins/dwarf/dwarf_import/src/lib.rs @@ -837,7 +837,9 @@ impl CustomDebugInfoParser for DWARFParser { 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); + builder + .post_process(bv, debug_info, debug_file.kind()) + .commit_info(debug_info); true } Err(e) => { From 64b12f44a93a12eb8200ec8edf9610405483601f Mon Sep 17 00:00:00 2001 From: Joe Rozner Date: Sun, 11 Oct 2026 08:22:37 +0000 Subject: [PATCH 2/3] [DWARF Import] Relocate object file addresses to where the view placed their sections Every section of a relocatable ELF object is at address 0, so an address in its DWARF info is an offset into the section its relocation's symbol is in. The ELF view lays those sections out one after another from the image base, but the importer only added the image base, so everything outside the first section landed in the wrong place: with -ffunction-sections every function was at the image base, and on Xtensa, where .literal precedes .text, every function was 0x20 too low. When the debug file is a relocatable ELF object, look up where the view put each allocated section and add that to the relocations against symbols in it. Linked files are unaffected. With object addresses now relative to the image base like any other file's, rebase their data variables too, and drop the exception that left them unrebased. --- .../dwarf/dwarf_import/src/dwarfdebuginfo.rs | 22 ++--- plugins/dwarf/dwarf_import/src/lib.rs | 86 ++++++++++++++++--- plugins/dwarf/shared/src/lib.rs | 44 ++++++++-- 3 files changed, 119 insertions(+), 33 deletions(-) diff --git a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs index 464799f83f..91b77e48cb 100644 --- a/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs +++ b/plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs @@ -30,7 +30,6 @@ use binaryninja::{ }; use gimli::{DebuggingInformationEntry, Dwarf, Unit}; -use object::ObjectKind; use binaryninja::confidence::{Conf, MAX_CONFIDENCE}; use binaryninja::variable::{Variable, VariableSourceType}; @@ -830,12 +829,7 @@ impl DebugInfoBuilder { } } - pub(crate) fn post_process( - &mut self, - bv: &BinaryView, - _debug_info: &mut DebugInfo, - debug_file_kind: ObjectKind, - ) -> &Self { + pub(crate) fn post_process(&mut self, bv: &BinaryView, _debug_info: &mut DebugInfo) -> &Self { // When originally resolving names, we need to check: // If there's already a name from binja that's "more correct" than what we found (has more namespaces) // If there's no name for the DIE, but there's a linkage name that's resolved in binja to a usable name @@ -889,15 +883,11 @@ impl DebugInfoBuilder { } } - // Addresses in a relocatable object are offsets into its sections, not addresses in the - // view, so rebasing wouldn't put its data variables on their symbols. Leave them be. - if debug_file_kind != ObjectKind::Relocatable { - if let Some(diff) = rebase_offset(bv).filter(|&diff| diff != 0) { - self.data_variables = std::mem::take(&mut self.data_variables) - .into_iter() - .map(|(address, variable)| (address.wrapping_add(diff), variable)) - .collect(); - } + if let Some(diff) = rebase_offset(bv).filter(|&diff| diff != 0) { + self.data_variables = std::mem::take(&mut self.data_variables) + .into_iter() + .map(|(address, variable)| (address.wrapping_add(diff), variable)) + .collect(); } self diff --git a/plugins/dwarf/dwarf_import/src/lib.rs b/plugins/dwarf/dwarf_import/src/lib.rs index 15acd0f3bd..d73421293a 100644 --- a/plugins/dwarf/dwarf_import/src/lib.rs +++ b/plugins/dwarf/dwarf_import/src/lib.rs @@ -18,13 +18,13 @@ mod functions; mod helpers; mod types; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use crate::dwarfdebuginfo::{DebugInfoBuilder, DebugInfoBuilderContext}; use crate::functions::parse_function_entry; use crate::helpers::{ - find_local_debug_file_from_path, get_attr_die, get_name, get_uid, DieReference, + find_local_debug_file_from_path, get_attr_die, get_name, get_uid, rebase_offset, DieReference, }; use crate::types::parse_variable; @@ -34,7 +34,7 @@ use binaryninja::{ debuginfo::{CustomDebugInfoParser, DebugInfo, DebugInfoParser}, settings::Settings, }; -use dwarfreader::create_section_reader_object; +use dwarfreader::{create_section_reader_object, SectionAddresses}; use functions::parse_lexical_block; use gimli::{ @@ -45,7 +45,7 @@ use gimli::{ use helpers::{get_build_id, load_debug_info_for_build_id}; use iset::IntervalMap; use object::read::macho::FatArch; -use object::{Object, ObjectSection}; +use object::{BinaryFormat, Object, ObjectKind, ObjectSection, SectionFlags}; trait ReaderType: Reader {} impl> ReaderType for T {} @@ -488,7 +488,10 @@ where } } -fn parse_range_data_offsets(file: &object::File) -> Result, String> { +fn parse_range_data_offsets( + file: &object::File, + section_addresses: &SectionAddresses, +) -> 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, @@ -496,7 +499,7 @@ fn parse_range_data_offsets(file: &object::File) -> Result }; let section_reader = |section_id: SectionId| -> _ { - create_section_reader_object(section_id, &file, endian, dwo_file) + create_section_reader_object(section_id, &file, endian, dwo_file, section_addresses) }; if file.section_by_name(".eh_frame").is_some() { @@ -537,6 +540,53 @@ fn parse_range_data_offsets(file: &object::File) -> Result } } +/// Where `bv` placed the allocated sections of `file`, if it's a relocatable ELF object. +/// +/// The addresses are before rebasing, like the other addresses read from the DWARF info, since +/// [`DebugInfoBuilder::post_process`] rebases them all. Linked files keep their own addresses. +fn object_section_addresses(bv: &BinaryView, file: &object::File) -> SectionAddresses { + let mut section_addresses = SectionAddresses::new(); + if file.format() != BinaryFormat::Elf || file.kind() != ObjectKind::Relocatable { + return section_addresses; + } + + let rebase = rebase_offset(bv).unwrap_or(0); + let mut seen_names = HashSet::new(); + for section in file.sections() { + let Ok(name) = section.name() else { + continue; + }; + // The ELF view keeps the first section with a given name as is, and suffixes the + // name of each later one with `#` and its section index. + let view_name = if seen_names.insert(name) { + name.to_string() + } else { + format!("{name}#{}", section.index().0) + }; + + let SectionFlags::Elf { sh_flags } = section.flags() else { + continue; + }; + if sh_flags & u64::from(object::elf::SHF_ALLOC) == 0 || section.size() == 0 { + continue; + } + + match bv.section_by_name(view_name.as_str()) { + Some(view_section) if view_section.len() as u64 == section.size() => { + section_addresses.insert( + section.index(), + view_section.start().wrapping_sub(rebase), + ); + } + _ => tracing::debug!( + "Section {} of the object is not in the view; addresses in it will not be relocated", + view_name + ), + } + } + section_addresses +} + fn parse_dwarf( bv: &BinaryView, debug_file: &object::File, @@ -552,7 +602,9 @@ fn parse_dwarf( None => bv.address_size(), }; - let range_data_offsets = parse_range_data_offsets(debug_file).unwrap_or_default(); + let section_addresses = object_section_addresses(bv, debug_file); + let range_data_offsets = + parse_range_data_offsets(debug_file, §ion_addresses).unwrap_or_default(); let dwo_file = debug_file.section_by_name(".debug_info.dwo").is_some(); let endian = match debug_file.endianness() { @@ -561,7 +613,13 @@ fn parse_dwarf( }; let mut section_reader = |section_id: SectionId| -> _ { - create_section_reader_object(section_id, &debug_file, endian, dwo_file) + create_section_reader_object( + section_id, + &debug_file, + endian, + dwo_file, + §ion_addresses, + ) }; let mut dwarf = Dwarf::load(&mut section_reader) @@ -581,7 +639,13 @@ fn parse_dwarf( 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) + create_section_reader_object( + section_id, + &sup_file, + sup_endian, + sup_dwo_file, + &SectionAddresses::new(), + ) }; if let Err(e) = dwarf.load_sup(sup_section_reader) { tracing::error!("Failed to load supplementary file: {}", e); @@ -837,9 +901,7 @@ impl CustomDebugInfoParser for DWARFParser { let result = match parse_dwarf(bv, &debug_file, sup_file.as_ref(), progress) { Ok(mut builder) => { - builder - .post_process(bv, debug_info, debug_file.kind()) - .commit_info(debug_info); + builder.post_process(bv, debug_info).commit_info(debug_info); true } Err(e) => { diff --git a/plugins/dwarf/shared/src/lib.rs b/plugins/dwarf/shared/src/lib.rs index 5502788a27..9ee593a213 100644 --- a/plugins/dwarf/shared/src/lib.rs +++ b/plugins/dwarf/shared/src/lib.rs @@ -13,7 +13,7 @@ // limitations under the License. use gimli::{EndianRcSlice, Endianity, RunTimeEndian, SectionId}; -use object::{Object, ObjectSection}; +use object::{Object, ObjectSection, ObjectSymbol, RelocationTarget}; use binaryninja::{ binary_view::{BinaryView, BinaryViewBase}, @@ -22,6 +22,7 @@ use binaryninja::{ }; use binaryninja::settings::QueryOptions; +use std::collections::HashMap; use std::rc::Rc; ////////////////////// // Dwarf Validation @@ -102,9 +103,16 @@ pub fn create_section_reader( 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) + create_section_reader_object(section_id, &file, endian, is_dwo, &SectionAddresses::new()) } +/// The addresses to place the sections of a relocatable object at, by section index. +/// +/// Every section of a relocatable object is at address 0, so an address in its debug info is +/// an offset into the section that the relocation's symbol is defined in. Adding the address +/// the section was placed at turns it into an address in the view. +pub type SectionAddresses = HashMap; + #[derive(Debug, Clone)] pub struct RelocationMap(Rc); @@ -115,16 +123,41 @@ impl Default for RelocationMap { } impl RelocationMap { - fn add(&mut self, file: &object::File, section: &object::Section) -> Result<(), Error> { + fn add( + &mut self, + file: &object::File, + section: &object::Section, + section_addresses: &SectionAddresses, + ) -> Result<(), Error> { let map = Rc::get_mut(&mut self.0).expect("Failed to get mutable reference to RelocationMap"); - for (offset, relocation) in section.relocations() { + for (offset, mut relocation) in section.relocations() { + if let Some(address) = target_section_address(file, &relocation, section_addresses) { + relocation.set_addend(relocation.addend().wrapping_add(address as i64)); + } map.add(file, offset, relocation)? } Ok(()) } } +/// The address in `section_addresses` of the section that `relocation` refers into. +fn target_section_address( + file: &object::File, + relocation: &object::Relocation, + section_addresses: &SectionAddresses, +) -> Option { + if section_addresses.is_empty() { + return None; + } + let section_index = match relocation.target() { + RelocationTarget::Symbol(index) => file.symbol_by_index(index).ok()?.section_index()?, + RelocationTarget::Section(index) => index, + _ => return None, + }; + section_addresses.get(§ion_index).copied() +} + impl gimli::read::Relocate for RelocationMap { fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result { Ok(self.0.relocate(offset as u64, value)) @@ -142,6 +175,7 @@ pub fn create_section_reader_object( file: &object::File, endian: Endian, is_dwo: bool, + section_addresses: &SectionAddresses, ) -> Result>, Error> { let mut relocations = RelocationMap::default(); let name = if is_dwo { @@ -155,7 +189,7 @@ pub fn create_section_reader_object( Some(ref section) => { // DWO sections never have relocations, so don't bother. if !is_dwo { - relocations.add(file, section)?; + relocations.add(file, section, section_addresses)?; } section.uncompressed_data()?.into_owned() } From c912229718f597f941082e4a6a88ab6bb2d1f232 Mon Sep 17 00:00:00 2001 From: Joe Rozner Date: Sun, 11 Oct 2026 08:15:28 +0000 Subject: [PATCH 3/3] [DWARF Import] Apply RISC-V label-difference relocations in debug sections Loading a RISC-V relocatable object built with -g failed with "Failed to load DWARF info: Unsupported relocation type", so no debug info was imported. object's RelocationMap only accepts absolute relocations, and with linker relaxation GNU as encodes differences between code labels as relocation pairs at the same offset: R_RISCV_ADD*/SUB* (high_pc, line table and FDE lengths), R_RISCV_SET6/SUB6 and SET8/SUB8 (CFA advances) and R_RISCV_SET_ULEB128/SUB_ULEB128 (location and range list offsets). Apply these to the section data before handing it to gimli, in order, as binutils' perform_relocation does, following the psABI formulas. The object isn't relaxed, so this gives the right difference. ULEB128 values keep the length the assembler gave them, and SUB_ULEB128's addend is ignored like binutils does. R_RISCV_NONE and R_RISCV_RELAX are skipped, and R_RISCV_32/64 still go through the relocation map, which moves them to where the view placed their section. The pairs use the symbols' offsets into their sections as S, without the section's address in the view: both labels of a pair are in the same section, so their difference doesn't depend on where it was placed. --- plugins/dwarf/shared/src/lib.rs | 199 +++++++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 4 deletions(-) diff --git a/plugins/dwarf/shared/src/lib.rs b/plugins/dwarf/shared/src/lib.rs index 9ee593a213..543a9ab968 100644 --- a/plugins/dwarf/shared/src/lib.rs +++ b/plugins/dwarf/shared/src/lib.rs @@ -13,7 +13,7 @@ // limitations under the License. use gimli::{EndianRcSlice, Endianity, RunTimeEndian, SectionId}; -use object::{Object, ObjectSection, ObjectSymbol, RelocationTarget}; +use object::{elf, Object, ObjectSection, ObjectSymbol, RelocationTarget}; use binaryninja::{ binary_view::{BinaryView, BinaryViewBase}, @@ -40,6 +40,12 @@ pub enum Error { #[error("{0}")] ObjectError(#[from] object::Error), + + #[error("relocation at {0:#x} is outside its section")] + RelocationOutOfBounds(u64), + + #[error("invalid relocation at {offset:#x}: {reason}")] + InvalidRelocation { offset: u64, reason: &'static str }, } pub fn is_non_dwo_dwarf(view: &BinaryView) -> bool { @@ -123,21 +129,38 @@ impl Default for RelocationMap { } impl RelocationMap { + /// Adds the relocations of `section`, whose uncompressed contents are `data`. Relocations + /// that can't be expressed as an entry in the map are applied to `data` instead. + /// + /// Relocations that go in the map and refer into a section in `section_addresses` are moved + /// to where that section was placed. The ones applied to `data` aren't: they compute + /// differences between labels in the same section, which don't depend on where it is. fn add( &mut self, file: &object::File, section: &object::Section, + data: &mut [u8], section_addresses: &SectionAddresses, ) -> Result<(), Error> { let map = Rc::get_mut(&mut self.0).expect("Failed to get mutable reference to RelocationMap"); + let mut riscv = RiscvRelocations::default(); for (offset, mut relocation) in section.relocations() { + let keep = match file.architecture() { + object::Architecture::Riscv32 | object::Architecture::Riscv64 => { + riscv.apply(file, offset, &relocation, data)? + } + _ => true, + }; + if !keep { + continue; + } if let Some(address) = target_section_address(file, &relocation, section_addresses) { relocation.set_addend(relocation.addend().wrapping_add(address as i64)); } map.add(file, offset, relocation)? } - Ok(()) + riscv.finish() } } @@ -158,6 +181,173 @@ fn target_section_address( section_addresses.get(§ion_index).copied() } +/// Applies the RISC-V relocations that `object::read::RelocationMap` can't represent. +/// +/// With linker relaxation enabled, the assembler can't resolve differences between labels in +/// code, so it emits `R_RISCV_ADD*`/`R_RISCV_SUB*`, `R_RISCV_SET*`/`R_RISCV_SUB6` and +/// `R_RISCV_SET_ULEB128`/`R_RISCV_SUB_ULEB128` pairs at the same offset for the linker to +/// evaluate (e.g. `DW_AT_high_pc`, line table advances, CFA advances and location list ranges). +/// They are applied to the section data in order, as `perform_relocation` in binutils' +/// `bfd/elfnn-riscv.c` does; since the file isn't relaxed, this gives the unrelaxed difference. +#[derive(Default)] +struct RiscvRelocations { + /// The offset and value of an `R_RISCV_SET_ULEB128` waiting for its `R_RISCV_SUB_ULEB128`. + pending_set_uleb128: Option<(u64, u64)>, +} + +impl RiscvRelocations { + /// Applies `relocation` to `data` if needed. Returns `false` if the relocation has been + /// handled and shouldn't be added to the relocation map. + fn apply( + &mut self, + file: &object::File, + offset: u64, + relocation: &object::Relocation, + data: &mut [u8], + ) -> Result { + let object::RelocationFlags::Elf { r_type } = relocation.flags() else { + return Ok(true); + }; + // The psABI requires `R_RISCV_SUB_ULEB128` to immediately follow its `R_RISCV_SET_ULEB128`. + if let Some((set_offset, set_value)) = self.pending_set_uleb128.take() { + if r_type != elf::R_RISCV_SUB_ULEB128 || offset != set_offset { + return Err(Error::InvalidRelocation { + offset: set_offset, + reason: "R_RISCV_SET_ULEB128 without a matching R_RISCV_SUB_ULEB128", + }); + } + // Like binutils, ignore the addend: older assemblers emitted a bogus non-zero one. + let value = set_value.wrapping_sub(riscv_symbol_value(file, offset, relocation)?); + write_uleb128_in_place(data, offset, value)?; + return Ok(false); + } + + let (size, apply): (usize, fn(u64, u64) -> u64) = match r_type { + elf::R_RISCV_NONE | elf::R_RISCV_RELAX => return Ok(false), + elf::R_RISCV_ADD8 => (1, u64::wrapping_add), + elf::R_RISCV_ADD16 => (2, u64::wrapping_add), + elf::R_RISCV_ADD32 => (4, u64::wrapping_add), + elf::R_RISCV_ADD64 => (8, u64::wrapping_add), + elf::R_RISCV_SUB6 => (1, |old, value| { + (old & !0x3f) | (old.wrapping_sub(value) & 0x3f) + }), + elf::R_RISCV_SUB8 => (1, u64::wrapping_sub), + elf::R_RISCV_SUB16 => (2, u64::wrapping_sub), + elf::R_RISCV_SUB32 => (4, u64::wrapping_sub), + elf::R_RISCV_SUB64 => (8, u64::wrapping_sub), + elf::R_RISCV_SET6 => (1, |old, value| (old & !0x3f) | (value & 0x3f)), + elf::R_RISCV_SET8 => (1, |_, value| value), + elf::R_RISCV_SET16 => (2, |_, value| value), + elf::R_RISCV_SET32 => (4, |_, value| value), + elf::R_RISCV_SET_ULEB128 => { + let value = riscv_symbol_value(file, offset, relocation)? + .wrapping_add(relocation.addend() as u64); + self.pending_set_uleb128 = Some((offset, value)); + return Ok(false); + } + elf::R_RISCV_SUB_ULEB128 => { + return Err(Error::InvalidRelocation { + offset, + reason: "R_RISCV_SUB_ULEB128 without a preceding R_RISCV_SET_ULEB128", + }) + } + // Leave the rest (`R_RISCV_32` and `R_RISCV_64` in particular) to the map. + _ => return Ok(true), + }; + let value = + riscv_symbol_value(file, offset, relocation)?.wrapping_add(relocation.addend() as u64); + update_word_in_place(data, offset, size, file.is_little_endian(), |old| { + apply(old, value) + })?; + Ok(false) + } + + /// Checks that no relocation pair was left incomplete. + fn finish(self) -> Result<(), Error> { + match self.pending_set_uleb128 { + Some((offset, _)) => Err(Error::InvalidRelocation { + offset, + reason: "R_RISCV_SET_ULEB128 without a matching R_RISCV_SUB_ULEB128", + }), + None => Ok(()), + } + } +} + +/// Returns `S`, the value of the symbol or section that `relocation` refers to. +/// +/// In a relocatable object this is the offset into the symbol's section, not where the view +/// placed it, which is fine for the label differences it's used for. +fn riscv_symbol_value( + file: &object::File, + offset: u64, + relocation: &object::Relocation, +) -> Result { + match relocation.target() { + RelocationTarget::Symbol(index) => Ok(file.symbol_by_index(index)?.address()), + RelocationTarget::Section(index) => Ok(file.section_by_index(index)?.address()), + RelocationTarget::Absolute => Ok(0), + _ => Err(Error::InvalidRelocation { + offset, + reason: "unsupported relocation target", + }), + } +} + +/// Replaces the `size`-byte word at `offset` in `data` with `update` applied to it, truncated. +fn update_word_in_place( + data: &mut [u8], + offset: u64, + size: usize, + little_endian: bool, + update: impl FnOnce(u64) -> u64, +) -> Result<(), Error> { + let bytes = usize::try_from(offset) + .ok() + .and_then(|start| data.get_mut(start..)?.get_mut(..size)) + .ok_or(Error::RelocationOutOfBounds(offset))?; + let mut word = [0; 8]; + let old = if little_endian { + word[..size].copy_from_slice(bytes); + u64::from_le_bytes(word) + } else { + word[8 - size..].copy_from_slice(bytes); + u64::from_be_bytes(word) + }; + let new = update(old); + if little_endian { + bytes.copy_from_slice(&new.to_le_bytes()[..size]); + } else { + bytes.copy_from_slice(&new.to_be_bytes()[8 - size..]); + } + Ok(()) +} + +/// Replaces the ULEB128 at `offset` in `data` with `value`, keeping its encoded length: the +/// data that follows can't move, and the assembler leaves enough room for the final value. +fn write_uleb128_in_place(data: &mut [u8], offset: u64, mut value: u64) -> Result<(), Error> { + let bytes = usize::try_from(offset) + .ok() + .and_then(|start| { + let bytes = data.get_mut(start..)?; + let len = bytes.iter().position(|byte| byte & 0x80 == 0)? + 1; + bytes.get_mut(..len) + }) + .ok_or(Error::RelocationOutOfBounds(offset))?; + let last = bytes.len() - 1; + for (i, byte) in bytes.iter_mut().enumerate() { + *byte = (value & 0x7f) as u8 | if i < last { 0x80 } else { 0 }; + value >>= 7; + } + if value != 0 { + return Err(Error::InvalidRelocation { + offset, + reason: "ULEB128 value doesn't fit in the space the assembler left", + }); + } + Ok(()) +} + impl gimli::read::Relocate for RelocationMap { fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result { Ok(self.0.relocate(offset as u64, value)) @@ -187,11 +377,12 @@ pub fn create_section_reader_object( }; let data = match name.and_then(|name| file.section_by_name(name)) { Some(ref section) => { + let mut data = section.uncompressed_data()?.into_owned(); // DWO sections never have relocations, so don't bother. if !is_dwo { - relocations.add(file, section, section_addresses)?; + relocations.add(file, section, &mut data, section_addresses)?; } - section.uncompressed_data()?.into_owned() + data } // Use a non-zero capacity so that `ReaderOffsetId`s are unique. None => Vec::with_capacity(1),