Don't fail on line info parsing; use gimli::RelocateReader

Workaround for #228
This commit is contained in:
Luke Street 2025-08-02 11:22:02 -06:00
parent 1e62d4664c
commit 0dc123b064
5 changed files with 147 additions and 54 deletions

15
Cargo.lock generated
View File

@ -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]]

View File

@ -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 }

View File

@ -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<u64> {
Ok(self.0.relocate(offset as u64, value))
}
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))
}
}
type Relocate<'a, R> = gimli::RelocateReader<R, &'a RelocationMap>;
fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
id: gimli::SectionId,
file: &object::File<'input>,
endian: Endian,
arena_data: &'arena Arena<alloc::borrow::Cow<'input, [u8]>>,
arena_relocations: &'arena Arena<RelocationMap>,
) -> Result<Relocate<'arena, gimli::EndianSlice<'arena, Endian>>> {
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:?}")
}

View File

@ -1,3 +1,5 @@
#[cfg(feature = "dwarf")]
mod dwarf2;
pub mod read;
pub mod split_meta;

View File

@ -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(())
}