summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Ferrell <josh@vector35.com>2025-10-16 12:19:57 -0400
committerJosh Ferrell <josh@vector35.com>2025-10-16 20:29:43 -0400
commitb6740d03abd22441e2e79e1d6a13c474e3fcba90 (patch)
treeb4b317e182f23a0b9f2f633b25c54bed814abb4b
parente75d64a86a128b9a8cb3cb606e5f9560e90a4758 (diff)
Apply relocations when parsing DWARF
-rw-r--r--Cargo.lock15
-rw-r--r--plugins/dwarf/dwarf_import/Cargo.toml3
-rw-r--r--plugins/dwarf/dwarf_import/src/dwarfdebuginfo.rs4
-rw-r--r--plugins/dwarf/dwarf_import/src/lib.rs207
-rw-r--r--plugins/dwarf/shared/Cargo.toml1
-rw-r--r--plugins/dwarf/shared/src/lib.rs158
6 files changed, 176 insertions, 212 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c09b9122..6ed30022 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -727,6 +727,7 @@ dependencies = [
"indexmap",
"iset",
"log",
+ "object 0.36.7",
"regex",
]
@@ -747,6 +748,7 @@ dependencies = [
"binaryninja",
"binaryninjacore-sys",
"gimli",
+ "object 0.36.7",
"thiserror 2.0.12",
"zstd",
]
@@ -1821,7 +1823,7 @@ dependencies = [
"hashbrown 0.14.5",
"indexmap",
"memchr",
- "ruzstd",
+ "ruzstd 0.5.0",
]
[[package]]
@@ -1830,7 +1832,9 @@ version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
+ "flate2",
"memchr",
+ "ruzstd 0.7.3",
]
[[package]]
@@ -2421,6 +2425,15 @@ dependencies = [
]
[[package]]
+name = "ruzstd"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
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(&section_header[16..20]) as u64 == section.start()
- } else {
- endian.read_u64(&section_header[24..32]) == section.start()
- }
- })
- {
- let section_flags = if view.address_size() == 4 {
- endian.read_u32(&current_section_header[8..12]) as u64
- } else {
- endian.read_u64(&current_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!(
- "__{}",
- &section_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))
}