decomp-toolkit/src/util/dwarf.rs

1182 lines
41 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 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 {
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,
}
impl FundType {
fn size(self) -> Result<u32> {
Ok(match self {
FundType::Char | FundType::SignedChar | FundType::UnsignedChar | FundType::Boolean => 1,
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 => 8,
FundType::Void => 0,
FundType::ExtPrecFloat
| FundType::Complex
| FundType::DblPrecComplex
| FundType::ExtPrecComplex
| FundType::Label => bail!("Unhandled fundamental type {self:?}"),
})
}
}
#[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
}
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
Unknown200 = 0x2000 | (FormKind::String as u16),
MwVariableRef = 0x2020 | (FormKind::Ref as u16),
Unknown234 = 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 tags.range(self.key + 1..).next() {
Some(child) => child,
None => return children,
};
if child.kind == TagKind::Padding {
return children;
}
loop {
if let Some(end) = sibling {
if child.key == end.key {
break;
}
}
children.push(child);
match child.next_sibling(tags) {
Some(next) => child = next,
None => break,
}
}
children
}
pub fn next_sibling<'a>(&self, tags: &'a TagMap) -> Option<&'a Tag> {
if let Some(key) = self.reference_attribute(AttributeKind::Sibling) {
if let Some(next) = tags.get(&key) {
if next.kind != TagKind::Padding {
return Some(next);
}
}
}
None
}
}
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 byte_size: u32,
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>,
}
#[derive(Debug, Clone)]
pub struct StructureType {
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<StructureMember>,
}
#[derive(Debug, Clone)]
pub struct EnumerationMember {
pub name: String,
pub value: u32,
}
#[derive(Debug, Clone)]
pub struct EnumerationType {
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<EnumerationMember>,
}
#[derive(Debug, Clone)]
pub struct UnionMember {
pub name: String,
pub kind: Type,
}
#[derive(Debug, Clone)]
pub struct UnionType {
pub name: Option<String>,
pub byte_size: u32,
pub members: Vec<UnionMember>,
}
#[derive(Debug, Clone)]
pub struct SubroutineParameter {
pub name: Option<String>,
pub kind: Type,
}
#[derive(Debug, Clone)]
pub struct SubroutineType {
pub return_type: Type,
pub parameters: Vec<SubroutineParameter>,
pub var_args: bool,
pub prototyped: bool,
}
#[derive(Debug, Clone)]
pub enum UserDefinedType {
Array(ArrayType),
Structure(StructureType),
Enumeration(EnumerationType),
Union(UnionType),
Subroutine(SubroutineType),
}
impl UserDefinedType {
pub fn is_definition(&self) -> bool {
match self {
UserDefinedType::Array(_) | UserDefinedType::Subroutine(_) => false,
UserDefinedType::Structure(t) => t.name.is_some(),
UserDefinedType::Enumeration(t) => t.name.is_some(),
UserDefinedType::Union(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,
})
}
}
#[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() {
str.prefix.push_str(" (*");
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)]
pub struct TypeString {
pub prefix: String,
pub suffix: String,
}
impl Display for TypeString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.prefix, self.suffix)
}
}
pub fn type_string(tags: &TagMap, typedefs: &TypedefMap, t: &Type) -> Result<TypeString> {
let str = match t.kind {
TypeKind::Fundamental(ft) => {
TypeString { prefix: fund_type_string(ft)?.to_string(), suffix: String::new() }
}
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(), suffix: String::new() }
} 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)?)?
}
}
};
apply_modifiers(str, &t.modifiers)
}
pub fn ud_type_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &UserDefinedType,
) -> Result<TypeString> {
Ok(match t {
UserDefinedType::Array(t) => {
let mut out = type_string(tags, typedefs, t.element_type.as_ref())?;
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)?
);
match dim.size {
None => out.suffix.insert_str(0, "[]"),
Some(size) => out.suffix = format!("[{}]{}", size, out.suffix),
};
}
out
}
UserDefinedType::Structure(t) => {
let struct_str = if let Some(name) = t.name.as_ref() {
format!("struct {}", name)
} else {
struct_def_string(tags, typedefs, t)?
};
TypeString { prefix: struct_str, suffix: String::new() }
}
UserDefinedType::Enumeration(t) => {
let struct_str = if let Some(name) = t.name.as_ref() {
format!("enum {}", name)
} else {
enum_def_string(t)?
};
TypeString { prefix: struct_str, suffix: String::new() }
}
UserDefinedType::Union(t) => {
let struct_str = if let Some(name) = t.name.as_ref() {
format!("union {}", name)
} else {
union_def_string(tags, typedefs, t)?
};
TypeString { prefix: struct_str, suffix: String::new() }
}
UserDefinedType::Subroutine(t) => {
let mut out = type_string(tags, typedefs, &t.return_type)?;
let mut parameters = String::new();
if t.parameters.is_empty() && 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)?;
if let Some(name) = &parameter.name {
write!(parameters, "{} {}{}", ts.prefix, name, ts.suffix)?;
} else {
write!(parameters, "{}{}", ts.prefix, ts.suffix)?;
}
}
}
out.suffix = format!("({}){}", parameters, out.suffix);
out
}
})
}
pub fn ud_type_def(tags: &TagMap, typedefs: &TypedefMap, t: &UserDefinedType) -> Result<String> {
match t {
UserDefinedType::Array(_) | UserDefinedType::Subroutine(_) => {
Err(anyhow!("Can't define non-definition type"))
}
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)?),
}
}
pub fn struct_def_string(
tags: &TagMap,
typedefs: &TypedefMap,
t: &StructureType,
) -> Result<String> {
let mut out = match t.name.as_ref() {
Some(name) => format!("struct {} {{\n", name),
None => "struct {\n".to_string(),
};
writeln!(out, "\t// total size: {:#X}", t.byte_size)?;
for member in &t.members {
let ts = type_string(tags, typedefs, &member.kind)?;
write!(out, "\t{} {}{}", ts.prefix, member.name, ts.suffix)?;
if let Some(bit) = &member.bit {
write!(out, " : {}", bit.bit_size)?;
}
writeln!(out, "; // offset {:#X}, size {:#X}", member.offset, member.kind.size(tags)?)?;
}
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().rev() {
writeln!(out, "\t{} = {},", 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(),
};
for member in t.members.iter().rev() {
let ts = type_string(tags, typedefs, &member.kind)?;
writeln!(out, "\t{} {}{};", ts.prefix, member.name, ts.suffix)?;
}
write!(out, "}}")?;
Ok(out)
}
pub fn fund_type_string(ft: FundType) -> Result<&'static str> {
Ok(match ft {
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 {ft:?}"),
FundType::LongLong => "long long",
FundType::SignedLongLong => "signed long long",
FundType::UnsignedLongLong => "unsigned long long",
})
}
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 {
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))
}
}
pub fn ud_type(tags: &TagMap, tag: &Tag) -> Result<UserDefinedType> {
match tag.kind {
TagKind::ArrayType => {
let mut data = tag
.block_attribute(AttributeKind::SubscrData)
.ok_or_else(|| anyhow!("ArrayType without SubscrData"))?;
let mut element_type = None;
let mut dimensions = Vec::new();
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),
}
}
if let Some(element_type) = element_type {
Ok(UserDefinedType::Array(ArrayType {
element_type: Box::from(element_type),
dimensions,
}))
} else {
Err(anyhow!("Array type without element type"))
}
}
TagKind::StructureType => {
let byte_size = tag.data4_attribute(AttributeKind::ByteSize).unwrap_or_default();
//.ok_or_else(|| {
// anyhow!("StructureType without ByteSize: {:?}", tag)
// })?
let name = tag.string_attribute(AttributeKind::Name).cloned();
let mut members = Vec::new();
for child in tag.children(tags) {
ensure!(
child.kind == TagKind::Member,
"Unhandled StructureType child {:?}",
child.kind
);
let member_name = child
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("Structure member without name: {:?}", child))?;
let member_type = process_type(
child
.type_attribute()
.ok_or_else(|| anyhow!("Structure member without type: {:?}", child))?,
)?;
if let Some(member_of) = child.reference_attribute(AttributeKind::Member) {
ensure!(
member_of == tag.key,
"Structure member mismatch: {} != {}",
member_of,
tag.key
);
}
let location = child
.block_attribute(AttributeKind::Location)
.ok_or_else(|| anyhow!("Structure member without location: {:?}", child))?;
let offset = process_offset(location)?;
let bit = match (
child.data4_attribute(AttributeKind::ByteSize),
child.data4_attribute(AttributeKind::BitSize),
child.data2_attribute(AttributeKind::BitOffset),
) {
(Some(byte_size), Some(bit_size), Some(bit_offset)) => {
Some(BitData { byte_size, bit_size, bit_offset })
}
(None, None, None) => None,
_ => bail!("Mismatched bit attributes in structure member: {child:?}"),
};
members.push(StructureMember {
name: member_name.clone(),
kind: member_type,
offset,
bit,
});
}
Ok(UserDefinedType::Structure(StructureType { name, byte_size, members }))
}
TagKind::EnumerationType => {
let byte_size = tag
.data4_attribute(AttributeKind::ByteSize)
.ok_or_else(|| anyhow!("EnumerationType without ByteSize"))?;
let data = tag
.block_attribute(AttributeKind::ElementList)
.ok_or_else(|| anyhow!("EnumerationType without ElementList"))?;
let name = tag.string_attribute(AttributeKind::Name).cloned();
let mut members = Vec::new();
let mut cursor = Cursor::new(data);
while cursor.position() < data.len() as u64 {
let value = u32::from_reader(&mut cursor, Endian::Big)?;
let name = read_string(&mut cursor)?;
members.push(EnumerationMember { name, value });
}
Ok(UserDefinedType::Enumeration(EnumerationType { name, byte_size, members }))
}
TagKind::UnionType => {
let byte_size = tag
.data4_attribute(AttributeKind::ByteSize)
.ok_or_else(|| anyhow!("UnionType without ByteSize"))?;
let name = tag.string_attribute(AttributeKind::Name).cloned();
let mut members = Vec::new();
for child in tag.children(tags) {
ensure!(
child.kind == TagKind::Member,
"Unhandled UnionType child {:?}",
child.kind
);
let member_name = child
.string_attribute(AttributeKind::Name)
.ok_or_else(|| anyhow!("Structure member without name: {:?}", child))?;
let member_type = process_type(
child
.type_attribute()
.ok_or_else(|| anyhow!("Structure member without type: {:?}", child))?,
)?;
if let Some(member_of) = child.reference_attribute(AttributeKind::Member) {
ensure!(
member_of == tag.key,
"Structure member mismatch: {member_of} != {}",
tag.key
);
}
let location = child
.block_attribute(AttributeKind::Location)
.ok_or_else(|| anyhow!("Structure member without location: {:?}", child))?;
ensure!(process_offset(location)? == 0, "Union member at non-zero offset");
members.push(UnionMember { name: member_name.clone(), kind: member_type });
}
Ok(UserDefinedType::Union(UnionType { name, byte_size, members }))
}
TagKind::SubroutineType | TagKind::GlobalSubroutine | TagKind::Subroutine => {
let return_type = match tag.type_attribute() {
Some(attr) => process_type(attr)?,
None => Type { kind: TypeKind::Fundamental(FundType::Void), modifiers: vec![] },
};
let prototyped = tag.string_attribute(AttributeKind::Prototyped).is_some();
let mut parameters = Vec::new();
let mut var_args = false;
for child in tag.children(tags) {
if tag.kind != TagKind::SubroutineType
&& child.kind != TagKind::FormalParameter
&& child.kind != TagKind::UnspecifiedParameters
{
break;
}
ensure!(!var_args, "{:?} after UnspecifiedParameters", child.kind);
match child.kind {
TagKind::FormalParameter => {
let parameter_name = child.string_attribute(AttributeKind::Name).cloned();
let parameter_type =
process_type(child.type_attribute().ok_or_else(|| {
anyhow!("FormalParameter without type: {:?}", child)
})?)?;
parameters.push(SubroutineParameter {
name: parameter_name,
kind: parameter_type,
});
}
TagKind::UnspecifiedParameters => {
var_args = true;
}
_ => bail!("Unhandled SubroutineType child {:?}", child.kind),
}
}
Ok(UserDefinedType::Subroutine(SubroutineType {
return_type,
parameters,
var_args,
prototyped,
}))
}
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 {
AttributeKind::FundType => {
if let AttributeValue::Data2(type_id) = attr.value {
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![] })
} else {
Err(anyhow!("Invalid value type for FundType"))
}
}
AttributeKind::ModFundType => {
if let AttributeValue::Block(ops) = &attr.value {
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 })
} else {
Err(anyhow!("Invalid value type for ModFundType"))
}
}
AttributeKind::UserDefType => {
if let AttributeValue::Reference(key) = attr.value {
Ok(Type { kind: TypeKind::UserDefined(key), modifiers: vec![] })
} else {
Err(anyhow!("Invalid value type for UserDefType"))
}
}
AttributeKind::ModUDType => {
if let AttributeValue::Block(ops) = &attr.value {
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 })
} else {
Err(anyhow!("Invalid value type for ModUDType"))
}
}
_ => Err(anyhow!("Invalid type attribute kind {:?}", attr.kind)),
}
}