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 { 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), 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, } pub type TagMap = BTreeMap; pub type TypedefMap = BTreeMap>; 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 { match self.attribute(kind) { Some(Attribute { value: AttributeValue::Address(addr), .. }) => Some(*addr), _ => None, } } #[inline] pub fn reference_attribute(&self, kind: AttributeKind) -> Option { 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 { match self.attribute(kind) { Some(Attribute { value: AttributeValue::Data4(value), .. }) => Some(*value), _ => None, } } #[inline] pub fn data2_attribute(&self, kind: AttributeKind) -> Option { 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(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 tag = read_tag(reader)?; tags.insert(position as u32, tag); } Ok(tags) } #[allow(unused)] pub fn read_aranges_section(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(reader: &mut R) -> Result 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(reader: &mut R) -> Result 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(reader: &mut R) -> Result 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, } #[derive(Debug, Clone)] pub struct ArrayType { pub element_type: Box, pub dimensions: Vec, } #[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, pub visibility: Visibility, pub byte_size: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StructureKind { Struct, Class, } #[derive(Debug, Clone)] pub struct StructureType { pub kind: StructureKind, pub name: Option, pub byte_size: u32, pub members: Vec, pub bases: Vec, } #[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, pub byte_size: u32, pub members: Vec, } #[derive(Debug, Clone)] pub struct UnionType { pub name: Option, pub byte_size: u32, pub members: Vec, } #[derive(Debug, Clone)] pub struct SubroutineParameter { pub name: Option, pub kind: Type, pub location: Option, } #[derive(Debug, Clone)] pub struct SubroutineVariable { pub name: Option, pub kind: Type, pub location: Option, } #[derive(Debug, Clone)] pub struct SubroutineType { pub name: Option, pub mangled_name: Option, pub return_type: Type, pub parameters: Vec, pub var_args: bool, pub prototyped: bool, pub references: Vec, pub member_of: Option, pub variables: Vec, } #[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, pub mangled_name: Option, pub kind: Type, pub address: Option, 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 { 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, } impl Type { pub fn size(&self, tags: &TagMap) -> Result { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, ¶meter.kind, true)?; if let Some(name) = ¶meter.name { write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?; } else { write!(parameters, "{}{}", ts.prefix, ts.suffix)?; } if let Some(location) = ¶meter.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 { 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, ¶meter.kind, true)?; if let Some(name) = ¶meter.name { write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?; } else { write!(parameters, "{}{}", ts.prefix, ts.suffix)?; } if let Some(location) = ¶meter.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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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)> { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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> { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 }) }