dwarf dump: Better support for GCC/ProDG & various fixes

- Displays subroutine "static" and "inline" if present
- Displays subroutine labels, blocks and inlines if present
- Displays struct member visibility if present
- Skips tags that can't be processed rather than bailing
This commit is contained in:
Luke Street 2023-11-27 16:35:12 -05:00
parent 5128ff67b2
commit 4cb6f4f85d
2 changed files with 433 additions and 19 deletions

View File

@ -178,11 +178,43 @@ where
let children = tag.children(&tags);
let mut typedefs = BTreeMap::<u32, Vec<u32>>::new();
for child in children {
let tag_type = process_root_tag(&tags, child)?;
let tag_type = match process_root_tag(&tags, child) {
Ok(tag_type) => tag_type,
Err(e) => {
log::error!(
"Failed to process tag {} (unit {}): {}",
child.key,
unit,
e
);
writeln!(
w,
"// ERROR: Failed to process tag {} ({:?})",
child.key, child.kind
)?;
continue;
}
};
if should_skip_tag(&tag_type) {
continue;
}
writeln!(w, "{}", tag_type_string(&tags, &typedefs, &tag_type)?)?;
match tag_type_string(&tags, &typedefs, &tag_type) {
Ok(s) => writeln!(w, "{}", s)?,
Err(e) => {
log::error!(
"Failed to emit tag {} (unit {}): {}",
child.key,
unit,
e
);
writeln!(
w,
"// ERROR: Failed to emit tag {} ({:?})",
child.key, child.kind
)?;
continue;
}
}
if let TagKind::Typedef = child.kind {
// TODO fundamental typedefs?

View File

@ -1,4 +1,5 @@
use std::{
cmp::max,
collections::BTreeMap,
convert::TryFrom,
fmt::{Display, Formatter, Write},
@ -567,7 +568,7 @@ pub enum StructureKind {
pub struct StructureType {
pub kind: StructureKind,
pub name: Option<String>,
pub byte_size: u32,
pub byte_size: Option<u32>,
pub members: Vec<StructureMember>,
pub bases: Vec<StructureBase>,
}
@ -581,7 +582,7 @@ pub enum Visibility {
#[derive(Debug, Clone)]
pub struct StructureBase {
pub name: String,
pub name: Option<String>,
pub base_type: Type,
pub offset: u32,
pub visibility: Visibility,
@ -622,6 +623,28 @@ pub struct SubroutineVariable {
pub location: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SubroutineLabel {
pub name: String,
pub address: u32,
}
#[derive(Debug, Clone)]
pub struct SubroutineBlock {
pub name: Option<String>,
pub start_address: u32,
pub end_address: u32,
pub variables: Vec<SubroutineVariable>,
pub blocks: Vec<SubroutineBlock>,
}
#[derive(Debug, Clone)]
pub struct SubroutineInline {
pub specification: u32,
pub start_address: u32,
pub end_address: u32,
}
#[derive(Debug, Clone)]
pub struct SubroutineType {
pub name: Option<String>,
@ -633,6 +656,11 @@ pub struct SubroutineType {
pub references: Vec<u32>,
pub member_of: Option<u32>,
pub variables: Vec<SubroutineVariable>,
pub inline: bool,
pub local: bool,
pub labels: Vec<SubroutineLabel>,
pub blocks: Vec<SubroutineBlock>,
pub inlines: Vec<SubroutineInline>,
}
#[derive(Debug, Clone)]
@ -674,6 +702,16 @@ pub enum TagType {
}
impl UserDefinedType {
pub fn name(&self) -> Option<String> {
match self {
UserDefinedType::Array(_) | UserDefinedType::PtrToMember(_) => None,
UserDefinedType::Structure(t) => t.name.clone(),
UserDefinedType::Enumeration(t) => t.name.clone(),
UserDefinedType::Union(t) => t.name.clone(),
UserDefinedType::Subroutine(t) => t.name.clone(),
}
}
pub fn is_definition(&self) -> bool {
match self {
UserDefinedType::Array(_) | UserDefinedType::PtrToMember(_) => false,
@ -693,7 +731,20 @@ impl UserDefinedType {
}
size
}
UserDefinedType::Structure(t) => t.byte_size,
UserDefinedType::Structure(t) => match t.byte_size {
Some(byte_size) => byte_size,
None => {
let mut max_end = 0;
for member in &t.members {
let size = match member.byte_size {
Some(byte_size) => byte_size,
None => member.kind.size(tags)?,
};
max_end = max(max_end, member.offset + size);
}
max_end
}
},
UserDefinedType::Enumeration(t) => t.byte_size,
UserDefinedType::Union(t) => t.byte_size,
UserDefinedType::Subroutine(_) => 0,
@ -826,6 +877,27 @@ pub fn type_string(
apply_modifiers(str, &t.modifiers)
}
fn type_name(tags: &TagMap, typedefs: &TypedefMap, t: &Type) -> Result<String> {
Ok(match t.kind {
TypeKind::Fundamental(ft) => ft.name()?.to_string(),
TypeKind::UserDefined(key) => {
if let Some(&td_key) = typedefs.get(&key).and_then(|v| v.first()) {
tags.get(&td_key)
.ok_or_else(|| anyhow!("Failed to locate typedef {}", key))?
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("typedef without name"))?
.clone()
} else {
let tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate user defined type {}", key))?;
let udt = ud_type(tags, tag)?;
udt.name().ok_or_else(|| anyhow!("User defined type without name"))?
}
}
})
}
fn array_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
@ -1037,7 +1109,11 @@ pub fn subroutine_def_string(
t: &SubroutineType,
) -> Result<String> {
let rt = type_string(tags, typedefs, &t.return_type, true)?;
let mut out = rt.prefix;
let mut out = if t.local { "static ".to_string() } else { String::new() };
if t.inline {
out.push_str("inline ");
}
out.push_str(&rt.prefix);
out.push(' ');
let mut name_written = false;
@ -1089,7 +1165,7 @@ pub fn subroutine_def_string(
}
}
if t.var_args {
write!(out, ", ...")?;
write!(parameters, ", ...")?;
}
}
write!(out, "({}){} {{", parameters, rt.suffix)?;
@ -1129,6 +1205,74 @@ pub fn subroutine_def_string(
}
}
if !t.labels.is_empty() {
writeln!(out, "\n // Labels")?;
for label in &t.labels {
writeln!(out, " {}: // {:#X}", label.name, label.address)?;
}
}
if !t.blocks.is_empty() {
writeln!(out, "\n // Blocks")?;
for block in &t.blocks {
let block_str = subroutine_block_string(tags, typedefs, block)?;
out.push_str(&indent_all_by(4, block_str));
}
}
if !t.inlines.is_empty() {
writeln!(out, "\n // Inlines")?;
for inline in &t.inlines {
let spec_tag = tags
.get(&inline.specification)
.ok_or_else(|| anyhow!("Failed to locate inline tag {}", inline.specification))?;
let subroutine = process_subroutine_tag(tags, spec_tag)?;
writeln!(
out,
" // -> {} ({:#X} - {:#X})",
subroutine_type_string(tags, typedefs, &subroutine)?,
inline.start_address,
inline.end_address,
)?;
}
}
writeln!(out, "}}")?;
Ok(out)
}
fn subroutine_block_string(
tags: &TagMap,
typedefs: &TypedefMap,
block: &SubroutineBlock,
) -> Result<String> {
let mut out = String::new();
if let Some(name) = &block.name {
write!(out, "{}: ", name)?;
} else {
out.push_str("/* anonymous block */ ");
}
writeln!(out, "{{\n // Range: {:#X} -> {:#X}", block.start_address, block.end_address)?;
let mut var_out = String::new();
for variable in &block.variables {
let ts = type_string(tags, typedefs, &variable.kind, true)?;
write!(
var_out,
"{} {}{};",
ts.prefix,
variable.name.as_deref().unwrap_or_default(),
ts.suffix
)?;
if let Some(location) = &variable.location {
write!(var_out, " // {}", location)?;
}
writeln!(var_out)?;
}
write!(out, "{}", indent_all_by(4, var_out))?;
for block in &block.blocks {
let block_str = subroutine_block_string(tags, typedefs, block)?;
out.push_str(&indent_all_by(4, block_str));
}
writeln!(out, "}}")?;
Ok(out)
}
@ -1161,11 +1305,30 @@ pub fn struct_def_string(
if base.virtual_base {
out.push_str("virtual ");
}
out.push_str(&base.name);
if let Some(name) = &base.name {
out.push_str(name);
} else {
out.push_str(&type_name(tags, typedefs, &base.base_type)?);
}
}
writeln!(out, " {{\n // total size: {:#X}", t.byte_size)?;
let mut var_out = String::new();
out.push_str(" {\n");
if let Some(byte_size) = t.byte_size {
writeln!(out, " // total size: {:#X}", byte_size)?;
}
let mut vis = match t.kind {
StructureKind::Struct => Visibility::Public,
StructureKind::Class => Visibility::Private,
};
for member in &t.members {
if vis != member.visibility {
vis = member.visibility;
match member.visibility {
Visibility::Private => out.push_str("private:\n"),
Visibility::Protected => out.push_str("protected:\n"),
Visibility::Public => out.push_str("public:\n"),
}
}
let mut var_out = String::new();
let ts = type_string(tags, typedefs, &member.kind, true)?;
write!(var_out, "{} {}{}", ts.prefix, member.name, ts.suffix)?;
if let Some(bit) = &member.bit {
@ -1173,9 +1336,9 @@ pub fn struct_def_string(
}
let size = if let Some(size) = member.byte_size { size } else { member.kind.size(tags)? };
writeln!(var_out, "; // offset {:#X}, size {:#X}", member.offset, size)?;
out.push_str(&indent_all_by(4, var_out));
}
write!(out, "{}", indent_all_by(4, var_out))?;
write!(out, "}}")?;
out.push('}');
Ok(out)
}
@ -1309,7 +1472,6 @@ fn process_inheritance_tag(tags: &TagMap, tag: &Tag) -> Result<StructureBase> {
bail!("Unhandled Inheritance child {:?}", child.kind);
}
let name = name.ok_or_else(|| anyhow!("Inheritance without name: {:?}", tag))?;
let base_type = base_type.ok_or_else(|| anyhow!("Inheritance without base type: {:?}", tag))?;
let offset = offset.ok_or_else(|| anyhow!("Inheritance without offset: {:?}", tag))?;
let visibility =
@ -1347,6 +1509,9 @@ fn process_structure_member_tag(tags: &TagMap, tag: &Tag) -> Result<StructureMem
(AttributeKind::Private, _) => visibility = Some(Visibility::Private),
(AttributeKind::Protected, _) => visibility = Some(Visibility::Protected),
(AttributeKind::Public, _) => visibility = Some(Visibility::Public),
(AttributeKind::Member, &AttributeValue::Reference(_key)) => {
// Pointer to parent structure, ignore
}
_ => {
bail!("Unhandled Member attribute {:?}", attr);
}
@ -1390,6 +1555,9 @@ fn process_structure_tag(tags: &TagMap, tag: &Tag) -> Result<StructureType> {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value),
(AttributeKind::Member, &AttributeValue::Reference(_key)) => {
// Pointer to parent structure, ignore
}
_ => {
bail!("Unhandled structure attribute {:?}", attr);
}
@ -1406,12 +1574,22 @@ fn process_structure_tag(tags: &TagMap, tag: &Tag) -> Result<StructureType> {
// TODO?
// info!("Structure {:?} Typedef: {:?}", name, child);
}
TagKind::Subroutine | TagKind::GlobalSubroutine => {
// TODO
}
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType
| TagKind::PtrToMemberType => {
// Variable type, ignore
}
kind => bail!("Unhandled StructureType child {:?}", kind),
}
}
let byte_size =
byte_size.ok_or_else(|| anyhow!("StructureType without ByteSize: {:?}", tag))?;
Ok(StructureType {
kind: if tag.kind == TagKind::ClassType {
StructureKind::Class
@ -1477,6 +1655,20 @@ fn process_array_subscript_data(data: &[u8]) -> Result<(Type, Vec<ArrayDimension
size: NonZeroU32::new(high_bound.wrapping_add(1)),
});
}
SubscriptFormat::FundTypeConstLocation => {
let index_type = FundType::try_from(u16::from_be_bytes(*array_ref!(data, 0, 2)))
.context("Invalid fundamental type ID")?;
let low_bound = u32::from_be_bytes(*array_ref!(data, 2, 4));
ensure!(low_bound == 0, "Invalid array low bound {low_bound}, expected 0");
let size = u16::from_be_bytes(*array_ref!(data, 6, 2));
let (block, remain) = data[8..].split_at(size as usize);
let location = if block.is_empty() { 0 } else { process_offset(block)? };
data = remain;
dimensions.push(ArrayDimension {
index_type: Type { kind: TypeKind::Fundamental(index_type), modifiers: vec![] },
size: NonZeroU32::new(location),
});
}
SubscriptFormat::ElementType => {
let mut cursor = Cursor::new(data);
let type_attr = read_attribute(&mut cursor)?;
@ -1529,21 +1721,33 @@ fn process_union_tag(tags: &TagMap, tag: &Tag) -> Result<UnionType> {
let mut name = None;
let mut byte_size = None;
let mut members = Vec::new();
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value),
(AttributeKind::Member, &AttributeValue::Reference(_key)) => {
// Pointer to parent structure, ignore
}
_ => {
bail!("Unhandled UnionType attribute {:?}", attr);
}
}
}
let mut members = Vec::new();
for child in tag.children(tags) {
match child.kind {
TagKind::Member => members.push(process_structure_member_tag(tags, child)?),
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType
| TagKind::PtrToMemberType => {
// Variable type, ignore
}
kind => bail!("Unhandled UnionType child {:?}", kind),
}
}
@ -1570,6 +1774,7 @@ fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
let mut var_args = false;
let mut references = Vec::new();
let mut member_of = None;
let mut inline = false;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
@ -1596,6 +1801,29 @@ fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
(AttributeKind::Member, &AttributeValue::Reference(key)) => {
member_of = Some(key);
}
(AttributeKind::MwPrologueEnd, &AttributeValue::Address(_addr)) => {
// Prologue end
}
(AttributeKind::MwEpilogueStart, &AttributeValue::Address(_addr)) => {
// Epilogue start
}
(AttributeKind::Inline, _) => inline = true,
(AttributeKind::Specification, &AttributeValue::Reference(key)) => {
let spec_tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate specification tag {}", key))?;
// Merge attributes from specification tag
let spec = process_subroutine_tag(tags, spec_tag)?;
name = name.or(spec.name);
mangled_name = mangled_name.or(spec.mangled_name);
return_type = return_type.or(Some(spec.return_type));
prototyped = prototyped || spec.prototyped;
parameters.extend(spec.parameters);
var_args = var_args || spec.var_args;
references.extend(spec.references);
member_of = member_of.or(spec.member_of);
inline = inline || spec.inline;
}
_ => {
bail!("Unhandled SubroutineType attribute {:?}", attr);
}
@ -1603,6 +1831,9 @@ fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
}
let mut variables = Vec::new();
let mut labels = Vec::new();
let mut blocks = Vec::new();
let mut inlines = Vec::new();
for child in tag.children(tags) {
ensure!(!var_args, "{:?} after UnspecifiedParameters", child.kind);
match child.kind {
@ -1611,12 +1842,30 @@ fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
}
TagKind::UnspecifiedParameters => var_args = true,
TagKind::LocalVariable => variables.push(process_local_variable_tag(tags, child)?),
TagKind::GlobalVariable => {
// TODO GlobalVariable refs?
}
TagKind::Label => labels.push(process_subroutine_label_tag(tags, child)?),
TagKind::LexicalBlock => blocks.push(process_subroutine_block_tag(tags, child)?),
TagKind::InlinedSubroutine => {
inlines.push(process_inlined_subroutine_tag(tags, child)?)
}
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType
| TagKind::PtrToMemberType => {
// Variable type, ignore
}
kind => bail!("Unhandled SubroutineType child {:?}", kind),
}
}
let return_type = return_type
.unwrap_or_else(|| Type { kind: TypeKind::Fundamental(FundType::Void), modifiers: vec![] });
let local = tag.kind == TagKind::Subroutine;
Ok(SubroutineType {
name,
mangled_name,
@ -1627,9 +1876,122 @@ fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
references,
member_of,
variables,
inline,
local,
labels,
blocks,
inlines,
})
}
fn process_subroutine_label_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineLabel> {
ensure!(tag.kind == TagKind::Label, "{:?} is not a Label tag", tag.kind);
let mut name = None;
let mut address = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(AttributeKind::LowPc, &AttributeValue::Address(addr)) => address = Some(addr),
_ => bail!("Unhandled Label attribute {:?}", attr),
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled Label child {:?}", child.kind);
}
let name = name.ok_or_else(|| anyhow!("Label without name: {:?}", tag))?;
let address = address.ok_or_else(|| anyhow!("Label without address: {:?}", tag))?;
Ok(SubroutineLabel { name, address })
}
fn process_subroutine_block_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineBlock> {
ensure!(tag.kind == TagKind::LexicalBlock, "{:?} is not a LexicalBlock tag", tag.kind);
let mut name = None;
let mut start_address = None;
let mut end_address = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(AttributeKind::LowPc, &AttributeValue::Address(addr)) => start_address = Some(addr),
(AttributeKind::HighPc, &AttributeValue::Address(addr)) => end_address = Some(addr),
_ => bail!("Unhandled Label attribute {:?}", attr),
}
}
let mut variables = Vec::new();
let mut blocks = Vec::new();
for child in tag.children(tags) {
match child.kind {
TagKind::LocalVariable => variables.push(process_local_variable_tag(tags, child)?),
TagKind::GlobalVariable => {
// TODO GlobalVariable refs?
}
TagKind::LexicalBlock => blocks.push(process_subroutine_block_tag(tags, child)?),
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType
| TagKind::PtrToMemberType => {
// Variable type, ignore
}
kind => bail!("Unhandled LexicalBlock child {:?}", kind),
}
}
let start_address =
start_address.ok_or_else(|| anyhow!("LexicalBlock without start address: {:?}", tag))?;
let end_address =
end_address.ok_or_else(|| anyhow!("LexicalBlock without end address: {:?}", tag))?;
Ok(SubroutineBlock { name, start_address, end_address, variables, blocks })
}
fn process_inlined_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineInline> {
ensure!(
tag.kind == TagKind::InlinedSubroutine,
"{:?} is not an InlinedSubroutine tag",
tag.kind
);
let mut specification = None;
let mut start_address = None;
let mut end_address = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Specification, &AttributeValue::Reference(key)) => {
specification = Some(key)
}
(AttributeKind::LowPc, &AttributeValue::Address(addr)) => start_address = Some(addr),
(AttributeKind::HighPc, &AttributeValue::Address(addr)) => end_address = Some(addr),
_ => bail!("Unhandled InlinedSubroutine attribute {:?}", attr),
}
}
for child in tag.children(tags) {
match child.kind {
TagKind::GlobalVariable => {
// TODO GlobalVariable refs?
}
kind => bail!("Unhandled InlinedSubroutine child {:?}", kind),
}
}
let specification = specification
.ok_or_else(|| anyhow!("InlinedSubroutine without specification: {:?}", tag))?;
let start_address = start_address
.ok_or_else(|| anyhow!("InlinedSubroutine without start address: {:?}", tag))?;
let end_address =
end_address.ok_or_else(|| anyhow!("InlinedSubroutine without end address: {:?}", tag))?;
Ok(SubroutineInline { specification, start_address, end_address })
}
fn process_subroutine_parameter_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineParameter> {
ensure!(tag.kind == TagKind::FormalParameter, "{:?} is not a FormalParameter tag", tag.kind);
@ -1654,9 +2016,17 @@ fn process_subroutine_parameter_tag(tags: &TagMap, tag: &Tag) -> Result<Subrouti
// TODO?
// info!("MwDwarf2Location: {:?} in {:?}", block, tag);
}
_ => {
bail!("Unhandled SubroutineParameter attribute {:?}", attr);
(AttributeKind::Specification, &AttributeValue::Reference(key)) => {
let spec_tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate specification tag {}", key))?;
// Merge attributes from specification tag
let spec = process_subroutine_parameter_tag(tags, spec_tag)?;
name = name.or(spec.name);
kind = kind.or(Some(spec.kind));
location = location.or(spec.location);
}
_ => bail!("Unhandled SubroutineParameter attribute {:?}", attr),
}
}
@ -1686,12 +2056,24 @@ fn process_local_variable_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineVari
_,
) => kind = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
location = Some(process_variable_location(block)?)
if !block.is_empty() {
location = Some(process_variable_location(block)?);
}
}
(AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => {
// TODO?
// info!("MwDwarf2Location: {:?} in {:?}", block, tag);
}
(AttributeKind::Specification, &AttributeValue::Reference(key)) => {
let spec_tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate specification tag {}", key))?;
// Merge attributes from specification tag
let spec = process_local_variable_tag(tags, spec_tag)?;
name = name.or(spec.name);
kind = kind.or(Some(spec.kind));
location = location.or(spec.location);
}
_ => {
bail!("Unhandled LocalVariable attribute {:?}", attr);
}