use std::borrow::Cow;
use super::input::{DebugObject, Malformed, Reader, Section};
use super::names::{NameEntry, gdb_hash};
use super::unit::{self, AbbrevTable, Abbrevs, DW_AT_NAME, UnitHeader, UnitInfo, Value};
const DW_TAG_CLASS_TYPE: u64 = 0x02;
const DW_TAG_ENUMERATION_TYPE: u64 = 0x04;
const DW_TAG_LEXICAL_BLOCK: u64 = 0x0b;
const DW_TAG_STRUCTURE_TYPE: u64 = 0x13;
const DW_TAG_TYPEDEF: u64 = 0x16;
const DW_TAG_UNION_TYPE: u64 = 0x17;
const DW_TAG_SUBRANGE_TYPE: u64 = 0x21;
const DW_TAG_BASE_TYPE: u64 = 0x24;
const DW_TAG_ENUMERATOR: u64 = 0x28;
const DW_TAG_SUBPROGRAM: u64 = 0x2e;
const DW_TAG_VARIABLE: u64 = 0x34;
const DW_TAG_NAMESPACE: u64 = 0x39;
const DW_TAG_TEMPLATE_ALIAS: u64 = 0x4309;
const OTHER_TYPES: [u64; 17] = [
0x01, 0x0f, 0x10, 0x12, 0x15, 0x1f, 0x20, 0x26, 0x2a, 0x35, 0x37, 0x3b, 0x42, 0x44, 0x47, 0x4b, 0x4101, ];
const DW_AT_LOCATION: u64 = 0x02;
const DW_AT_ABSTRACT_ORIGIN: u64 = 0x31;
const DW_AT_DECLARATION: u64 = 0x3c;
const DW_AT_EXTERNAL: u64 = 0x3f;
const DW_AT_SPECIFICATION: u64 = 0x47;
const DW_AT_SIGNATURE: u64 = 0x69;
const DW_OP_ADDR: u8 = 0x03;
const DW_OP_CONST4U: u8 = 0x0c;
const DW_OP_CONST8U: u8 = 0x0e;
const DW_OP_ADDRX: u8 = 0xa1;
const DW_OP_GNU_ADDR_INDEX: u8 = 0xfb;
const KIND_TYPE: u32 = 1;
const KIND_VARIABLE: u32 = 2;
const KIND_FUNCTION: u32 = 3;
const TYPE_UNIT_BITS: u32 = KIND_TYPE << 4;
const MAX_DEPTH: usize = 256;
const NONE: u32 = u32::MAX;
#[derive(Clone, Copy)]
struct Die<'a> {
offset: usize,
parent: u32,
tag: u64,
name: Option<Value<'a>>,
spec: Option<usize>,
signature: Option<u64>,
abstract_origin: bool,
declaration: bool,
external: bool,
static_location: bool,
}
struct Dies<'a> {
list: Vec<Die<'a>>,
signatures: Vec<u64>,
}
#[derive(Default)]
struct TypeUnit<'a> {
name: Option<&'a [u8]>,
names: Vec<(bool, Cow<'a, [u8]>)>,
refs: Vec<u64>,
}
#[derive(Default)]
pub(crate) struct TypeUnits<'a> {
units: hashbrown::HashMap<u64, TypeUnit<'a>, foldhash::fast::FixedState>,
}
impl<'a> TypeUnits<'a> {
pub(crate) fn read<F: crate::elf::read::ElfFormat>(
obj: &DebugObject<'_, 'a, F>,
abbrevs: &mut Abbrevs,
problems: &mut Vec<(u32, Malformed)>,
) -> Self {
let mut this = Self::default();
let mut read: Vec<(UnitHeader, &Section<'a, F>, Dies<'a>, UnitInfo)> = Vec::new();
for (section, types) in &obj.type_units {
let (headers, error) = unit::unit_headers(obj, section, *types);
if let Some(error) = error {
problems.push((section.index, error));
}
for header in headers.into_iter().filter(UnitHeader::is_type_unit) {
let result = abbrevs.get(obj, &header).and_then(|table| {
let Some(info) = unit::unit_info(obj, section, &header, table)? else {
return Ok(None);
};
let dies = if info.children {
read_dies(obj, section, &header, table, info.children_at)?
} else {
Dies {
list: Vec::new(),
signatures: Vec::new(),
}
};
Ok(Some((dies, info)))
});
match result {
Ok(Some((dies, info))) => read.push((header, section, dies, info)),
Ok(None) => {}
Err(e) => problems.push((section.index, e)),
}
}
}
let empty = Self::default();
let mut type_names = Vec::with_capacity(read.len());
for (header, _, dies, info) in &read {
let scan = Scan {
obj,
unit: header,
bases: info.bases,
dies: &dies.list,
cplusplus: info.language.is_some_and(is_cplusplus),
type_units: &empty,
};
let type_die = u64::try_from(header.offset)
.ok()
.and_then(|o| o.checked_add(header.type_offset))
.and_then(|o| usize::try_from(o).ok())
.and_then(|o| scan.at_offset(o))
.and_then(|i| scan.die(i));
type_names.push((header.signature, type_die.and_then(|d| scan.string(d.name))));
}
for (signature, name) in type_names {
this.units.insert(
signature,
TypeUnit {
name,
..TypeUnit::default()
},
);
}
let mut lists = Vec::with_capacity(read.len());
for (header, _, dies, info) in &read {
let scan = Scan {
obj,
unit: header,
bases: info.bases,
dies: &dies.list,
cplusplus: info.language.is_some_and(is_cplusplus),
type_units: &this,
};
let mut names = Vec::new();
for (i, d) in dies.list.iter().enumerate() {
let i = u32::try_from(i).unwrap_or(NONE);
if let Some((name, _)) = scan.global_name(i, d) {
names.push((false, name));
}
if let Some((name, _)) = scan.global_type(i, d) {
names.push((true, name));
}
}
lists.push((header.signature, names, dies.signatures.clone()));
}
for (signature, names, refs) in lists {
if let Some(unit) = this.units.get_mut(&signature) {
unit.names = names;
unit.refs = refs;
}
}
this
}
fn name(&self, signature: u64) -> Option<&'a [u8]> {
self.units.get(&signature).and_then(|u| u.name)
}
fn closure(&self, signatures: &[u64]) -> Vec<&TypeUnit<'a>> {
let mut seen: hashbrown::HashSet<u64, foldhash::fast::FixedState> =
hashbrown::HashSet::with_hasher(foldhash::fast::FixedState::default());
let mut queue: Vec<u64> = signatures.to_vec();
let mut out = Vec::new();
let mut next = 0usize;
while let Some(&signature) = queue.get(next) {
next = next.saturating_add(1);
if !seen.insert(signature) {
continue;
}
if let Some(unit) = self.units.get(&signature) {
queue.extend_from_slice(&unit.refs);
out.push(unit);
}
}
out
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn names<'a, F: crate::elf::read::ElfFormat>(
obj: &DebugObject<'_, 'a, F>,
info: &Section<'a, F>,
unit: &UnitHeader,
abbrevs: &AbbrevTable,
die: &UnitInfo,
index: u32,
type_units: &TypeUnits<'a>,
out: &mut Vec<NameEntry<'a>>,
) -> Result<(), Malformed> {
if !die.children {
return Ok(());
}
let dies = read_dies(obj, info, unit, abbrevs, die.children_at)?;
let scan = Scan {
obj,
unit,
bases: die.bases,
dies: &dies.list,
cplusplus: die.language.is_some_and(is_cplusplus),
type_units,
};
let mut names: Vec<Listed<'a>> = Vec::new();
let mut types: Vec<Listed<'a>> = Vec::new();
for (i, d) in dies.list.iter().enumerate() {
let i = u32::try_from(i).unwrap_or(NONE);
if let Some((name, bits)) = scan.global_name(i, d) {
names.push((name, bits, d.offset));
}
if let Some((name, bits)) = scan.global_type(i, d) {
types.push((name, bits, d.offset));
}
}
let mut names = dedup(names);
let mut types = dedup(types);
let mut from_type_units: [Vec<(Cow<'a, [u8]>, u32)>; 2] = [Vec::new(), Vec::new()];
for tu in type_units.closure(&dies.signatures) {
for (is_type, name) in &tu.names {
let (own, extra) = if *is_type {
(&types, &mut from_type_units[1])
} else {
(&names, &mut from_type_units[0])
};
if !own.iter().any(|(n, _)| n == name) && !extra.iter().any(|(n, _)| n == name) {
extra.push((name.clone(), TYPE_UNIT_BITS));
}
}
}
let [tu_names, tu_types] = from_type_units;
names.splice(0..0, tu_names);
types.splice(0..0, tu_types);
for (name, bits) in names.into_iter().chain(types) {
let hash = gdb_hash(&name);
out.push(NameEntry {
name,
hash,
value: (bits << 24) | (index & 0x00ff_ffff),
});
}
Ok(())
}
type Listed<'a> = (Cow<'a, [u8]>, u32, usize);
fn dedup<'a>(list: Vec<Listed<'a>>) -> Vec<(Cow<'a, [u8]>, u32)> {
let mut seen: hashbrown::HashMap<Cow<'a, [u8]>, usize, foldhash::fast::FixedState> =
hashbrown::HashMap::with_hasher(foldhash::fast::FixedState::default());
let mut keep: Vec<Option<Listed<'a>>> = Vec::with_capacity(list.len());
for entry in list {
if let Some(&at) = seen.get(&entry.0)
&& let Some(slot) = keep.get_mut(at)
{
*slot = None;
}
seen.insert(entry.0.clone(), keep.len());
keep.push(Some(entry));
}
let mut kept: Vec<Listed<'a>> = keep.into_iter().flatten().collect();
kept.sort_by_key(|&(_, _, offset)| offset);
kept.into_iter().map(|(n, b, _)| (n, b)).collect()
}
fn is_cplusplus(language: u64) -> bool {
matches!(language, 0x04 | 0x19 | 0x1a | 0x21 | 0x2a | 0x2b)
}
#[derive(Clone, Copy)]
enum Step {
Skip(u64),
Skip1(u64),
Read(u64, u64, i64),
Signature,
}
struct Plan {
tag: u64,
children: bool,
steps: Vec<Step>,
abstract_origin: bool,
declaration: bool,
external: bool,
}
fn plan(abbrev: &unit::Abbrev, unit: &UnitHeader) -> Plan {
let mut plan = Plan {
tag: abbrev.tag,
children: abbrev.children,
steps: Vec::with_capacity(abbrev.attrs.len()),
abstract_origin: false,
declaration: false,
external: false,
};
let mut run = 0u64;
let flush = |steps: &mut Vec<Step>, run: &mut u64| {
if *run > 0 {
steps.push(Step::Skip(*run));
*run = 0;
}
};
for &(at, form, implicit) in &abbrev.attrs {
let wanted = matches!(
at,
DW_AT_NAME | DW_AT_SPECIFICATION | DW_AT_LOCATION | DW_AT_SIGNATURE
);
if at == DW_AT_ABSTRACT_ORIGIN {
plan.abstract_origin = true;
}
if matches!(at, DW_AT_DECLARATION | DW_AT_EXTERNAL) {
if form == unit::DW_FORM_FLAG_PRESENT {
if at == DW_AT_DECLARATION {
plan.declaration = true;
} else {
plan.external = true;
}
continue;
}
flush(&mut plan.steps, &mut run);
plan.steps.push(Step::Read(at, form, implicit));
continue;
}
if wanted {
flush(&mut plan.steps, &mut run);
plan.steps.push(Step::Read(at, form, implicit));
} else if form == unit::DW_FORM_REF_SIG8 {
flush(&mut plan.steps, &mut run);
plan.steps.push(Step::Signature);
} else if let Some(size) = unit::fixed_size(form, unit) {
run = run.saturating_add(size);
} else {
flush(&mut plan.steps, &mut run);
plan.steps.push(Step::Skip1(form));
}
}
flush(&mut plan.steps, &mut run);
plan
}
fn read_dies<'a, F: crate::elf::read::ElfFormat>(
obj: &DebugObject<'_, 'a, F>,
info: &Section<'a, F>,
unit: &UnitHeader,
abbrevs: &AbbrevTable,
start: usize,
) -> Result<Dies<'a>, Malformed> {
let data = info.data.get(..unit.end).unwrap_or_default();
let mut r = Reader::at(data, start);
let mut dies: Vec<Die<'a>> = Vec::new();
let mut signatures = Vec::new();
let mut plans: Vec<Option<Plan>> = Vec::new();
let mut stack: Vec<u32> = vec![NONE];
while !r.is_empty() {
let offset = r.pos();
let code = r.uleb()?;
if code == 0 {
stack.pop();
if stack.is_empty() {
break;
}
continue;
}
let slot = usize::try_from(code).ok().filter(|&c| c < 1 << 16);
let compiled;
let plan = match slot {
Some(slot) => {
if plans.len() <= slot {
plans.resize_with(slot.saturating_add(1), || None);
}
let entry = plans
.get_mut(slot)
.ok_or_else(|| r.error("abbreviation code"))?;
if entry.is_none() {
let abbrev = abbrevs
.get(code)
.ok_or_else(|| r.error("abbreviation code (not found)"))?;
*entry = Some(plan(abbrev, unit));
}
entry.as_ref().ok_or_else(|| r.error("abbreviation code"))?
}
None => {
let abbrev = abbrevs
.get(code)
.ok_or_else(|| r.error("abbreviation code (not found)"))?;
compiled = plan(abbrev, unit);
&compiled
}
};
let mut die = Die {
offset,
parent: stack.last().copied().unwrap_or(NONE),
tag: plan.tag,
name: None,
spec: None,
signature: None,
abstract_origin: plan.abstract_origin,
declaration: plan.declaration,
external: plan.external,
static_location: false,
};
for &step in &plan.steps {
match step {
Step::Skip(n) => r.skip(n)?,
Step::Skip1(form) => unit::skip_value(&mut r, form, unit)?,
Step::Signature => signatures.push(r.uint(8)?),
Step::Read(at, form, implicit) => {
let value = unit::read_value(obj, info, &mut r, form, implicit, unit)?;
match (at, value) {
(DW_AT_NAME, _) => die.name = Some(value),
(DW_AT_SPECIFICATION, Value::Ref(o)) => {
die.spec = usize::try_from(o)
.ok()
.and_then(|o| unit.offset.checked_add(o));
}
(DW_AT_SPECIFICATION, Value::RefAddr(o)) => {
die.spec = usize::try_from(o).ok();
}
(DW_AT_SIGNATURE, Value::Signature(s)) => {
die.signature = Some(s);
signatures.push(s);
}
(DW_AT_LOCATION, Value::Block(block)) => {
die.static_location = matches!(
block.first(),
Some(
&(DW_OP_ADDR
| DW_OP_ADDRX
| DW_OP_GNU_ADDR_INDEX
| DW_OP_CONST4U
| DW_OP_CONST8U)
)
);
}
(DW_AT_DECLARATION, _) => {
die.declaration = value.unsigned().is_some_and(|v| v != 0);
}
(DW_AT_EXTERNAL, _) => {
die.external = value.unsigned().is_some_and(|v| v != 0);
}
_ => {}
}
}
}
}
let index = u32::try_from(dies.len()).map_err(|_| r.error("too many DIEs"))?;
let children = plan.children;
dies.push(die);
if children {
stack.push(index);
}
}
Ok(Dies {
list: dies,
signatures,
})
}
struct Scan<'s, 'o, 'a, F: crate::elf::read::ElfFormat = crate::elf::read::Elf64Le> {
obj: &'s DebugObject<'o, 'a, F>,
unit: &'s UnitHeader,
bases: unit::Bases,
dies: &'s [Die<'a>],
cplusplus: bool,
type_units: &'s TypeUnits<'a>,
}
impl<'a, F: crate::elf::read::ElfFormat> Scan<'_, '_, 'a, F> {
fn die(&self, index: u32) -> Option<&Die<'a>> {
self.dies.get(usize::try_from(index).ok()?)
}
fn at_offset(&self, offset: usize) -> Option<u32> {
let at = self.dies.binary_search_by_key(&offset, |d| d.offset).ok()?;
u32::try_from(at).ok()
}
fn string(&self, value: Option<Value<'a>>) -> Option<&'a [u8]> {
unit::string(self.obj, value?, self.unit, &self.bases)
}
fn spec(&self, die: &Die<'a>) -> Option<(u32, &Die<'a>)> {
let at = self.at_offset(die.spec?)?;
Some((at, self.die(at)?))
}
fn name_of(&self, die: &Die<'a>) -> Option<&'a [u8]> {
self.string(die.name)
.or_else(|| self.spec(die).and_then(|(_, s)| self.string(s.name)))
.or_else(|| self.type_units.name(die.signature?))
}
fn scope_of(&self, die: &Die<'a>) -> u32 {
match self.spec(die) {
Some((_, spec)) => spec.parent,
None => die.parent,
}
}
fn prefix(&self, mut scope: u32) -> Vec<u8> {
if !self.cplusplus {
return Vec::new();
}
let mut parts: Vec<&[u8]> = Vec::new();
for _ in 0..self.dies.len().min(MAX_DEPTH) {
let Some(die) = self.die(scope) else { break };
let name = match self.name_of(die) {
Some(name) if !name.is_empty() => Some(name),
_ if die.tag == DW_TAG_NAMESPACE => Some(&b"(anonymous namespace)"[..]),
_ => None,
};
if let Some(name) = name {
parts.push(name);
}
scope = self.scope_of(die);
}
let mut out = Vec::new();
for part in parts.iter().rev() {
out.extend_from_slice(part);
out.extend_from_slice(b"::");
}
out
}
fn qualified(&self, scope: u32, name: &'a [u8]) -> Cow<'a, [u8]> {
let mut prefix = self.prefix(scope);
if prefix.is_empty() {
return Cow::Borrowed(name);
}
prefix.extend_from_slice(name);
Cow::Owned(prefix)
}
fn at_namespace_scope(&self, index: u32) -> bool {
self.die(index).is_none_or(|d| d.tag == DW_TAG_NAMESPACE)
}
fn in_function(&self, die: &Die<'a>) -> bool {
let mut scope = die.parent;
for _ in 0..self.dies.len().min(MAX_DEPTH) {
let Some(parent) = self.die(scope) else {
return false;
};
match parent.tag {
DW_TAG_SUBPROGRAM | DW_TAG_LEXICAL_BLOCK => return true,
DW_TAG_NAMESPACE => scope = parent.parent,
_ => return false,
}
}
false
}
fn linkage(&self, die: &Die<'a>) -> u32 {
let external = match self.spec(die) {
Some((_, spec)) => spec.external,
None => die.external,
};
u32::from(!external)
}
fn global_name(&self, _index: u32, die: &Die<'a>) -> Option<(Cow<'a, [u8]>, u32)> {
let bits = |kind: u32, is_static: u32| (kind << 4) | (is_static << 7);
match die.tag {
DW_TAG_SUBPROGRAM => {
if die.declaration || die.abstract_origin {
return None;
}
let name = self.name_of(die).unwrap_or_default();
Some((
self.qualified(self.scope_of(die), name),
bits(KIND_FUNCTION, self.linkage(die)),
))
}
DW_TAG_VARIABLE => {
if die.declaration {
return None;
}
let global = if die.spec.is_some() {
true
} else if self.in_function(die) {
die.static_location
} else {
self.at_namespace_scope(die.parent)
};
if !global {
return None;
}
let name = self.name_of(die).unwrap_or_default();
Some((
self.qualified(self.scope_of(die), name),
bits(KIND_VARIABLE, self.linkage(die)),
))
}
DW_TAG_NAMESPACE => {
let name = match self.string(die.name) {
Some(name) if !name.is_empty() => name,
_ => b"(anonymous namespace)",
};
Some((self.qualified(die.parent, name), bits(KIND_TYPE, 0)))
}
DW_TAG_ENUMERATOR => {
let enumeration = self.die(die.parent)?;
if enumeration.tag != DW_TAG_ENUMERATION_TYPE
|| !self.at_namespace_scope(enumeration.parent)
{
return None;
}
let name = self.string(die.name)?;
Some((
self.qualified(enumeration.parent, name),
bits(KIND_VARIABLE, 1),
))
}
_ => None,
}
}
fn global_type(&self, _index: u32, die: &Die<'a>) -> Option<(Cow<'a, [u8]>, u32)> {
let bits = |kind: u32, is_static: u32| (kind << 4) | (is_static << 7);
let kind_bits = match die.tag {
DW_TAG_CLASS_TYPE
| DW_TAG_STRUCTURE_TYPE
| DW_TAG_UNION_TYPE
| DW_TAG_ENUMERATION_TYPE => bits(KIND_TYPE, u32::from(!self.cplusplus)),
DW_TAG_TYPEDEF | DW_TAG_BASE_TYPE | DW_TAG_SUBRANGE_TYPE | DW_TAG_TEMPLATE_ALIAS => {
bits(KIND_TYPE, 1)
}
tag if OTHER_TYPES.contains(&tag) => bits(0, 0),
_ => return None,
};
if !self.at_namespace_scope(die.parent) {
return None;
}
if let Some(signature) = die.signature {
let name = self.type_units.name(signature).unwrap_or_default();
return Some((self.qualified(die.parent, name), kind_bits));
}
if die.declaration {
return None;
}
let name = self.string(die.name).filter(|n| !n.is_empty())?;
if die.tag == DW_TAG_BASE_TYPE
&& (name == b"__ARRAY_SIZE_TYPE__" || name.starts_with(b"DW_ATE_"))
{
return None;
}
Some((self.qualified(die.parent, name), kind_bits))
}
}