use std::{collections::HashSet, fmt};
use gimli::{Encoding, RunTimeEndian, UnitHeader, UnitIndex, UnitSectionOffset, UnitType};
use object::{
write::{Object as WritableObject, SectionId},
BinaryFormat, Object, ObjectSection, SectionKind,
};
use tracing::debug;
use crate::{
error::{Error, Result},
ext::{CompressedDataRangeExt, EndianityExt, IndexSectionExt, PackageFormatExt},
index::{write_index, Bucketable, Contribution, ContributionOffset, IndexEntry},
relocate::RelocationMap,
strings::PackageStringTable,
Session,
};
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub(crate) struct DwoId(pub(crate) u64);
impl Bucketable for DwoId {
fn index(&self) -> u64 {
self.0
}
}
impl fmt::Debug for DwoId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DwoId({:#x})", self.0)
}
}
impl From<gimli::DwoId> for DwoId {
fn from(dwo_id: gimli::DwoId) -> Self {
Self(dwo_id.0)
}
}
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub(crate) struct DebugTypeSignature(pub(crate) u64);
impl Bucketable for DebugTypeSignature {
fn index(&self) -> u64 {
self.0
}
}
impl fmt::Debug for DebugTypeSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DebugTypeSignature({:#x})", self.0)
}
}
impl From<gimli::DebugTypeSignature> for DebugTypeSignature {
fn from(signature: gimli::DebugTypeSignature) -> Self {
Self(signature.0)
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) enum DwarfObject {
Compilation(DwoId),
Type(DebugTypeSignature),
}
impl Bucketable for DwarfObject {
fn index(&self) -> u64 {
match *self {
DwarfObject::Compilation(dwo_id) => dwo_id.index(),
DwarfObject::Type(type_signature) => type_signature.index(),
}
}
}
#[tracing::instrument(level = "trace", skip(debug_abbrev, header))]
pub(crate) fn dwo_identifier_of_unit<R: gimli::Reader>(
debug_abbrev: &gimli::DebugAbbrev<R>,
header: &gimli::UnitHeader<R>,
) -> Result<Option<DwarfObject>> {
match header.type_() {
UnitType::Skeleton(dwo_id) | UnitType::SplitCompilation(dwo_id) => {
Ok(Some(DwarfObject::Compilation(dwo_id.into())))
}
UnitType::Compilation => {
let abbreviations =
header.abbreviations(&debug_abbrev).map_err(Error::ParseUnitAbbreviations)?;
let mut cursor = header.entries(&abbreviations);
cursor.next_dfs()?;
let root = cursor.current().ok_or(Error::NoDie)?;
match root.tag() {
gimli::DW_TAG_compile_unit | gimli::DW_TAG_type_unit => (),
_ => return Err(Error::TopLevelDieNotUnit),
}
let mut attrs = root.attrs();
while let Some(attr) = attrs.next().map_err(Error::ParseUnitAttribute)? {
match (attr.name(), attr.value()) {
(gimli::constants::DW_AT_GNU_dwo_id, gimli::AttributeValue::DwoId(dwo_id)) => {
return Ok(Some(DwarfObject::Compilation(dwo_id.into())))
}
_ => (),
}
}
Ok(None)
}
UnitType::SplitType { type_signature, .. } => {
Ok(Some(DwarfObject::Type(type_signature.into())))
}
UnitType::Type { type_signature, .. } => Ok(Some(DwarfObject::Type(type_signature.into()))),
_ => Ok(None),
}
}
enum UnitHeaderIterator<R: gimli::Reader> {
DebugInfo(gimli::read::DebugInfoUnitHeadersIter<R>),
DebugTypes(gimli::read::DebugTypesUnitHeadersIter<R>),
}
impl<R: gimli::Reader> UnitHeaderIterator<R> {
fn next(&mut self) -> gimli::read::Result<Option<UnitHeader<R>>> {
match self {
UnitHeaderIterator::DebugInfo(iter) => iter.next(),
UnitHeaderIterator::DebugTypes(iter) => iter.next(),
}
}
}
pub(crate) fn maybe_load_index_section<'input, 'session: 'input, Endian, Index, R, Sess>(
sess: &'session Sess,
encoding: Encoding,
endian: Endian,
input: &object::File<'input>,
) -> Result<Option<UnitIndex<R>>>
where
Endian: gimli::Endianity,
Index: IndexSectionExt<'input, Endian, R>,
R: gimli::Reader,
Sess: Session<RelocationMap>,
{
let index_name = Index::id().dwo_name().expect("index id w/out known value");
if let Some(index_section) = input.section_by_name(index_name) {
let index_data = index_section
.compressed_data()
.and_then(|d| d.decompress())
.map_err(Error::DecompressData)?;
let index_data_ref = sess.alloc_owned_cow(index_data);
let unit_index = Index::new(index_data_ref, endian)
.index()
.map_err(|e| Error::ParseIndex(e, index_name.to_string()))?;
if !encoding.is_compatible_dwarf_package_index_version(unit_index.version()) {
return Err(Error::IncompatibleIndexVersion(
index_name.to_string(),
encoding.dwarf_package_index_version(),
unit_index.version(),
));
}
Ok(Some(unit_index))
} else {
Ok(None)
}
}
pub(crate) fn create_contribution_adjustor<'input, R: 'input>(
cu_index: Option<&'input UnitIndex<R>>,
tu_index: Option<&'input UnitIndex<R>>,
target_section_id: gimli::IndexSectionId,
) -> impl FnMut(DwarfObject, Option<Contribution>) -> Result<Option<Contribution>> + 'input
where
R: gimli::Reader,
{
let mut cu_adjustment = 0;
let mut tu_adjustment = 0;
move |identifier: DwarfObject,
contribution: Option<Contribution>|
-> Result<Option<Contribution>> {
let (adjustment, index) = match identifier {
DwarfObject::Compilation(_) => (&mut cu_adjustment, &cu_index),
DwarfObject::Type(_) => (&mut tu_adjustment, &tu_index),
};
match (index, contribution) {
(Some(index), Some(contribution)) => {
let idx = identifier.index();
let row_id = index.find(idx).ok_or(Error::UnitNotInIndex(idx))?;
let section = index
.sections(row_id)
.map_err(|e| Error::RowNotInIndex(e, row_id))?
.find(|index_section| index_section.section == target_section_id)
.ok_or(Error::SectionNotInRow)?;
let adjusted_offset: u64 = contribution.offset.0 + *adjustment;
*adjustment += section.size as u64;
Ok(Some(Contribution {
offset: ContributionOffset(adjusted_offset),
size: section.size as u64,
}))
}
(Some(_) | None, None) => Ok(contribution),
(None, Some(_)) => Ok(contribution),
}
}
}
struct DwarfPackageObject<'file> {
obj: WritableObject<'file>,
debug_cu_index: Option<SectionId>,
debug_tu_index: Option<SectionId>,
debug_info: Option<SectionId>,
debug_abbrev: Option<SectionId>,
debug_str: Option<SectionId>,
debug_types: Option<SectionId>,
debug_line: Option<SectionId>,
debug_loc: Option<SectionId>,
debug_loclists: Option<SectionId>,
debug_rnglists: Option<SectionId>,
debug_str_offsets: Option<SectionId>,
debug_macinfo: Option<SectionId>,
debug_macro: Option<SectionId>,
}
macro_rules! generate_append_for {
( $( $fn_name:ident => ($name:ident, $section_name:expr) ),+ ) => {
$(
fn $fn_name(&mut self, data: &[u8]) -> Option<Contribution> {
if data.is_empty() {
return None;
}
let id = *self.$name.get_or_insert_with(|| self.obj.add_section(
Vec::new(),
Vec::from($section_name),
SectionKind::Debug,
));
let offset = self.obj.append_section_data(id, data, 1);
debug!(?offset, ?data);
Some(Contribution {
offset: ContributionOffset(offset),
size: data.len().try_into().expect("data size larger than u64"),
})
}
)+
};
}
impl<'file> DwarfPackageObject<'file> {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(
architecture: object::Architecture,
endianness: object::Endianness,
) -> DwarfPackageObject<'file> {
let obj = WritableObject::new(BinaryFormat::Elf, architecture, endianness);
Self {
obj,
debug_cu_index: Default::default(),
debug_tu_index: Default::default(),
debug_info: Default::default(),
debug_abbrev: Default::default(),
debug_str: Default::default(),
debug_types: Default::default(),
debug_line: Default::default(),
debug_loc: Default::default(),
debug_loclists: Default::default(),
debug_rnglists: Default::default(),
debug_str_offsets: Default::default(),
debug_macinfo: Default::default(),
debug_macro: Default::default(),
}
}
generate_append_for! {
append_to_debug_abbrev => (debug_abbrev, ".debug_abbrev.dwo"),
append_to_debug_cu_index => (debug_cu_index, ".debug_cu_index"),
append_to_debug_info => (debug_info, ".debug_info.dwo"),
append_to_debug_line => (debug_line, ".debug_line.dwo"),
append_to_debug_loc => (debug_loc, ".debug_loc.dwo"),
append_to_debug_loclists => (debug_loclists, ".debug_loclists.dwo"),
append_to_debug_macinfo => (debug_macinfo, ".debug_macinfo.dwo"),
append_to_debug_macro => (debug_macro, ".debug_macro.dwo"),
append_to_debug_rnglists => (debug_rnglists, ".debug_rnglists.dwo"),
append_to_debug_str => (debug_str, ".debug_str.dwo"),
append_to_debug_str_offsets => (debug_str_offsets, ".debug_str_offsets.dwo"),
append_to_debug_tu_index => (debug_tu_index, ".debug_tu_index"),
append_to_debug_types => (debug_types, ".debug_types.dwo")
}
pub(crate) fn finish(self) -> WritableObject<'file> {
self.obj
}
}
pub(crate) struct InProgressDwarfPackage<'file> {
endian: RunTimeEndian,
obj: DwarfPackageObject<'file>,
string_table: PackageStringTable,
cu_index_entries: Vec<IndexEntry>,
tu_index_entries: Vec<IndexEntry>,
contained_units: HashSet<DwarfObject>,
}
impl<'file> InProgressDwarfPackage<'file> {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(
architecture: object::Architecture,
endianness: object::Endianness,
) -> InProgressDwarfPackage<'file> {
let endian = endianness.as_runtime_endian();
Self {
endian,
obj: DwarfPackageObject::new(architecture, endianness),
string_table: PackageStringTable::new(),
cu_index_entries: Default::default(),
tu_index_entries: Default::default(),
contained_units: Default::default(),
}
}
pub(crate) fn contained_units(&self) -> &HashSet<DwarfObject> {
&self.contained_units
}
#[tracing::instrument(level = "trace", skip(sess, input,))]
pub(crate) fn add_input_object<'input, 'session: 'input>(
&mut self,
sess: &'session impl Session<RelocationMap>,
input: &object::File<'input>,
encoding: Encoding,
) -> Result<()> {
let cu_index = maybe_load_index_section::<_, gimli::DebugCuIndex<_>, _, _>(
sess,
encoding,
self.endian,
input,
)?;
let tu_index = maybe_load_index_section::<_, gimli::DebugTuIndex<_>, _, _>(
sess,
encoding,
self.endian,
input,
)?;
let mut debug_abbrev = None;
let mut debug_line = None;
let mut debug_loc = None;
let mut debug_loclists = None;
let mut debug_macinfo = None;
let mut debug_macro = None;
let mut debug_rnglists = None;
let mut debug_str_offsets = None;
macro_rules! update {
($target:ident += $source:expr) => {
if let Some(other) = $source {
let contribution = $target.get_or_insert(Contribution { size: 0, ..other });
contribution.size += other.size;
}
debug!(?$target);
};
}
for section in input.sections() {
match section.name() {
Ok(".debug_abbrev.dwo" | ".zdebug_abbrev.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_abbrev += self.obj.append_to_debug_abbrev(&data));
}
Ok(".debug_line.dwo" | ".zdebug_line.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_line += self.obj.append_to_debug_line(&data));
}
Ok(".debug_loc.dwo" | ".zdebug_loc.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_loc += self.obj.append_to_debug_loc(&data));
}
Ok(".debug_loclists.dwo" | ".zdebug_loclists.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_loclists += self.obj.append_to_debug_loclists(&data));
}
Ok(".debug_macinfo.dwo" | ".zdebug_macinfo.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_macinfo += self.obj.append_to_debug_macinfo(&data));
}
Ok(".debug_macro.dwo" | ".zdebug_macro.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_macro += self.obj.append_to_debug_macro(&data));
}
Ok(".debug_rnglists.dwo" | ".zdebug_rnglists.dwo") => {
let data = section.compressed_data()?.decompress()?;
update!(debug_rnglists += self.obj.append_to_debug_rnglists(&data));
}
Ok(".debug_str_offsets.dwo" | ".zdebug_str_offsets.dwo") => {
let (debug_str_offsets_section, debug_str_offsets_section_len) = {
let data = section.compressed_data()?.decompress()?;
let len = data.len() as u64;
let data_ref = sess.alloc_owned_cow(data);
(
gimli::DebugStrOffsets::from(gimli::EndianSlice::new(
data_ref,
self.endian,
)),
len,
)
};
let debug_str_section =
if let Some(section) = input.section_by_name(".debug_str.dwo") {
let data = section.compressed_data()?.decompress()?;
let data_ref = sess.alloc_owned_cow(data);
gimli::DebugStr::new(data_ref, self.endian)
} else {
return Err(Error::MissingRequiredSection(".debug_str.dwo"));
};
let data = self.string_table.remap_str_offsets_section(
debug_str_section,
debug_str_offsets_section,
debug_str_offsets_section_len,
self.endian,
encoding,
)?;
update!(
debug_str_offsets += self.obj.append_to_debug_str_offsets(data.slice())
);
}
_ => (),
}
}
let debug_abbrev_section = if let Some(section) = input.section_by_name(".debug_abbrev.dwo")
{
let data = section.compressed_data()?.decompress()?;
let data_ref = sess.alloc_owned_cow(data);
gimli::DebugAbbrev::new(data_ref, self.endian)
} else {
return Err(Error::MissingRequiredSection(".debug_abbrev.dwo"));
};
let adjustor_for_index =
|id| create_contribution_adjustor(cu_index.as_ref(), tu_index.as_ref(), id);
let mut abbrev_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugAbbrev);
let mut line_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugLine);
let mut loc_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugLoc);
let mut loclists_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugLocLists);
let mut rnglists_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugRngLists);
let mut str_offsets_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugStrOffsets);
let mut macinfo_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugMacinfo);
let mut macro_adjustor = adjustor_for_index(gimli::IndexSectionId::DebugMacro);
let mut seen_debug_info = false;
let mut seen_debug_types = false;
for section in input.sections() {
let data;
let mut iter = match section.name() {
Ok(".debug_info.dwo" | ".zdebug_info.dwo")
if seen_debug_info && cu_index.is_some() =>
{
return Err(Error::MultipleDebugInfoSection);
}
Ok(".debug_info.dwo" | ".zdebug_info.dwo") => {
data = section.compressed_data()?.decompress()?;
seen_debug_info = true;
UnitHeaderIterator::DebugInfo(
gimli::DebugInfo::new(&data, self.endian).units(),
)
}
Ok(".debug_types.dwo" | ".zdebug_types.dwo")
if seen_debug_types && tu_index.is_some() =>
{
return Err(Error::MultipleDebugTypesSection);
}
Ok(".debug_types.dwo" | ".zdebug_types.dwo") => {
data = section.compressed_data()?.decompress()?;
seen_debug_types = true;
UnitHeaderIterator::DebugTypes(
gimli::DebugTypes::new(&data, self.endian).units(),
)
}
_ => continue,
};
while let Some(header) = iter.next().map_err(Error::ParseUnitHeader)? {
let id = match dwo_identifier_of_unit(&debug_abbrev_section, &header)? {
None => {
return Err(Error::NotSplitUnit);
}
Some(id @ DwarfObject::Compilation(dwo_id))
if self.contained_units.contains(&id) =>
{
return Err(Error::DuplicateUnit(dwo_id.0));
}
Some(id @ DwarfObject::Type(type_sig))
if self.contained_units.contains(&id) =>
{
debug!(?type_sig, "skipping duplicate type unit, already seen");
continue;
}
Some(id) => id,
};
let size: u64 = header
.length_including_self()
.try_into()
.expect("unit header length larger than u64");
let offset = match header.offset() {
UnitSectionOffset::DebugInfoOffset(offset) => offset.0,
UnitSectionOffset::DebugTypesOffset(offset) => offset.0,
};
let data = section
.compressed_data_range(
sess,
offset.try_into().expect("offset larger than u64"),
size,
)
.map_err(Error::DecompressData)?
.ok_or(Error::EmptyUnit(id.index()))?;
let (debug_info, debug_types) = match (&iter, id) {
(UnitHeaderIterator::DebugTypes(_), DwarfObject::Type(_)) => {
(None, self.obj.append_to_debug_types(data))
}
(_, DwarfObject::Compilation(_) | DwarfObject::Type(_)) => {
(self.obj.append_to_debug_info(data), None)
}
};
let debug_abbrev = abbrev_adjustor(id, debug_abbrev)?;
let debug_line = line_adjustor(id, debug_line)?;
let debug_loc = loc_adjustor(id, debug_loc)?;
let debug_loclists = loclists_adjustor(id, debug_loclists)?;
let debug_rnglists = rnglists_adjustor(id, debug_rnglists)?;
let debug_str_offsets = str_offsets_adjustor(id, debug_str_offsets)?;
let debug_macinfo = macinfo_adjustor(id, debug_macinfo)?;
let debug_macro = macro_adjustor(id, debug_macro)?;
let entry = IndexEntry {
encoding,
id,
debug_info,
debug_types,
debug_abbrev,
debug_line,
debug_loc,
debug_loclists,
debug_rnglists,
debug_str_offsets,
debug_macinfo,
debug_macro,
};
debug!(?entry);
match id {
DwarfObject::Compilation(_) => self.cu_index_entries.push(entry),
DwarfObject::Type(_) => self.tu_index_entries.push(entry),
}
self.contained_units.insert(id);
}
}
if !seen_debug_info {
return Err(Error::MissingRequiredSection(".debug_info.dwo"));
}
Ok(())
}
pub(crate) fn finish(self) -> Result<WritableObject<'file>> {
let Self { mut obj, string_table, cu_index_entries, tu_index_entries, .. } = self;
let _ = obj.append_to_debug_str(&string_table.finish());
debug!("writing cu index");
let cu_index_data = write_index(self.endian, &cu_index_entries)?;
let _ = obj.append_to_debug_cu_index(cu_index_data.slice());
debug!("writing tu index");
let tu_index_data = write_index(self.endian, &tu_index_entries)?;
let _ = obj.append_to_debug_tu_index(tu_index_data.slice());
Ok(obj.finish())
}
}
impl<'file> fmt::Debug for InProgressDwarfPackage<'file> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InProgressDwarfPackage")
}
}