diff --git a/Cargo.lock b/Cargo.lock index cfbca1a..75c214e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -722,7 +722,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] @@ -3505,6 +3505,7 @@ dependencies = [ "syn", "tempfile", "time", + "typed-arena", "typed-path", "unarm", "winapi", @@ -4014,7 +4015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" dependencies = [ "heck", - "itertools 0.13.0", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -4034,7 +4035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -5620,6 +5621,12 @@ dependencies = [ "rustc-hash 2.1.1", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typed-path" version = "0.11.0" @@ -6257,7 +6264,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/objdiff-core/Cargo.toml b/objdiff-core/Cargo.toml index d0667ff..a80ab9d 100644 --- a/objdiff-core/Cargo.toml +++ b/objdiff-core/Cargo.toml @@ -62,7 +62,10 @@ config = [ "dep:semver", "dep:typed-path", ] -dwarf = ["dep:gimli"] +dwarf = [ + "dep:gimli", + "dep:typed-arena", +] serde = [ "dep:pbjson", "dep:pbjson-build", @@ -78,6 +81,7 @@ std = [ "prost?/std", "serde?/std", "similar?/std", + "typed-arena?/std", "typed-path?/std", "dep:filetime", "dep:memmap2", @@ -143,6 +147,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"], # dwarf gimli = { version = "0.32", default-features = false, features = ["read"], optional = true } +typed-arena = { version = "2.0", default-features = false, optional = true } # ppc cwdemangle = { version = "1.0", optional = true } diff --git a/objdiff-core/src/obj/dwarf2.rs b/objdiff-core/src/obj/dwarf2.rs new file mode 100644 index 0000000..827d98c --- /dev/null +++ b/objdiff-core/src/obj/dwarf2.rs @@ -0,0 +1,106 @@ +use anyhow::{Context, Result}; +use object::{Object, ObjectSection}; +use typed_arena::Arena; + +use crate::obj::{Section, SectionKind}; + +/// Parse line information from DWARF 2+ sections. +pub(crate) fn parse_line_info_dwarf2( + obj_file: &object::File, + sections: &mut [Section], +) -> Result<()> { + let arena_data = Arena::new(); + let arena_relocations = Arena::new(); + let endian = match obj_file.endianness() { + object::Endianness::Little => gimli::RunTimeEndian::Little, + object::Endianness::Big => gimli::RunTimeEndian::Big, + }; + let dwarf = gimli::Dwarf::load(|id: gimli::SectionId| -> Result<_> { + load_file_section(id, obj_file, endian, &arena_data, &arena_relocations) + }) + .context("loading DWARF sections")?; + + let mut iter = dwarf.units(); + if let Some(header) = iter.next().map_err(|e| gimli_error(e, "iterating over DWARF units"))? { + let unit = dwarf.unit(header).map_err(|e| gimli_error(e, "loading DWARF unit"))?; + if let Some(program) = unit.line_program.clone() { + let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code); + let mut lines = text_sections.next().map(|section| &mut section.line_info); + + let mut rows = program.rows(); + while let Some((_header, row)) = + rows.next_row().map_err(|e| gimli_error(e, "loading program row"))? + { + if let (Some(line), Some(lines)) = (row.line(), &mut lines) { + lines.insert(row.address(), line.get() as u32); + } + if row.end_sequence() { + // The next row is the start of a new sequence, which means we must + // advance to the next .text section. + lines = text_sections.next().map(|section| &mut section.line_info); + } + } + } + } + if iter.next().map_err(|e| gimli_error(e, "checking for next unit"))?.is_some() { + log::warn!("Multiple units found in DWARF data, only processing the first"); + } + + Ok(()) +} + +#[derive(Debug, Default)] +struct RelocationMap(object::read::RelocationMap); + +impl RelocationMap { + fn add(&mut self, file: &object::File, section: &object::Section) { + for (offset, relocation) in section.relocations() { + if let Err(e) = self.0.add(file, offset, relocation) { + log::error!( + "Relocation error for section {} at offset 0x{:08x}: {}", + section.name().unwrap(), + offset, + e + ); + } + } + } +} + +impl gimli::read::Relocate for &'_ RelocationMap { + fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result { + Ok(self.0.relocate(offset as u64, value)) + } + + fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result { + ::from_u64(self.0.relocate(offset as u64, value as u64)) + } +} + +type Relocate<'a, R> = gimli::RelocateReader; + +fn load_file_section<'input, 'arena, Endian: gimli::Endianity>( + id: gimli::SectionId, + file: &object::File<'input>, + endian: Endian, + arena_data: &'arena Arena>, + arena_relocations: &'arena Arena, +) -> Result>> { + let mut relocations = RelocationMap::default(); + let data = match file.section_by_name(id.name()) { + Some(ref section) => { + relocations.add(file, section); + section.uncompressed_data()? + } + // Use a non-zero capacity so that `ReaderOffsetId`s are unique. + None => alloc::borrow::Cow::Owned(Vec::with_capacity(1)), + }; + let data_ref = arena_data.alloc(data); + let section = gimli::EndianSlice::new(data_ref, endian); + let relocations = arena_relocations.alloc(relocations); + Ok(Relocate::new(section, relocations)) +} + +fn gimli_error(e: gimli::Error, context: &str) -> anyhow::Error { + anyhow::anyhow!("gimli error {context}: {e:?}") +} diff --git a/objdiff-core/src/obj/mod.rs b/objdiff-core/src/obj/mod.rs index 22b909f..96a6df4 100644 --- a/objdiff-core/src/obj/mod.rs +++ b/objdiff-core/src/obj/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "dwarf")] +mod dwarf2; pub mod read; pub mod split_meta; diff --git a/objdiff-core/src/obj/read.rs b/objdiff-core/src/obj/read.rs index f86c0de..bea3281 100644 --- a/objdiff-core/src/obj/read.rs +++ b/objdiff-core/src/obj/read.rs @@ -578,6 +578,28 @@ fn parse_line_info( obj_data: &[u8], ) -> Result<()> { // DWARF 1.1 + if let Err(e) = parse_line_info_dwarf1(obj_file, sections) { + log::warn!("Failed to parse DWARF 1.1 line info: {e}"); + } + + // DWARF 2+ + #[cfg(feature = "dwarf")] + if let Err(e) = super::dwarf2::parse_line_info_dwarf2(obj_file, sections) { + log::warn!("Failed to parse DWARF 2+ line info: {e}"); + } + + // COFF + if let object::File::Coff(coff) = obj_file + && let Err(e) = parse_line_info_coff(coff, sections, section_indices, obj_data) + { + log::warn!("Failed to parse COFF line info: {e}"); + } + + Ok(()) +} + +/// Parse .line section from DWARF 1.1 format. +fn parse_line_info_dwarf1(obj_file: &object::File, sections: &mut [Section]) -> Result<()> { if let Some(section) = obj_file.section_by_name(".line") { let data = section.uncompressed_data()?; let mut reader: &[u8] = data.as_ref(); @@ -605,55 +627,6 @@ fn parse_line_info( } } } - - // DWARF 2+ - #[cfg(feature = "dwarf")] - { - fn gimli_error(e: gimli::Error) -> anyhow::Error { anyhow::anyhow!("DWARF error: {e:?}") } - let dwarf_cow = gimli::DwarfSections::load(|id| { - Ok::<_, gimli::Error>( - obj_file - .section_by_name(id.name()) - .and_then(|section| section.uncompressed_data().ok()) - .unwrap_or(alloc::borrow::Cow::Borrowed(&[][..])), - ) - }) - .map_err(gimli_error)?; - let endian = match obj_file.endianness() { - object::Endianness::Little => gimli::RunTimeEndian::Little, - object::Endianness::Big => gimli::RunTimeEndian::Big, - }; - let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian)); - let mut iter = dwarf.units(); - if let Some(header) = iter.next().map_err(gimli_error)? { - let unit = dwarf.unit(header).map_err(gimli_error)?; - if let Some(program) = unit.line_program.clone() { - let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code); - let mut lines = text_sections.next().map(|section| &mut section.line_info); - - let mut rows = program.rows(); - while let Some((_header, row)) = rows.next_row().map_err(gimli_error)? { - if let (Some(line), Some(lines)) = (row.line(), &mut lines) { - lines.insert(row.address(), line.get() as u32); - } - if row.end_sequence() { - // The next row is the start of a new sequence, which means we must - // advance to the next .text section. - lines = text_sections.next().map(|section| &mut section.line_info); - } - } - } - } - if iter.next().map_err(gimli_error)?.is_some() { - log::warn!("Multiple units found in DWARF data, only processing the first"); - } - } - - // COFF - if let object::File::Coff(coff) = obj_file { - parse_line_info_coff(coff, sections, section_indices, obj_data)?; - } - Ok(()) }