use std::fmt;
use gimli::{
write::{EndianVec, Writer},
Encoding,
};
use tracing::{debug, trace};
use crate::{
error::{Error, Result},
ext::PackageFormatExt,
package::DwarfObject,
};
pub(crate) trait Bucketable {
fn index(&self) -> u64;
}
#[tracing::instrument(level = "trace", skip_all)]
fn bucket<B: Bucketable + fmt::Debug>(elements: &[B]) -> Vec<u32> {
let unit_count: u32 = elements.len().try_into().expect("unit count larger than u32");
let num_buckets = if elements.len() < 2 { 2 } else { (3 * unit_count / 2).next_power_of_two() };
let mask: u64 = num_buckets as u64 - 1;
trace!(?mask);
let mut buckets = vec![0u32; num_buckets as usize];
trace!(?buckets);
for (elem, i) in elements.iter().zip(0u32..) {
trace!(?i, ?elem);
let s = elem.index();
let mut h = s & mask;
let hp = ((s >> 32) & mask) | 1;
trace!(?s, ?h, ?hp);
while buckets[h as usize] > 0 {
assert!(elements[(buckets[h as usize] - 1) as usize].index() != elem.index());
h = (h + hp) & mask;
trace!(?h);
}
buckets[h as usize] = i + 1;
trace!(?buckets);
}
buckets
}
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub(crate) struct ContributionOffset(pub(crate) u64);
impl fmt::Debug for ContributionOffset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ContributionOffset({:#x})", self.0)
}
}
type ContributionSize = u64;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct Contribution {
pub(crate) offset: ContributionOffset,
pub(crate) size: ContributionSize,
}
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct IndexColumns {
pub(crate) debug_info: bool,
pub(crate) debug_types: bool,
pub(crate) debug_abbrev: bool,
pub(crate) debug_line: bool,
pub(crate) debug_loc: bool,
pub(crate) debug_loclists: bool,
pub(crate) debug_rnglists: bool,
pub(crate) debug_str_offsets: bool,
pub(crate) debug_macinfo: bool,
pub(crate) debug_macro: bool,
}
impl IndexColumns {
fn number_of_columns(&self) -> u32 {
self.debug_info as u32
+ self.debug_types as u32
+ self.debug_abbrev as u32
+ self.debug_line as u32
+ self.debug_loc as u32
+ self.debug_loclists as u32
+ self.debug_rnglists as u32
+ self.debug_str_offsets as u32
+ self.debug_macinfo as u32
+ self.debug_macro as u32
}
fn add_entry(&mut self, entry: &IndexEntry) {
self.debug_info |= entry.debug_info.is_some();
self.debug_types |= entry.debug_types.is_some();
self.debug_abbrev |= entry.debug_abbrev.is_some();
self.debug_line |= entry.debug_line.is_some();
self.debug_loc |= entry.debug_loc.is_some();
self.debug_loclists |= entry.debug_loclists.is_some();
self.debug_rnglists |= entry.debug_rnglists.is_some();
self.debug_str_offsets |= entry.debug_str_offsets.is_some();
self.debug_macinfo |= entry.debug_macinfo.is_some();
self.debug_macro |= entry.debug_macro.is_some();
}
fn write_header<Endian: gimli::Endianity>(
&self,
out: &mut EndianVec<Endian>,
encoding: Encoding,
) -> Result<()> {
if encoding.is_gnu_extension_dwarf_package_format() {
if self.debug_info {
out.write_u32(gimli::DW_SECT_V2_INFO.0)?;
}
if self.debug_types {
out.write_u32(gimli::DW_SECT_V2_TYPES.0)?;
}
if self.debug_abbrev {
out.write_u32(gimli::DW_SECT_V2_ABBREV.0)?;
}
if self.debug_line {
out.write_u32(gimli::DW_SECT_V2_LINE.0)?;
}
if self.debug_loc {
out.write_u32(gimli::DW_SECT_V2_LOC.0)?;
}
if self.debug_str_offsets {
out.write_u32(gimli::DW_SECT_V2_STR_OFFSETS.0)?;
}
if self.debug_macinfo {
out.write_u32(gimli::DW_SECT_V2_MACINFO.0)?;
}
if self.debug_macro {
out.write_u32(gimli::DW_SECT_V2_MACRO.0)?;
}
} else {
if self.debug_info {
out.write_u32(gimli::DW_SECT_INFO.0)?;
}
if self.debug_abbrev {
out.write_u32(gimli::DW_SECT_ABBREV.0)?;
}
if self.debug_line {
out.write_u32(gimli::DW_SECT_LINE.0)?;
}
if self.debug_loclists {
out.write_u32(gimli::DW_SECT_LOCLISTS.0)?;
}
if self.debug_rnglists {
out.write_u32(gimli::DW_SECT_RNGLISTS.0)?;
}
if self.debug_str_offsets {
out.write_u32(gimli::DW_SECT_STR_OFFSETS.0)?;
}
if self.debug_macro {
out.write_u32(gimli::DW_SECT_MACRO.0)?;
}
}
Ok(())
}
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct IndexEntry {
pub(crate) encoding: Encoding,
pub(crate) id: DwarfObject,
pub(crate) debug_info: Option<Contribution>,
pub(crate) debug_types: Option<Contribution>,
pub(crate) debug_abbrev: Option<Contribution>,
pub(crate) debug_line: Option<Contribution>,
pub(crate) debug_loc: Option<Contribution>,
pub(crate) debug_loclists: Option<Contribution>,
pub(crate) debug_rnglists: Option<Contribution>,
pub(crate) debug_str_offsets: Option<Contribution>,
pub(crate) debug_macinfo: Option<Contribution>,
pub(crate) debug_macro: Option<Contribution>,
}
impl IndexEntry {
#[tracing::instrument(level = "trace", skip(out, proj))]
fn write_contribution<Endian, Proj>(
&self,
out: &mut EndianVec<Endian>,
proj: Proj,
columns: &IndexColumns,
) -> Result<()>
where
Endian: gimli::Endianity,
Proj: Copy + Fn(Contribution) -> u32,
{
let proj = |contrib: Option<Contribution>| contrib.map(proj).unwrap_or(0);
if columns.debug_info {
out.write_u32(proj(self.debug_info))?;
}
if columns.debug_types {
out.write_u32(proj(self.debug_types))?;
}
if columns.debug_abbrev {
out.write_u32(proj(self.debug_abbrev))?;
}
if columns.debug_line {
out.write_u32(proj(self.debug_line))?;
}
if columns.debug_loc {
out.write_u32(proj(self.debug_loc))?;
}
if columns.debug_loclists {
out.write_u32(proj(self.debug_loclists))?;
}
if columns.debug_rnglists {
out.write_u32(proj(self.debug_rnglists))?;
}
if columns.debug_str_offsets {
out.write_u32(proj(self.debug_str_offsets))?;
}
if columns.debug_macinfo {
out.write_u32(proj(self.debug_macinfo))?;
}
if columns.debug_macro {
out.write_u32(proj(self.debug_macro))?;
}
Ok(())
}
}
impl Bucketable for IndexEntry {
fn index(&self) -> u64 {
self.id.index()
}
}
#[tracing::instrument(level = "trace")]
pub(crate) fn write_index<'output, Endian: gimli::Endianity>(
endianness: Endian,
entries: &[IndexEntry],
) -> Result<EndianVec<Endian>> {
let mut out = EndianVec::new(endianness);
if entries.len() == 0 {
return Ok(out);
}
let buckets = bucket(entries);
debug!(?buckets);
let encoding = entries[0].encoding;
if !entries.iter().all(|e| e.encoding == encoding) {
return Err(Error::MixedInputEncodings);
}
debug!(?encoding);
let mut columns = IndexColumns::default();
for entry in entries {
columns.add_entry(entry);
}
let num_columns = columns.number_of_columns();
debug!(?entries, ?columns, ?num_columns);
if encoding.is_gnu_extension_dwarf_package_format() {
out.write_u32(2)?;
} else {
out.write_u16(5)?;
out.write_u16(0)?;
}
out.write_u32(num_columns)?;
out.write_u32(entries.len().try_into().expect("number of units larger than u32"))?;
out.write_u32(buckets.len().try_into().expect("number of buckets larger than u32"))?;
for i in &buckets {
if *i > 0 {
out.write_u64(entries[(*i - 1) as usize].index())?;
} else {
out.write_u64(0)?;
}
}
for i in &buckets {
out.write_u32(*i)?;
}
columns.write_header(&mut out, encoding)?;
let write_offset = |contrib: Contribution| {
contrib.offset.0.try_into().expect("contribution offset larger than u32")
};
for entry in entries {
entry.write_contribution(&mut out, write_offset, &columns)?;
}
let write_size =
|contrib: Contribution| contrib.size.try_into().expect("contribution size larger than u32");
for entry in entries {
entry.write_contribution(&mut out, write_size, &columns)?;
}
Ok(out)
}