1941 lines
66 KiB
Rust

use std::{
collections::BTreeMap,
convert::TryFrom,
fmt::{Display, Formatter, Write},
io::{BufRead, Cursor, Seek, SeekFrom},
num::NonZeroU32,
};
use anyhow::{anyhow, bail, ensure, Context, Result};
use indent::indent_all_by;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
array_ref,
util::reader::{Endian, FromReader},
};
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
pub enum TagKind {
Padding = 0x0000,
ArrayType = 0x0001,
ClassType = 0x0002,
EntryPoint = 0x0003,
EnumerationType = 0x0004,
FormalParameter = 0x0005,
GlobalSubroutine = 0x0006,
GlobalVariable = 0x0007,
Label = 0x000a,
LexicalBlock = 0x000b,
LocalVariable = 0x000c,
Member = 0x000d,
PointerType = 0x000f,
ReferenceType = 0x0010,
// aka SourceFile
CompileUnit = 0x0011,
StringType = 0x0012,
StructureType = 0x0013,
Subroutine = 0x0014,
SubroutineType = 0x0015,
Typedef = 0x0016,
UnionType = 0x0017,
UnspecifiedParameters = 0x0018,
Variant = 0x0019,
CommonBlock = 0x001a,
CommonInclusion = 0x001b,
Inheritance = 0x001c,
InlinedSubroutine = 0x001d,
Module = 0x001e,
PtrToMemberType = 0x001f,
SetType = 0x0020,
SubrangeType = 0x0021,
WithStmt = 0x0022,
// User types
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
pub enum FundType {
WideChar = 0x0000, // Likely an MW bug
Char = 0x0001,
SignedChar = 0x0002,
UnsignedChar = 0x0003,
Short = 0x0004,
SignedShort = 0x0005,
UnsignedShort = 0x0006,
Integer = 0x0007,
SignedInteger = 0x0008,
UnsignedInteger = 0x0009,
Long = 0x000a,
SignedLong = 0x000b,
UnsignedLong = 0x000c,
Pointer = 0x000d,
Float = 0x000e,
DblPrecFloat = 0x000f,
ExtPrecFloat = 0x0010,
Complex = 0x0011,
DblPrecComplex = 0x0012,
Void = 0x0014,
Boolean = 0x0015,
ExtPrecComplex = 0x0016,
Label = 0x0017,
// User types
LongLong = 0x8008,
SignedLongLong = 0x8108,
UnsignedLongLong = 0x8208,
Vec2x32Float = 0xac00,
}
impl FundType {
pub fn size(self) -> Result<u32> {
Ok(match self {
FundType::Char | FundType::SignedChar | FundType::UnsignedChar | FundType::Boolean => 1,
FundType::WideChar
| FundType::Short
| FundType::SignedShort
| FundType::UnsignedShort => 2,
FundType::Integer | FundType::SignedInteger | FundType::UnsignedInteger => 4,
FundType::Long
| FundType::SignedLong
| FundType::UnsignedLong
| FundType::Pointer
| FundType::Float => 4,
FundType::DblPrecFloat
| FundType::LongLong
| FundType::SignedLongLong
| FundType::UnsignedLongLong
| FundType::Vec2x32Float => 8,
FundType::Void => 0,
FundType::ExtPrecFloat
| FundType::Complex
| FundType::DblPrecComplex
| FundType::ExtPrecComplex
| FundType::Label => bail!("Unhandled fundamental type {self:?}"),
})
}
pub fn name(self) -> Result<&'static str> {
Ok(match self {
FundType::WideChar => "wchar_t",
FundType::Char => "char",
FundType::SignedChar => "signed char",
FundType::UnsignedChar => "unsigned char",
FundType::Short => "short",
FundType::SignedShort => "signed short",
FundType::UnsignedShort => "unsigned short",
FundType::Integer => "int",
FundType::SignedInteger => "signed int",
FundType::UnsignedInteger => "unsigned int",
FundType::Long => "long",
FundType::SignedLong => "signed long",
FundType::UnsignedLong => "unsigned long",
FundType::Pointer => "void *",
FundType::Float => "float",
FundType::DblPrecFloat => "double",
FundType::ExtPrecFloat => "long double",
FundType::Void => "void",
FundType::Boolean => "bool",
FundType::Complex
| FundType::DblPrecComplex
| FundType::ExtPrecComplex
| FundType::Label => bail!("Unhandled fundamental type {self:?}"),
FundType::LongLong => "long long",
FundType::SignedLongLong => "signed long long",
FundType::UnsignedLongLong => "unsigned long long",
FundType::Vec2x32Float => "__vec2x32float__",
})
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum Modifier {
PointerTo = 0x01,
ReferenceTo = 0x02,
Const = 0x03,
Volatile = 0x04,
// User types
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum SubscriptFormat {
FundTypeConstConst = 0x0,
FundTypeConstLocation = 0x1,
FundTypeLocationConst = 0x2,
FundTypeLocationLocation = 0x3,
UserTypeConstConst = 0x4,
UserTypeConstLocation = 0x5,
UserTypeLocationConst = 0x6,
UserTypeLocationLocation = 0x7,
ElementType = 0x8,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u8)]
pub enum LocationOp {
Register = 0x01,
BaseRegister = 0x02,
Address = 0x03,
Const = 0x04,
Deref2 = 0x05,
Deref4 = 0x06,
Add = 0x07,
// User types
MwFpReg = 0x80,
MwFpDReg = 0x81,
MwDRef8 = 0x82,
}
const FORM_MASK: u16 = 0xF;
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
enum FormKind {
Addr = 0x1,
Ref = 0x2,
Block2 = 0x3,
Block4 = 0x4,
Data2 = 0x5,
Data4 = 0x6,
Data8 = 0x7,
String = 0x8,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, IntoPrimitive, TryFromPrimitive)]
#[repr(u16)]
pub enum AttributeKind {
Sibling = 0x0010 | (FormKind::Ref as u16),
Location = 0x0020 | (FormKind::Block2 as u16),
Name = 0x0030 | (FormKind::String as u16),
FundType = 0x0050 | (FormKind::Data2 as u16),
ModFundType = 0x0060 | (FormKind::Block2 as u16),
UserDefType = 0x0070 | (FormKind::Ref as u16),
ModUDType = 0x0080 | (FormKind::Block2 as u16),
Ordering = 0x0090 | (FormKind::Data2 as u16),
SubscrData = 0x00a0 | (FormKind::Block2 as u16),
ByteSize = 0x00b0 | (FormKind::Data4 as u16),
BitOffset = 0x00c0 | (FormKind::Data2 as u16),
BitSize = 0x00d0 | (FormKind::Data4 as u16),
ElementList = 0x00f0 | (FormKind::Block4 as u16),
StmtList = 0x0100 | (FormKind::Data4 as u16),
LowPc = 0x0110 | (FormKind::Addr as u16),
HighPc = 0x0120 | (FormKind::Addr as u16),
Language = 0x0130 | (FormKind::Data4 as u16),
Member = 0x0140 | (FormKind::Ref as u16),
Discr = 0x0150 | (FormKind::Ref as u16),
DiscrValue = 0x0160 | (FormKind::Block2 as u16),
StringLength = 0x0190 | (FormKind::Block2 as u16),
CommonReference = 0x01a0 | (FormKind::Ref as u16),
CompDir = 0x01b0 | (FormKind::String as u16),
ConstValueString = 0x01c0 | (FormKind::String as u16),
ConstValueData2 = 0x01c0 | (FormKind::Data2 as u16),
ConstValueData4 = 0x01c0 | (FormKind::Data4 as u16),
ConstValueData8 = 0x01c0 | (FormKind::Data8 as u16),
ConstValueBlock2 = 0x01c0 | (FormKind::Block2 as u16),
ConstValueBlock4 = 0x01c0 | (FormKind::Block4 as u16),
ContainingType = 0x01d0 | (FormKind::Ref as u16),
DefaultValueAddr = 0x01e0 | (FormKind::Addr as u16),
DefaultValueData2 = 0x01e0 | (FormKind::Data2 as u16),
DefaultValueData8 = 0x01e0 | (FormKind::Data8 as u16),
DefaultValueString = 0x01e0 | (FormKind::String as u16),
Friends = 0x01f0 | (FormKind::Block2 as u16),
Inline = 0x0200 | (FormKind::String as u16),
IsOptional = 0x0210 | (FormKind::String as u16),
LowerBoundRef = 0x0220 | (FormKind::Ref as u16),
LowerBoundData2 = 0x0220 | (FormKind::Data2 as u16),
LowerBoundData4 = 0x0220 | (FormKind::Data4 as u16),
LowerBoundData8 = 0x0220 | (FormKind::Data8 as u16),
Program = 0x0230 | (FormKind::String as u16),
Private = 0x0240 | (FormKind::String as u16),
Producer = 0x0250 | (FormKind::String as u16),
Protected = 0x0260 | (FormKind::String as u16),
Prototyped = 0x0270 | (FormKind::String as u16),
Public = 0x0280 | (FormKind::String as u16),
PureVirtual = 0x0290 | (FormKind::String as u16),
ReturnAddr = 0x02a0 | (FormKind::Block2 as u16),
Specification = 0x02b0 | (FormKind::Ref as u16),
StartScope = 0x02c0 | (FormKind::Data4 as u16),
StrideSize = 0x02e0 | (FormKind::Data4 as u16),
UpperBoundRef = 0x02f0 | (FormKind::Ref as u16),
UpperBoundData2 = 0x02f0 | (FormKind::Data2 as u16),
UpperBoundData4 = 0x02f0 | (FormKind::Data4 as u16),
UpperBoundData8 = 0x02f0 | (FormKind::Data8 as u16),
Virtual = 0x0300 | (FormKind::String as u16),
LoUser = 0x2000,
HiUser = 0x3ff0,
// User types
MwMangled = 0x2000 | (FormKind::String as u16),
MwGlobalRef = 0x2020 | (FormKind::Ref as u16),
MwGlobalRefByName = 0x2030 | (FormKind::String as u16),
MwDwarf2Location = 0x2340 | (FormKind::Block2 as u16),
Unknown800 = 0x8000 | (FormKind::Data4 as u16),
Unknown801 = 0x8010 | (FormKind::Data4 as u16),
MwPrologueEnd = 0x8040 | (FormKind::Addr as u16),
MwEpilogueStart = 0x8050 | (FormKind::Addr as u16),
}
#[derive(Debug, Clone)]
pub enum AttributeValue {
Address(u32),
Reference(u32),
Data2(u16),
Data4(u32),
Data8(u64),
Block(Vec<u8>),
String(String),
}
#[derive(Debug, Clone)]
pub struct Attribute {
pub kind: AttributeKind,
pub value: AttributeValue,
}
#[derive(Debug, Clone)]
pub struct Tag {
pub key: u32,
pub kind: TagKind,
pub attributes: Vec<Attribute>,
}
pub type TagMap = BTreeMap<u32, Tag>;
pub type TypedefMap = BTreeMap<u32, Vec<u32>>;
impl Tag {
#[inline]
pub fn attribute(&self, kind: AttributeKind) -> Option<&Attribute> {
self.attributes.iter().find(|attr| attr.kind == kind)
}
#[inline]
pub fn address_attribute(&self, kind: AttributeKind) -> Option<u32> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::Address(addr), .. }) => Some(*addr),
_ => None,
}
}
#[inline]
pub fn reference_attribute(&self, kind: AttributeKind) -> Option<u32> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::Reference(addr), .. }) => Some(*addr),
_ => None,
}
}
#[inline]
pub fn string_attribute(&self, kind: AttributeKind) -> Option<&String> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::String(str), .. }) => Some(str),
_ => None,
}
}
#[inline]
pub fn block_attribute(&self, kind: AttributeKind) -> Option<&[u8]> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::Block(vec), .. }) => Some(vec),
_ => None,
}
}
#[inline]
pub fn data4_attribute(&self, kind: AttributeKind) -> Option<u32> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::Data4(value), .. }) => Some(*value),
_ => None,
}
}
#[inline]
pub fn data2_attribute(&self, kind: AttributeKind) -> Option<u16> {
match self.attribute(kind) {
Some(Attribute { value: AttributeValue::Data2(value), .. }) => Some(*value),
_ => None,
}
}
#[inline]
pub fn type_attribute(&self) -> Option<&Attribute> {
self.attributes.iter().find(|attr| {
matches!(
attr.kind,
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType
)
})
}
pub fn children<'a>(&self, tags: &'a TagMap) -> Vec<&'a Tag> {
let sibling = self.next_sibling(tags);
let mut children = Vec::new();
let mut child = match self.next_tag(tags) {
Some(child) => child,
None => return children,
};
loop {
if let Some(end) = sibling {
if child.key == end.key {
break;
}
}
if child.kind != TagKind::Padding {
children.push(child);
}
match child.next_sibling(tags) {
Some(next) => child = next,
None => break,
}
}
children
}
/// Returns the next sibling tag, if any
pub fn next_sibling<'a>(&self, tags: &'a TagMap) -> Option<&'a Tag> {
if let Some(key) = self.reference_attribute(AttributeKind::Sibling) {
tags.get(&key)
} else {
self.next_tag(tags)
}
}
/// Returns the next tag sequentially, if any
pub fn next_tag<'a>(&self, tags: &'a TagMap) -> Option<&'a Tag> {
tags.range(self.key + 1..).next().map(|(_, tag)| tag)
}
}
pub fn read_debug_section<R>(reader: &mut R) -> Result<TagMap>
where R: BufRead + Seek + ?Sized {
let len = {
let old_pos = reader.stream_position()?;
let len = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(old_pos))?;
len
};
let mut tags = BTreeMap::new();
loop {
let position = reader.stream_position()?;
if position >= len {
break;
}
let tag = read_tag(reader)?;
tags.insert(position as u32, tag);
}
Ok(tags)
}
#[allow(unused)]
pub fn read_aranges_section<R>(reader: &mut R) -> Result<()>
where R: BufRead + Seek + ?Sized {
let len = {
let old_pos = reader.stream_position()?;
let len = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(old_pos))?;
len
};
// let mut tags = BTreeMap::new();
loop {
let position = reader.stream_position()?;
if position >= len {
break;
}
let size = u32::from_reader(reader, Endian::Big)?;
let version = u8::from_reader(reader, Endian::Big)?;
ensure!(version == 1, "Expected version 1, got {version}");
let _debug_offs = u32::from_reader(reader, Endian::Big)?;
let _debug_size = u32::from_reader(reader, Endian::Big)?;
while reader.stream_position()? < position + size as u64 {
let _address = u32::from_reader(reader, Endian::Big)?;
let _length = u32::from_reader(reader, Endian::Big)?;
}
}
Ok(())
}
fn read_tag<R>(reader: &mut R) -> Result<Tag>
where R: BufRead + Seek + ?Sized {
let position = reader.stream_position()?;
let size = u32::from_reader(reader, Endian::Big)?;
if size < 8 {
// Null entry
if size > 4 {
reader.seek(SeekFrom::Current(size as i64 - 4))?;
}
return Ok(Tag { key: position as u32, kind: TagKind::Padding, attributes: vec![] });
}
let tag = TagKind::try_from(u16::from_reader(reader, Endian::Big)?)
.context("Unknown DWARF tag type")?;
let mut attributes = Vec::new();
if tag == TagKind::Padding {
reader.seek(SeekFrom::Start(position + size as u64))?; // Skip padding
} else {
while reader.stream_position()? < position + size as u64 {
let attribute = read_attribute(reader)?;
attributes.push(attribute);
}
}
Ok(Tag { key: position as u32, kind: tag, attributes })
}
// TODO Shift-JIS?
fn read_string<R>(reader: &mut R) -> Result<String>
where R: BufRead + ?Sized {
let mut str = String::new();
let mut buf = [0u8; 1];
loop {
reader.read_exact(&mut buf)?;
if buf[0] == 0 {
break;
}
str.push(buf[0] as char);
}
Ok(str)
}
fn read_attribute<R>(reader: &mut R) -> Result<Attribute>
where R: BufRead + Seek + ?Sized {
let attr_type = u16::from_reader(reader, Endian::Big)?;
let attr = AttributeKind::try_from(attr_type).context("Unknown DWARF attribute type")?;
let form = FormKind::try_from(attr_type & FORM_MASK).context("Unknown DWARF form type")?;
let value = match form {
FormKind::Addr => AttributeValue::Address(u32::from_reader(reader, Endian::Big)?),
FormKind::Ref => AttributeValue::Reference(u32::from_reader(reader, Endian::Big)?),
FormKind::Block2 => {
let size = u16::from_reader(reader, Endian::Big)?;
let mut data = vec![0u8; size as usize];
reader.read_exact(&mut data)?;
AttributeValue::Block(data)
}
FormKind::Block4 => {
let size = u32::from_reader(reader, Endian::Big)?;
let mut data = vec![0u8; size as usize];
reader.read_exact(&mut data)?;
AttributeValue::Block(data)
}
FormKind::Data2 => AttributeValue::Data2(u16::from_reader(reader, Endian::Big)?),
FormKind::Data4 => AttributeValue::Data4(u32::from_reader(reader, Endian::Big)?),
FormKind::Data8 => AttributeValue::Data8(u64::from_reader(reader, Endian::Big)?),
FormKind::String => AttributeValue::String(read_string(reader)?),
};
Ok(Attribute { kind: attr, value })
}
#[derive(Debug, Clone)]
pub struct ArrayDimension {
pub index_type: Type,
pub size: Option<NonZeroU32>,
}
#[derive(Debug, Clone)]
pub struct ArrayType {
pub element_type: Box<Type>,
pub dimensions: Vec<ArrayDimension>,
}
#[derive(Debug, Clone)]
pub struct BitData {
pub bit_size: u32,
pub bit_offset: u16,
}
#[derive(Debug, Clone)]
pub struct StructureMember {
pub name: String,
pub kind: Type,
pub offset: u32,
pub bit: Option<BitData>,
pub visibility: Visibility,
pub byte_size: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructureKind {
Struct,
Class,
}
#[derive(Debug, Clone)]
pub struct StructureType {
pub kind: StructureKind,
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<StructureMember>,
pub bases: Vec<StructureBase>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Visibility {
Private,
Protected,
Public,
}
#[derive(Debug, Clone)]
pub struct StructureBase {
pub name: String,
pub base_type: Type,
pub offset: u32,
pub visibility: Visibility,
pub virtual_base: bool,
}
#[derive(Debug, Clone)]
pub struct EnumerationMember {
pub name: String,
pub value: i32,
}
#[derive(Debug, Clone)]
pub struct EnumerationType {
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<EnumerationMember>,
}
#[derive(Debug, Clone)]
pub struct UnionType {
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<StructureMember>,
}
#[derive(Debug, Clone)]
pub struct SubroutineParameter {
pub name: Option<String>,
pub kind: Type,
pub location: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SubroutineVariable {
pub name: Option<String>,
pub kind: Type,
pub location: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SubroutineType {
pub name: Option<String>,
pub mangled_name: Option<String>,
pub return_type: Type,
pub parameters: Vec<SubroutineParameter>,
pub var_args: bool,
pub prototyped: bool,
pub references: Vec<u32>,
pub member_of: Option<u32>,
pub variables: Vec<SubroutineVariable>,
}
#[derive(Debug, Clone)]
pub struct PtrToMemberType {
pub kind: Type,
pub containing_type: u32,
}
#[derive(Debug, Clone)]
pub enum UserDefinedType {
Array(ArrayType),
Structure(StructureType),
Enumeration(EnumerationType),
Union(UnionType),
Subroutine(SubroutineType),
PtrToMember(PtrToMemberType),
}
#[derive(Debug, Clone)]
pub struct VariableTag {
pub name: Option<String>,
pub mangled_name: Option<String>,
pub kind: Type,
pub address: Option<u32>,
pub local: bool,
}
#[derive(Debug, Clone)]
pub struct TypedefTag {
pub name: String,
pub kind: Type,
}
#[derive(Debug, Clone)]
pub enum TagType {
Variable(VariableTag),
Typedef(TypedefTag),
UserDefined(UserDefinedType),
}
impl UserDefinedType {
pub fn is_definition(&self) -> bool {
match self {
UserDefinedType::Array(_) | UserDefinedType::PtrToMember(_) => false,
UserDefinedType::Structure(t) => t.name.is_some(),
UserDefinedType::Enumeration(t) => t.name.is_some(),
UserDefinedType::Union(t) => t.name.is_some(),
UserDefinedType::Subroutine(t) => t.name.is_some(),
}
}
pub fn size(&self, tags: &TagMap) -> Result<u32> {
Ok(match self {
UserDefinedType::Array(t) => {
let mut size = t.element_type.size(tags)?;
for dim in &t.dimensions {
size *= dim.size.map(|u| u.get()).unwrap_or_default();
}
size
}
UserDefinedType::Structure(t) => t.byte_size,
UserDefinedType::Enumeration(t) => t.byte_size,
UserDefinedType::Union(t) => t.byte_size,
UserDefinedType::Subroutine(_) => 0,
UserDefinedType::PtrToMember(_) => 4,
})
}
}
#[derive(Debug, Copy, Clone)]
pub enum TypeKind {
Fundamental(FundType),
UserDefined(u32),
}
#[derive(Debug, Clone)]
pub struct Type {
pub kind: TypeKind,
pub modifiers: Vec<Modifier>,
}
impl Type {
pub fn size(&self, tags: &TagMap) -> Result<u32> {
if self.modifiers.iter().any(|m| matches!(m, Modifier::PointerTo | Modifier::ReferenceTo)) {
return Ok(4);
}
match self.kind {
TypeKind::Fundamental(ft) => ft.size(),
TypeKind::UserDefined(key) => {
let tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate user defined type {}", key))?;
let ud_type = ud_type(tags, tag)?;
ud_type.size(tags)
}
}
}
}
pub fn apply_modifiers(mut str: TypeString, modifiers: &[Modifier]) -> Result<TypeString> {
let mut has_pointer = false;
for &modifier in modifiers.iter().rev() {
match modifier {
Modifier::PointerTo => {
if !has_pointer && !str.suffix.is_empty() {
if str.member.is_empty() {
str.prefix.push_str(" (*");
} else {
write!(str.prefix, " ({}*", str.member)?;
}
str.suffix.insert(0, ')');
} else {
str.prefix.push_str(" *");
}
has_pointer = true;
}
Modifier::ReferenceTo => {
if !has_pointer && !str.suffix.is_empty() {
str.prefix.push_str(" (&");
str.suffix.insert(0, ')');
} else {
str.prefix.push_str(" &");
}
has_pointer = true;
}
Modifier::Const => {
if has_pointer {
str.prefix.push_str(" const");
} else {
str.prefix.insert_str(0, "const ");
}
}
Modifier::Volatile => {
if has_pointer {
str.prefix.push_str(" volatile");
} else {
str.prefix.insert_str(0, "volatile ");
}
}
}
}
Ok(str)
}
#[derive(Debug, Default, Clone)]
pub struct TypeString {
pub prefix: String,
pub suffix: String,
// TODO: rework this eventually and merge with PTMF handling
pub member: String,
}
impl Display for TypeString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.member.is_empty() {
write!(f, "{}{}", self.prefix, self.suffix)?;
} else {
// TODO member print likely wrong
write!(f, "{} {}{}", self.prefix, self.member, self.suffix)?;
}
Ok(())
}
}
pub fn type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &Type,
include_anonymous_def: bool,
) -> Result<TypeString> {
let str = match t.kind {
TypeKind::Fundamental(ft) => {
TypeString { prefix: ft.name()?.to_string(), ..Default::default() }
}
TypeKind::UserDefined(key) => {
if let Some(&td_key) = typedefs.get(&key).and_then(|v| v.first()) {
let tag =
tags.get(&td_key).ok_or_else(|| anyhow!("Failed to locate typedef {}", key))?;
let td_name = tag
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("typedef without name"))?;
TypeString { prefix: td_name.clone(), ..Default::default() }
} else {
let tag = tags
.get(&key)
.ok_or_else(|| anyhow!("Failed to locate user defined type {}", key))?;
ud_type_string(tags, typedefs, &ud_type(tags, tag)?, true, include_anonymous_def)?
}
}
};
apply_modifiers(str, &t.modifiers)
}
fn array_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &ArrayType,
include_anonymous_def: bool,
) -> Result<TypeString> {
let mut out = type_string(tags, typedefs, t.element_type.as_ref(), include_anonymous_def)?;
for dim in &t.dimensions {
ensure!(
matches!(
dim.index_type.kind,
TypeKind::Fundamental(FundType::Long | FundType::Integer)
),
"Unsupported array index type '{}'",
type_string(tags, typedefs, &dim.index_type, true)?
);
match dim.size {
None => out.suffix.insert_str(0, "[]"),
Some(size) => out.suffix = format!("[{}]{}", size, out.suffix),
};
}
Ok(out)
}
fn structure_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &StructureType,
include_keyword: bool,
include_anonymous_def: bool,
) -> Result<TypeString> {
let prefix = if let Some(name) = t.name.as_ref() {
if include_keyword {
match t.kind {
StructureKind::Struct => format!("struct {}", name),
StructureKind::Class => format!("class {}", name),
}
} else {
name.clone()
}
} else if include_anonymous_def {
struct_def_string(tags, typedefs, t)?
} else if include_keyword {
match t.kind {
StructureKind::Struct => "struct [anonymous]".to_string(),
StructureKind::Class => "class [anonymous]".to_string(),
}
} else {
match t.kind {
StructureKind::Struct => "[anonymous struct]".to_string(),
StructureKind::Class => "[anonymous class]".to_string(),
}
};
Ok(TypeString { prefix, ..Default::default() })
}
fn enumeration_type_string(
_tags: &TagMap,
_typedefs: &TypedefMap,
t: &EnumerationType,
include_keyword: bool,
include_anonymous_def: bool,
) -> Result<TypeString> {
let prefix = if let Some(name) = t.name.as_ref() {
if include_keyword {
format!("enum {}", name)
} else {
name.clone()
}
} else if include_anonymous_def {
enum_def_string(t)?
} else if include_keyword {
"enum [anonymous]".to_string()
} else {
"[anonymous enum]".to_string()
};
Ok(TypeString { prefix, ..Default::default() })
}
fn union_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &UnionType,
include_keyword: bool,
include_anonymous_def: bool,
) -> Result<TypeString> {
let prefix = if let Some(name) = t.name.as_ref() {
if include_keyword {
format!("union {}", name)
} else {
name.clone()
}
} else if include_anonymous_def {
union_def_string(tags, typedefs, t)?
} else if include_keyword {
"union [anonymous]".to_string()
} else {
"[anonymous union]".to_string()
};
Ok(TypeString { prefix, ..Default::default() })
}
pub fn ud_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &UserDefinedType,
include_keyword: bool,
include_anonymous_def: bool,
) -> Result<TypeString> {
Ok(match t {
UserDefinedType::Array(t) => array_type_string(tags, typedefs, t, include_anonymous_def)?,
UserDefinedType::Structure(t) => {
structure_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)?
}
UserDefinedType::Enumeration(t) => {
enumeration_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)?
}
UserDefinedType::Union(t) => {
union_type_string(tags, typedefs, t, include_keyword, include_anonymous_def)?
}
UserDefinedType::Subroutine(t) => subroutine_type_string(tags, typedefs, t)?,
UserDefinedType::PtrToMember(t) => ptr_to_member_type_string(tags, typedefs, t)?,
})
}
fn ptr_to_member_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &PtrToMemberType,
) -> Result<TypeString> {
let ts = type_string(tags, typedefs, &t.kind, true)?;
let containing_type = tags
.get(&t.containing_type)
.ok_or_else(|| anyhow!("Failed to locate containing type {}", t.containing_type))?;
let containing_ts =
ud_type_string(tags, typedefs, &ud_type(tags, containing_type)?, false, false)?;
Ok(TypeString {
prefix: format!("{} ({}::*", ts.prefix, containing_ts.prefix),
suffix: format!("{}){}", containing_ts.suffix, ts.suffix),
..Default::default()
})
}
pub fn ud_type_def(tags: &TagMap, typedefs: &TypedefMap, t: &UserDefinedType) -> Result<String> {
match t {
UserDefinedType::Array(t) => {
let ts = array_type_string(tags, typedefs, t, false)?;
Ok(format!("// Array: {}{}", ts.prefix, ts.suffix))
}
UserDefinedType::Subroutine(t) => Ok(subroutine_def_string(tags, typedefs, t)?),
UserDefinedType::Structure(t) => Ok(struct_def_string(tags, typedefs, t)?),
UserDefinedType::Enumeration(t) => Ok(enum_def_string(t)?),
UserDefinedType::Union(t) => Ok(union_def_string(tags, typedefs, t)?),
UserDefinedType::PtrToMember(t) => {
let ts = ptr_to_member_type_string(tags, typedefs, t)?;
Ok(format!("// PtrToMember: {}{}", ts.prefix, ts.suffix))
}
}
}
pub fn subroutine_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &SubroutineType,
) -> Result<TypeString> {
let mut out = type_string(tags, typedefs, &t.return_type, true)?;
let mut parameters = String::new();
if t.parameters.is_empty() {
if t.var_args {
parameters = "...".to_string();
} else if t.prototyped {
parameters = "void".to_string();
}
} else {
for (idx, parameter) in t.parameters.iter().enumerate() {
if idx > 0 {
write!(parameters, ", ")?;
}
let ts = type_string(tags, typedefs, &parameter.kind, true)?;
if let Some(name) = &parameter.name {
write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?;
} else {
write!(parameters, "{}{}", ts.prefix, ts.suffix)?;
}
if let Some(location) = &parameter.location {
write!(parameters, " /* {} */", location)?;
}
}
if t.var_args {
write!(parameters, ", ...")?;
}
}
out.suffix = format!("({}){}", parameters, out.suffix);
if let Some(member_of) = t.member_of {
let tag = tags
.get(&member_of)
.ok_or_else(|| anyhow!("Failed to locate member_of tag {}", member_of))?;
let base_name = tag
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("member_of tag {} has no name attribute", member_of))?;
out.member = format!("{}::", base_name);
}
Ok(out)
}
pub fn subroutine_def_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &SubroutineType,
) -> Result<String> {
let rt = type_string(tags, typedefs, &t.return_type, true)?;
let mut out = rt.prefix;
out.push(' ');
let mut name_written = false;
if let Some(member_of) = t.member_of {
let tag = tags
.get(&member_of)
.ok_or_else(|| anyhow!("Failed to locate member_of tag {}", member_of))?;
let base_name = tag
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("member_of tag {} has no name attribute", member_of))?;
write!(out, "{}::", base_name)?;
// Handle constructors and destructors
if let Some(name) = t.name.as_ref() {
if name == "__dt" {
write!(out, "~{}", base_name)?;
name_written = true;
} else if name == "__ct" {
write!(out, "{}", base_name)?;
name_written = true;
}
}
}
if !name_written {
if let Some(name) = t.name.as_ref() {
out.push_str(name);
}
}
let mut parameters = String::new();
if t.parameters.is_empty() {
if t.var_args {
parameters = "...".to_string();
} else if t.prototyped {
parameters = "void".to_string();
}
} else {
for (idx, parameter) in t.parameters.iter().enumerate() {
if idx > 0 {
write!(parameters, ", ")?;
}
let ts = type_string(tags, typedefs, &parameter.kind, true)?;
if let Some(name) = &parameter.name {
write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?;
} else {
write!(parameters, "{}{}", ts.prefix, ts.suffix)?;
}
if let Some(location) = &parameter.location {
write!(parameters, " /* {} */", location)?;
}
}
if t.var_args {
write!(out, ", ...")?;
}
}
write!(out, "({}){} {{", parameters, rt.suffix)?;
if !t.variables.is_empty() {
writeln!(out, "\n // Local variables")?;
let mut var_out = String::new();
for variable in &t.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))?;
}
if !t.references.is_empty() {
writeln!(out, "\n // References")?;
for &reference in &t.references {
let tag = tags
.get(&reference)
.ok_or_else(|| anyhow!("Failed to locate reference tag {}", reference))?;
if tag.kind == TagKind::Padding {
writeln!(out, " // -> ??? ({})", reference)?;
continue;
}
let variable = process_variable_tag(tags, tag)?;
writeln!(out, " // -> {}", variable_string(tags, typedefs, &variable, false)?)?;
}
}
writeln!(out, "}}")?;
Ok(out)
}
pub fn struct_def_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &StructureType,
) -> Result<String> {
let mut out = match t.kind {
StructureKind::Struct => "struct".to_string(),
StructureKind::Class => "class".to_string(),
};
if let Some(name) = t.name.as_ref() {
write!(out, " {}", name)?;
}
let mut wrote_base = false;
for base in &t.bases {
if !wrote_base {
out.push_str(" : ");
wrote_base = true;
} else {
out.push_str(", ");
}
match base.visibility {
Visibility::Private => out.push_str("private "),
Visibility::Protected => out.push_str("protected "),
Visibility::Public => out.push_str("public "),
}
if base.virtual_base {
out.push_str("virtual ");
}
out.push_str(&base.name);
}
writeln!(out, " {{\n // total size: {:#X}", t.byte_size)?;
let mut var_out = String::new();
for member in &t.members {
let ts = type_string(tags, typedefs, &member.kind, true)?;
write!(var_out, "{} {}{}", ts.prefix, member.name, ts.suffix)?;
if let Some(bit) = &member.bit {
write!(var_out, " : {}", bit.bit_size)?;
}
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)?;
}
write!(out, "{}", indent_all_by(4, var_out))?;
write!(out, "}}")?;
Ok(out)
}
pub fn enum_def_string(t: &EnumerationType) -> Result<String> {
let mut out = match t.name.as_ref() {
Some(name) => format!("enum {} {{\n", name),
None => "enum {\n".to_string(),
};
for member in t.members.iter() {
writeln!(out, " {} = {},", member.name, member.value)?;
}
write!(out, "}}")?;
Ok(out)
}
pub fn union_def_string(tags: &TagMap, typedefs: &TypedefMap, t: &UnionType) -> Result<String> {
let mut out = match t.name.as_ref() {
Some(name) => format!("union {} {{\n", name),
None => "union {\n".to_string(),
};
let mut var_out = String::new();
for member in t.members.iter() {
let ts = type_string(tags, typedefs, &member.kind, true)?;
write!(var_out, "{} {}{};", ts.prefix, member.name, ts.suffix)?;
let size = if let Some(size) = member.byte_size { size } else { member.kind.size(tags)? };
write!(var_out, " // offset {:#X}, size {:#X}", member.offset, size)?;
writeln!(var_out)?;
}
write!(out, "{}", indent_all_by(4, var_out))?;
write!(out, "}}")?;
Ok(out)
}
pub fn process_offset(block: &[u8]) -> Result<u32> {
if block.len() == 6 && block[0] == LocationOp::Const as u8 && block[5] == LocationOp::Add as u8
{
Ok(u32::from_be_bytes(*array_ref!(block, 1, 4)))
} else {
Err(anyhow!("Unhandled location data, expected offset"))
}
}
pub fn process_address(block: &[u8]) -> Result<u32> {
if block.len() == 5 && block[0] == LocationOp::Address as u8 {
Ok(u32::from_be_bytes(*array_ref!(block, 1, 4)))
} else {
Err(anyhow!("Unhandled location data, expected address"))
}
}
pub const REGISTER_NAMES: [&str; 109] = [
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", // 0-7
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", // 8-15
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", // 16-23
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", // 24-31
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", // 32-39
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", // 40-47
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", // 48-55
"f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", // 56-63
"mq", "lr", "ctr", "ap", "cr0", "cr1", "cr2", "cr3", // 64-71
"cr4", "cr5", "cr6", "cr7", "xer", "v0", "v1", "v2", // 72-79
"v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", // 80-87
"v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", // 88-95
"v19", "v20", "v21", "v22", "v23", "v24", "v25", "v26", // 96-103
"v27", "v28", "v29", "v30", "v31", // 104-108
];
pub const fn register_name(reg: u32) -> &'static str {
if reg < REGISTER_NAMES.len() as u32 {
REGISTER_NAMES[reg as usize]
} else {
"[invalid]"
}
}
pub fn process_variable_location(block: &[u8]) -> Result<String> {
if block.len() == 5
&& (block[0] == LocationOp::Register as u8 || block[0] == LocationOp::BaseRegister as u8)
{
Ok(register_name(u32::from_be_bytes(*array_ref!(block, 1, 4))).to_string())
} else if block.len() == 5 && block[0] == LocationOp::Address as u8 {
Ok(format!("@ {:#010X}", u32::from_be_bytes(*array_ref!(block, 1, 4))))
} else if block.len() == 11
&& block[0] == LocationOp::BaseRegister as u8
&& block[5] == LocationOp::Const as u8
&& block[10] == LocationOp::Add as u8
{
Ok(format!(
"{}+{:#X}",
register_name(u32::from_be_bytes(*array_ref!(block, 1, 4))),
u32::from_be_bytes(*array_ref!(block, 6, 4))
))
} else {
Err(anyhow!("Unhandled location data {:?}, expected variable loc", block))
}
}
fn process_inheritance_tag(tags: &TagMap, tag: &Tag) -> Result<StructureBase> {
ensure!(tag.kind == TagKind::Inheritance, "{:?} is not an Inheritance tag", tag.kind);
let mut name = None;
let mut base_type = None;
let mut offset = None;
let mut visibility = None;
let mut virtual_base = false;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => base_type = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
offset = Some(process_offset(block)?)
}
(AttributeKind::Private, _) => visibility = Some(Visibility::Private),
(AttributeKind::Protected, _) => visibility = Some(Visibility::Protected),
(AttributeKind::Public, _) => visibility = Some(Visibility::Public),
(AttributeKind::Virtual, _) => virtual_base = true,
_ => {
bail!("Unhandled Inheritance attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
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 =
visibility.ok_or_else(|| anyhow!("Inheritance without visibility: {:?}", tag))?;
Ok(StructureBase { name, base_type, offset, visibility, virtual_base })
}
fn process_structure_member_tag(tags: &TagMap, tag: &Tag) -> Result<StructureMember> {
ensure!(tag.kind == TagKind::Member, "{:?} is not a Member tag", tag.kind);
let mut name = None;
let mut member_type = None;
let mut offset = None;
let mut byte_size = None;
let mut bit_size = None;
let mut bit_offset = None;
let mut visibility = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => member_type = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
offset = Some(process_offset(block)?)
}
(AttributeKind::ByteSize, &AttributeValue::Data4(value)) => byte_size = Some(value),
(AttributeKind::BitSize, &AttributeValue::Data4(value)) => bit_size = Some(value),
(AttributeKind::BitOffset, &AttributeValue::Data2(value)) => bit_offset = Some(value),
(AttributeKind::Private, _) => visibility = Some(Visibility::Private),
(AttributeKind::Protected, _) => visibility = Some(Visibility::Protected),
(AttributeKind::Public, _) => visibility = Some(Visibility::Public),
_ => {
bail!("Unhandled Member attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled Member child {:?}", child.kind);
}
let name = name.ok_or_else(|| anyhow!("Member without name: {:?}", tag))?;
let member_type = member_type.ok_or_else(|| anyhow!("Member without type: {:?}", tag))?;
let offset = offset.ok_or_else(|| anyhow!("Member without offset: {:?}", tag))?;
let bit = match (bit_size, bit_offset) {
(Some(bit_size), Some(bit_offset)) => Some(BitData { bit_size, bit_offset }),
(None, None) => None,
_ => bail!("Mismatched bit attributes in Member: {tag:?}"),
};
let visibility = visibility.unwrap_or(Visibility::Public);
Ok(StructureMember {
name: name.clone(),
kind: member_type,
offset,
bit,
visibility,
byte_size,
})
}
fn process_structure_tag(tags: &TagMap, tag: &Tag) -> Result<StructureType> {
ensure!(
matches!(tag.kind, TagKind::StructureType | TagKind::ClassType),
"{:?} is not a Structure type tag",
tag.kind
);
let mut name = None;
let mut byte_size = None;
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),
_ => {
bail!("Unhandled structure attribute {:?}", attr);
}
}
}
let mut members = Vec::new();
let mut bases = Vec::new();
for child in tag.children(tags) {
match child.kind {
TagKind::Inheritance => bases.push(process_inheritance_tag(tags, child)?),
TagKind::Member => members.push(process_structure_member_tag(tags, child)?),
TagKind::Typedef => {
// TODO?
// info!("Structure {:?} Typedef: {:?}", name, child);
}
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
} else {
StructureKind::Struct
},
name,
byte_size,
members,
bases,
})
}
fn process_array_tag(tags: &TagMap, tag: &Tag) -> Result<ArrayType> {
ensure!(tag.kind == TagKind::ArrayType, "{:?} is not an ArrayType tag", tag.kind);
let mut subscr_data = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::SubscrData, AttributeValue::Block(data)) => {
subscr_data =
Some(process_array_subscript_data(data).with_context(|| {
format!("Failed to process SubscrData for tag: {:?}", tag)
})?)
}
_ => {
bail!("Unhandled ArrayType attribute {:?}", attr)
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled ArrayType child {:?}", child.kind);
}
let (element_type, dimensions) =
subscr_data.ok_or_else(|| anyhow!("ArrayType without SubscrData: {:?}", tag))?;
Ok(ArrayType { element_type: Box::from(element_type), dimensions })
}
fn process_array_subscript_data(data: &[u8]) -> Result<(Type, Vec<ArrayDimension>)> {
let mut element_type = None;
let mut dimensions = Vec::new();
let mut data = data;
while !data.is_empty() {
let format = SubscriptFormat::try_from(
data.first().cloned().ok_or_else(|| anyhow!("Empty SubscrData"))?,
)
.context("Unknown array subscript format")?;
data = &data[1..];
match format {
SubscriptFormat::FundTypeConstConst => {
let index_type = FundType::try_from(u16::from_be_bytes(data[..2].try_into()?))
.context("Invalid fundamental type ID")?;
let low_bound = u32::from_be_bytes(data[2..6].try_into()?);
ensure!(low_bound == 0, "Invalid array low bound {low_bound}, expected 0");
let high_bound = u32::from_be_bytes(data[6..10].try_into()?);
data = &data[10..];
dimensions.push(ArrayDimension {
index_type: Type { kind: TypeKind::Fundamental(index_type), modifiers: vec![] },
// u32::MAX will wrap to 0, meaning unbounded
size: NonZeroU32::new(high_bound.wrapping_add(1)),
});
}
SubscriptFormat::ElementType => {
let mut cursor = Cursor::new(data);
let type_attr = read_attribute(&mut cursor)?;
element_type = Some(process_type(&type_attr)?);
data = &data[cursor.position() as usize..];
}
_ => bail!("Unhandled subscript format type {:?}", format),
}
}
let element_type = element_type.ok_or_else(|| anyhow!("ArrayType without ElementType"))?;
Ok((element_type, dimensions))
}
fn process_enumeration_tag(tags: &TagMap, tag: &Tag) -> Result<EnumerationType> {
ensure!(tag.kind == TagKind::EnumerationType, "{:?} is not an EnumerationType tag", tag.kind);
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::ElementList, AttributeValue::Block(data)) => {
let mut cursor = Cursor::new(data);
while cursor.position() < data.len() as u64 {
let value = i32::from_reader(&mut cursor, Endian::Big)?;
let name = read_string(&mut cursor)?;
members.push(EnumerationMember { name, value });
}
}
_ => {
bail!("Unhandled EnumerationType attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled EnumerationType child {:?}", child.kind);
}
let byte_size =
byte_size.ok_or_else(|| anyhow!("EnumerationType without ByteSize: {:?}", tag))?;
Ok(EnumerationType { name, byte_size, members })
}
fn process_union_tag(tags: &TagMap, tag: &Tag) -> Result<UnionType> {
ensure!(tag.kind == TagKind::UnionType, "{:?} is not a UnionType tag", tag.kind);
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),
_ => {
bail!("Unhandled UnionType attribute {:?}", attr);
}
}
}
for child in tag.children(tags) {
match child.kind {
TagKind::Member => members.push(process_structure_member_tag(tags, child)?),
kind => bail!("Unhandled UnionType child {:?}", kind),
}
}
let byte_size = byte_size.ok_or_else(|| anyhow!("UnionType without ByteSize: {:?}", tag))?;
Ok(UnionType { name, byte_size, members })
}
fn process_subroutine_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineType> {
ensure!(
matches!(
tag.kind,
TagKind::SubroutineType | TagKind::GlobalSubroutine | TagKind::Subroutine
),
"{:?} is not a Subroutine tag",
tag.kind
);
let mut name = None;
let mut mangled_name = None;
let mut return_type = None;
let mut prototyped = false;
let mut parameters = Vec::new();
let mut var_args = false;
let mut references = Vec::new();
let mut member_of = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(AttributeKind::MwMangled, AttributeValue::String(s)) => mangled_name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => return_type = Some(process_type(attr)?),
(AttributeKind::Prototyped, _) => prototyped = true,
(AttributeKind::LowPc, _) | (AttributeKind::HighPc, _) => {
// TODO?
}
(AttributeKind::MwGlobalRef, &AttributeValue::Reference(key)) => {
references.push(key);
}
(AttributeKind::ReturnAddr, AttributeValue::Block(_block)) => {
// let location = process_variable_location(block)?;
// info!("ReturnAddr: {}", location);
}
(AttributeKind::Member, &AttributeValue::Reference(key)) => {
member_of = Some(key);
}
_ => {
bail!("Unhandled SubroutineType attribute {:?}", attr);
}
}
}
let mut variables = Vec::new();
for child in tag.children(tags) {
ensure!(!var_args, "{:?} after UnspecifiedParameters", child.kind);
match child.kind {
TagKind::FormalParameter => {
parameters.push(process_subroutine_parameter_tag(tags, child)?)
}
TagKind::UnspecifiedParameters => var_args = true,
TagKind::LocalVariable => variables.push(process_local_variable_tag(tags, child)?),
kind => bail!("Unhandled SubroutineType child {:?}", kind),
}
}
let return_type = return_type
.unwrap_or_else(|| Type { kind: TypeKind::Fundamental(FundType::Void), modifiers: vec![] });
Ok(SubroutineType {
name,
mangled_name,
return_type,
parameters,
var_args,
prototyped,
references,
member_of,
variables,
})
}
fn process_subroutine_parameter_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineParameter> {
ensure!(tag.kind == TagKind::FormalParameter, "{:?} is not a FormalParameter tag", tag.kind);
let mut name = None;
let mut kind = None;
let mut location = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => kind = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
location = Some(process_variable_location(block)?)
}
(AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => {
// TODO?
// info!("MwDwarf2Location: {:?} in {:?}", block, tag);
}
_ => {
bail!("Unhandled SubroutineParameter attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled SubroutineParameter child {:?}", child.kind);
}
let kind = kind.ok_or_else(|| anyhow!("SubroutineParameter without type: {:?}", tag))?;
Ok(SubroutineParameter { name, kind, location })
}
fn process_local_variable_tag(tags: &TagMap, tag: &Tag) -> Result<SubroutineVariable> {
ensure!(tag.kind == TagKind::LocalVariable, "{:?} is not a LocalVariable tag", tag.kind);
let mut name = None;
let mut kind = None;
let mut location = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => kind = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
location = Some(process_variable_location(block)?)
}
(AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => {
// TODO?
// info!("MwDwarf2Location: {:?} in {:?}", block, tag);
}
_ => {
bail!("Unhandled LocalVariable attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled LocalVariable child {:?}", child.kind);
}
let kind = kind.ok_or_else(|| anyhow!("LocalVariable without type: {:?}", tag))?;
Ok(SubroutineVariable { name, kind, location })
}
fn process_ptr_to_member_tag(tags: &TagMap, tag: &Tag) -> Result<PtrToMemberType> {
ensure!(tag.kind == TagKind::PtrToMemberType, "{:?} is not a PtrToMemberType tag", tag.kind);
let mut kind = None;
let mut containing_type = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => kind = Some(process_type(attr)?),
(AttributeKind::ContainingType, &AttributeValue::Reference(key)) => {
containing_type = Some(key)
}
_ => {
bail!("Unhandled PtrToMemberType attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled PtrToMemberType child {:?}", child.kind);
}
let kind = kind.ok_or_else(|| anyhow!("PtrToMemberType without type: {:?}", tag))?;
let containing_type = containing_type
.ok_or_else(|| anyhow!("PtrToMemberType without containing type: {:?}", tag))?;
Ok(PtrToMemberType { kind, containing_type })
}
pub fn ud_type(tags: &TagMap, tag: &Tag) -> Result<UserDefinedType> {
match tag.kind {
TagKind::ArrayType => Ok(UserDefinedType::Array(process_array_tag(tags, tag)?)),
TagKind::StructureType | TagKind::ClassType => {
Ok(UserDefinedType::Structure(process_structure_tag(tags, tag)?))
}
TagKind::EnumerationType => {
Ok(UserDefinedType::Enumeration(process_enumeration_tag(tags, tag)?))
}
TagKind::UnionType => Ok(UserDefinedType::Union(process_union_tag(tags, tag)?)),
TagKind::SubroutineType | TagKind::GlobalSubroutine | TagKind::Subroutine => {
Ok(UserDefinedType::Subroutine(process_subroutine_tag(tags, tag)?))
}
TagKind::PtrToMemberType => {
Ok(UserDefinedType::PtrToMember(process_ptr_to_member_tag(tags, tag)?))
}
kind => Err(anyhow!("Unhandled user defined type {kind:?}")),
}
}
pub fn process_modifiers(block: &[u8]) -> Result<Vec<Modifier>> {
let mut out = Vec::with_capacity(block.len());
for &b in block {
out.push(Modifier::try_from(b)?);
}
Ok(out)
}
pub fn process_type(attr: &Attribute) -> Result<Type> {
match (attr.kind, &attr.value) {
(AttributeKind::FundType, &AttributeValue::Data2(type_id)) => {
let fund_type = FundType::try_from(type_id)
.with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?;
Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers: vec![] })
}
(AttributeKind::ModFundType, AttributeValue::Block(ops)) => {
let type_id = u16::from_be_bytes(ops[ops.len() - 2..].try_into()?);
let fund_type = FundType::try_from(type_id)
.with_context(|| format!("Invalid fundamental type ID '{}'", type_id))?;
let modifiers = process_modifiers(&ops[..ops.len() - 2])?;
Ok(Type { kind: TypeKind::Fundamental(fund_type), modifiers })
}
(AttributeKind::UserDefType, &AttributeValue::Reference(key)) => {
Ok(Type { kind: TypeKind::UserDefined(key), modifiers: vec![] })
}
(AttributeKind::ModUDType, AttributeValue::Block(ops)) => {
let ud_ref = u32::from_be_bytes(ops[ops.len() - 4..].try_into()?);
let modifiers = process_modifiers(&ops[..ops.len() - 4])?;
Ok(Type { kind: TypeKind::UserDefined(ud_ref), modifiers })
}
_ => Err(anyhow!("Invalid type attribute {:?}", attr)),
}
}
pub fn process_root_tag(tags: &TagMap, tag: &Tag) -> Result<TagType> {
match tag.kind {
TagKind::Typedef => Ok(TagType::Typedef(process_typedef_tag(tags, tag)?)),
TagKind::GlobalVariable | TagKind::LocalVariable => {
Ok(TagType::Variable(process_variable_tag(tags, tag)?))
}
TagKind::StructureType
| TagKind::ArrayType
| TagKind::EnumerationType
| TagKind::UnionType
| TagKind::ClassType
| TagKind::SubroutineType
| TagKind::GlobalSubroutine
| TagKind::Subroutine
| TagKind::PtrToMemberType => Ok(TagType::UserDefined(ud_type(tags, tag)?)),
kind => Err(anyhow!("Unhandled root tag type {:?}", kind)),
}
}
/// Logic to skip uninteresting tags
pub fn should_skip_tag(tag_type: &TagType) -> bool {
match tag_type {
TagType::Variable(_) => false,
TagType::Typedef(_) => false,
TagType::UserDefined(t) => !t.is_definition(),
}
}
pub fn tag_type_string(tags: &TagMap, typedefs: &TypedefMap, tag_type: &TagType) -> Result<String> {
match tag_type {
TagType::Typedef(t) => typedef_string(tags, typedefs, t),
TagType::Variable(v) => variable_string(tags, typedefs, v, true),
TagType::UserDefined(ud) => {
let ud_str = ud_type_def(tags, typedefs, ud)?;
match ud {
UserDefinedType::Structure(_)
| UserDefinedType::Enumeration(_)
| UserDefinedType::Union(_) => Ok(format!("{};", ud_str)),
_ => Ok(ud_str),
}
}
}
}
fn typedef_string(tags: &TagMap, typedefs: &TypedefMap, typedef: &TypedefTag) -> Result<String> {
let ts = type_string(tags, typedefs, &typedef.kind, true)?;
Ok(format!("typedef {} {}{};", ts.prefix, typedef.name, ts.suffix))
}
fn variable_string(
tags: &TagMap,
typedefs: &TypedefMap,
variable: &VariableTag,
include_extra: bool,
) -> Result<String> {
let ts = type_string(tags, typedefs, &variable.kind, include_extra)?;
let mut out = if variable.local { "static ".to_string() } else { String::new() };
out.push_str(&ts.prefix);
out.push(' ');
out.push_str(variable.name.as_deref().unwrap_or("[unknown]"));
out.push_str(&ts.suffix);
match &variable.address {
Some(addr) => out.push_str(&format!(" : {:#010X}", addr)),
None => {}
}
out.push(';');
if include_extra {
let size = variable.kind.size(tags)?;
out.push_str(&format!(" // size: {:#X}", size));
}
Ok(out)
}
fn process_typedef_tag(tags: &TagMap, tag: &Tag) -> Result<TypedefTag> {
ensure!(tag.kind == TagKind::Typedef, "{:?} is not a typedef tag", tag.kind);
let mut name = None;
let mut kind = None;
for attr in &tag.attributes {
match (attr.kind, &attr.value) {
(AttributeKind::Sibling, _) => {}
(AttributeKind::Name, AttributeValue::String(s)) => name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => kind = Some(process_type(attr)?),
_ => {
bail!("Unhandled Typedef attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled Typedef child {:?}", child.kind);
}
let name = name.ok_or_else(|| anyhow!("Typedef without Name: {:?}", tag))?;
let kind = kind.ok_or_else(|| anyhow!("Typedef without Type: {:?}", tag))?;
Ok(TypedefTag { name, kind })
}
fn process_variable_tag(tags: &TagMap, tag: &Tag) -> Result<VariableTag> {
ensure!(
matches!(tag.kind, TagKind::GlobalVariable | TagKind::LocalVariable),
"{:?} is not a variable tag",
tag.kind
);
let mut name = None;
let mut mangled_name = None;
let mut kind = 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::MwMangled, AttributeValue::String(s)) => mangled_name = Some(s.clone()),
(
AttributeKind::FundType
| AttributeKind::ModFundType
| AttributeKind::UserDefType
| AttributeKind::ModUDType,
_,
) => kind = Some(process_type(attr)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
address = Some(process_address(block)?)
}
_ => {
bail!("Unhandled Variable attribute {:?}", attr);
}
}
}
if let Some(child) = tag.children(tags).first() {
bail!("Unhandled Variable child {:?}", child.kind);
}
let kind = kind.ok_or_else(|| anyhow!("Variable without Type: {:?}", tag))?;
let local = tag.kind == TagKind::LocalVariable;
Ok(VariableTag { name, mangled_name, kind, address, local })
}