diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/thorin-dwp/src/lib.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/thorin-dwp/src/lib.rs')
-rw-r--r-- | vendor/thorin-dwp/src/lib.rs | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/vendor/thorin-dwp/src/lib.rs b/vendor/thorin-dwp/src/lib.rs new file mode 100644 index 000000000..b084aa311 --- /dev/null +++ b/vendor/thorin-dwp/src/lib.rs @@ -0,0 +1,310 @@ +use std::{ + borrow::Cow, + collections::HashSet, + fmt, + path::{Path, PathBuf}, +}; + +use gimli::{EndianSlice, Reader}; +use object::{write::Object as WritableObject, FileKind, Object, ObjectSection}; +use tracing::{debug, trace}; + +use crate::{ + error::Result, + ext::EndianityExt, + index::Bucketable, + package::{dwo_identifier_of_unit, DwarfObject, InProgressDwarfPackage}, + relocate::{add_relocations, Relocate, RelocationMap}, +}; + +mod error; +mod ext; +mod index; +mod package; +mod relocate; +mod strings; + +pub use crate::error::Error; + +/// `Session` is expected to be implemented by users of `thorin`, allowing users of `thorin` to +/// decide how to manage data, rather than `thorin` having arenas internally. +pub trait Session<Relocations> { + /// Returns a reference to `data`'s contents with lifetime `'session`. + fn alloc_data<'session>(&'session self, data: Vec<u8>) -> &'session [u8]; + + /// Returns a reference to `data`'s contents with lifetime `'input`. + /// + /// If `Cow` is borrowed, then return the contained reference (`'input`). If `Cow` is owned, + /// then calls `alloc_data` to return a reference of lifetime `'session`, which is guaranteed + /// to be longer than `'input`, so can be returned. + fn alloc_owned_cow<'input, 'session: 'input>( + &'session self, + data: Cow<'input, [u8]>, + ) -> &'input [u8] { + match data { + Cow::Borrowed(data) => data, + Cow::Owned(data) => self.alloc_data(data), + } + } + + /// Returns a reference to `relocation` with lifetime `'session`. + fn alloc_relocation<'session>(&'session self, data: Relocations) -> &'session Relocations; + + /// Returns a reference to contents of file at `path` with lifetime `'session`. + fn read_input<'session>(&'session self, path: &Path) -> std::io::Result<&'session [u8]>; +} + +/// Should missing DWARF objects referenced by executables be skipped or result in an error? +/// +/// Referenced objects that are still missing when the DWARF package is finished will result in +/// an error. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum MissingReferencedObjectBehaviour { + /// Skip missing referenced DWARF objects - useful if this is expected, i.e. the path in the + /// executable is wrong, but the referenced object will be found because it is an input. + Skip, + /// Error when encountering missing referenced DWARF objects. + Error, +} + +impl MissingReferencedObjectBehaviour { + /// Should missing referenced objects be skipped? + pub fn skip_missing(&self) -> bool { + match *self { + MissingReferencedObjectBehaviour::Skip => true, + MissingReferencedObjectBehaviour::Error => false, + } + } +} + +/// Builder for DWARF packages, add input objects/packages with `add_input_object` or input objects +/// referenced by an executable with `add_executable` before accessing the completed object with +/// `finish`. +pub struct DwarfPackage<'output, 'session: 'output, Sess: Session<RelocationMap>> { + sess: &'session Sess, + maybe_in_progress: Option<InProgressDwarfPackage<'output>>, + targets: HashSet<DwarfObject>, +} + +impl<'output, 'session: 'output, Sess> fmt::Debug for DwarfPackage<'output, 'session, Sess> +where + Sess: Session<RelocationMap>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DwarfPackage") + .field("in_progress", &self.maybe_in_progress) + .field("target_count", &self.targets.len()) + .finish() + } +} + +impl<'output, 'session: 'output, Sess> DwarfPackage<'output, 'session, Sess> +where + Sess: Session<RelocationMap>, +{ + /// Create a new `DwarfPackage` with the provided `Session` implementation. + pub fn new(sess: &'session Sess) -> Self { + Self { sess, maybe_in_progress: None, targets: HashSet::new() } + } + + /// Add an input object to the in-progress package. + #[tracing::instrument(level = "trace", skip(obj))] + fn process_input_object<'input>(&mut self, obj: &'input object::File<'input>) -> Result<()> { + if self.maybe_in_progress.is_none() { + self.maybe_in_progress = + Some(InProgressDwarfPackage::new(obj.architecture(), obj.endianness())); + } + + let encoding = if let Some(section) = obj.section_by_name(".debug_info.dwo") { + let data = section.compressed_data()?.decompress()?; + let data_ref = self.sess.alloc_owned_cow(data); + let debug_info = gimli::DebugInfo::new(data_ref, obj.endianness().as_runtime_endian()); + debug_info + .units() + .next() + .map_err(Error::ParseUnitHeader)? + .map(|root_header| root_header.encoding()) + .ok_or(Error::NoCompilationUnits)? + } else { + debug!("no `.debug_info.dwo` in input dwarf object"); + return Ok(()); + }; + + let sess = self.sess; + self.maybe_in_progress + .as_mut() + .expect("`process_input_object` is broken") + .add_input_object(sess, obj, encoding) + } + + /// Add input objects referenced by executable to the DWARF package. + #[tracing::instrument(level = "trace")] + pub fn add_executable( + &mut self, + path: &Path, + missing_behaviour: MissingReferencedObjectBehaviour, + ) -> Result<()> { + let data = self.sess.read_input(path).map_err(Error::ReadInput)?; + let obj = object::File::parse(data).map_err(Error::ParseObjectFile)?; + + let mut load_section = |id: gimli::SectionId| -> Result<_> { + let mut relocations = RelocationMap::default(); + let data = match obj.section_by_name(&id.name()) { + Some(ref section) => { + add_relocations(&mut relocations, &obj, section)?; + section.compressed_data()?.decompress()? + } + // Use a non-zero capacity so that `ReaderOffsetId`s are unique. + None => Cow::Owned(Vec::with_capacity(1)), + }; + + let data_ref = self.sess.alloc_owned_cow(data); + let reader = EndianSlice::new(data_ref, obj.endianness().as_runtime_endian()); + let section = reader; + let relocations = self.sess.alloc_relocation(relocations); + Ok(Relocate { relocations, section, reader }) + }; + + let dwarf = gimli::Dwarf::load(&mut load_section)?; + + let mut iter = dwarf.units(); + while let Some(header) = iter.next().map_err(Error::ParseUnitHeader)? { + let unit = dwarf.unit(header).map_err(Error::ParseUnit)?; + + let target = match dwo_identifier_of_unit(&dwarf.debug_abbrev, &unit.header)? { + Some(target) => target, + None => { + debug!("no target"); + continue; + } + }; + + let dwo_name = { + let mut cursor = unit.header.entries(&unit.abbreviations); + cursor.next_dfs()?; + let root = cursor.current().expect("unit w/out root debugging information entry"); + + let dwo_name = if let Some(val) = root.attr_value(gimli::DW_AT_dwo_name)? { + // DWARF 5 + val + } else if let Some(val) = root.attr_value(gimli::DW_AT_GNU_dwo_name)? { + // GNU Extension + val + } else { + return Err(Error::MissingDwoName(target.index())); + }; + + dwarf.attr_string(&unit, dwo_name)?.to_string()?.into_owned() + }; + + // Prepend the compilation directory if it exists. + let mut path = if let Some(comp_dir) = &unit.comp_dir { + PathBuf::from(comp_dir.to_string()?.into_owned()) + } else { + PathBuf::new() + }; + path.push(dwo_name); + + // Only add `DwoId`s to the targets, not `DebugTypeSignature`s. There doesn't + // appear to be a "skeleton type unit" to find the corresponding unit of (there are + // normal type units in an executable, but should we expect to find a corresponding + // split type unit for those?). + if matches!(target, DwarfObject::Compilation(_)) { + // Input objects are processed first, if a DWARF object referenced by this + // executable was already found then don't add it to the target and try to add it + // again. + if let Some(package) = &self.maybe_in_progress { + if package.contained_units().contains(&target) { + continue; + } + } + + debug!(?target, "adding target"); + self.targets.insert(target); + } + + match self.add_input_object(&path) { + Ok(()) => (), + Err(Error::ReadInput(..)) if missing_behaviour.skip_missing() => (), + Err(e) => return Err(e), + } + } + + Ok(()) + } + + /// Add an input object to the DWARF package. + /// + /// Input object must be an archive or an elf object. + #[tracing::instrument(level = "trace")] + pub fn add_input_object(&mut self, path: &Path) -> Result<()> { + let data = self.sess.read_input(&path).map_err(Error::ReadInput)?; + + let kind = FileKind::parse(data).map_err(Error::ParseFileKind)?; + trace!(?kind); + match kind { + FileKind::Archive => { + let archive = object::read::archive::ArchiveFile::parse(data) + .map_err(Error::ParseArchiveFile)?; + + for member in archive.members() { + let member = member.map_err(Error::ParseArchiveMember)?; + let data = member.data(data)?; + + let kind = if let Ok(kind) = FileKind::parse(data) { + kind + } else { + trace!("skipping non-elf archive member"); + continue; + }; + + trace!(?kind, "archive member"); + match kind { + FileKind::Elf32 | FileKind::Elf64 => { + let obj = object::File::parse(data).map_err(Error::ParseObjectFile)?; + self.process_input_object(&obj)?; + } + _ => { + trace!("skipping non-elf archive member"); + } + } + } + + Ok(()) + } + FileKind::Elf32 | FileKind::Elf64 => { + let obj = object::File::parse(data).map_err(Error::ParseObjectFile)?; + self.process_input_object(&obj) + } + _ => Err(Error::InvalidInputKind), + } + } + + /// Returns the `object::write::Object` containing the created DWARF package. + /// + /// Returns an `Error::MissingReferencedUnit` if DWARF objects referenced by executables were + /// not subsequently found. + /// Returns an `Error::NoOutputObjectCreated` if no input objects or executables were provided. + #[tracing::instrument(level = "trace")] + pub fn finish(self) -> Result<WritableObject<'output>> { + match self.maybe_in_progress { + Some(package) => { + if let Some(missing) = self.targets.difference(package.contained_units()).next() { + return Err(Error::MissingReferencedUnit(missing.index())); + } + + package.finish() + } + None if !self.targets.is_empty() => { + let first_missing_unit = self + .targets + .iter() + .next() + .copied() + .expect("non-empty map doesn't have first element"); + Err(Error::MissingReferencedUnit(first_missing_unit.index())) + } + None => Err(Error::NoOutputObjectCreated), + } + } +} |