Skip to content

Instantly share code, notes, and snippets.

@mikeschinkel
Last active February 19, 2025 06:41
Show Gist options
  • Save mikeschinkel/6396526d22fa64b91222c777ba4f3ca6 to your computer and use it in GitHub Desktop.
Save mikeschinkel/6396526d22fa64b91222c777ba4f3ca6 to your computer and use it in GitHub Desktop.
// readType reads a type from r at off of name. It adds types to the
// type cache, appends new typedef types to typedefs, and computes the
// sizes of types. Callers should pass nil for typedefs; this is used
// for internal recursion.
func (d *Data) readType(name string, r typeReader, off Offset, typeCache map[Offset]Type, fixups *typeFixer) (t Type, err error) {
var e *Entry
var ok bool
var addressSize int
var typ Type
var nextDepth int
var next entryChildFunc
var typeOf entryTypeFunc
if t, ok = typeCache[off]; ok {
goto end
}
r.Seek(off)
e, err = r.Next()
if err != nil {
goto end
}
addressSize = r.AddressSize()
if e == nil || e.Offset != off {
err = DecodeError{name, off, "no type at offset"}
goto end
}
// If this is the root of the recursion, prepare to resolve
// typedef sizes and perform other fixups once the recursion is
// done. This must be done after the type graph is constructed
// because it may need to resolve cycles in a different order than
// readType encounters them.
if fixups == nil {
var fixer typeFixer
defer func() {
fixer.apply()
}()
fixups = &fixer
}
// Parse type from Entry.
// Must always set typeCache[off] before calling
// d.readType recursively, to handle circular types correctly.
// Get next child; set err if error happens.
next = func() *Entry {
return e.getChild(name, r, &nextDepth)
}
// Get Type referred to by Entry's AttrType field.
// Set err if error happens. Not having a type is an error.
typeOf = func(e *Entry) (Type, error) {
return e.getType(d, name, r, typeCache, fixups)
}
switch e.Tag {
case TagArrayType:
// Multi-dimensional array. (DWARF v2 §5.4)
typ, err = e.readArrayTag(name, off, typeCache, next, typeOf)
case TagBaseType:
// Basic type. (DWARF v2 §5.1)
typ, err = e.readBaseTag(name, off, typeCache)
case TagClassType, TagStructType, TagUnionType:
// Structure, union, or class type. (DWARF v2 §5.5)
typ, err = e.readClassStructUnionTag(d, name, off, typeCache, fixups, next, typeOf)
case TagConstType, TagVolatileType, TagRestrictType:
// Type modifier (DWARF v2 §5.2)
typ, err = e.readConstVolatileRestrictTag(d, name, off, typeCache, fixups, next, typeOf)
case TagEnumerationType:
// Enumeration type (DWARF v2 §5.6)
typ, err = e.readEnumerationTag(off, typeCache, next)
case TagPointerType:
// Type modifier (DWARF v2 §5.2)
typ, err = e.readPointerTag(off, typeCache, typeOf)
case TagSubroutineType:
// Subroutine type. (DWARF v2 §5.7)
typ, err = e.readSubroutineTag(off, typeCache, next, typeOf)
case TagTypedef:
// Typedef (DWARF v2 §5.3)
typ, err = e.readTypedefTag(off, typeCache, typeOf)
case TagUnspecifiedType:
// Unspecified type (DWARF v3 §5.2)
typ, err = e.readUnspecifiedTypeTag(off, typeCache)
default:
// This is some other type DIE that we're currently not
// equipped to handle.
typ, err = e.readUnsupportedTypeTag(off, typeCache)
}
if err != nil {
goto end
}
typ, fixups = e.fixupByteSize(typ, fixups, addressSize)
end:
if err != nil {
// If the parse fails, take the type out of the cache
// so that the next call with this offset doesn't hit
// the cache and return success.
delete(typeCache, off)
}
return typ, err
}
func zeroArray(t *Type) {
at := (*t).(*ArrayType)
if at.Type.Size() == 0 {
return
}
// Make a copy to avoid invalidating typeCache.
tt := *at
tt.Count = 0
*t = &tt
}
type entryChildFunc func() *Entry
type entryTypeFunc func(e *Entry) (Type, error)
type offsetMap = map[Offset]Type
func (e *Entry) getChild(name string, r typeReader, nextDepth *int) (kid *Entry) {
var err error
if !e.Children {
goto end
}
// Only return direct children.
// Skip over composite entries that happen to be nested
// inside this one. Most DWARF generators wouldn't generate
// such a thing, but clang does.
// See golang.org/issue/6472.
for {
kid, err = r.Next()
if err != nil {
goto end
}
if kid == nil {
err = DecodeError{name, r.offset(), "unexpected end of DWARF entries"}
goto end
}
if kid.Tag == 0 {
if *nextDepth > 0 {
*nextDepth--
continue
}
kid = nil
goto end
}
if kid.Children {
*nextDepth++
}
if *nextDepth > 0 {
continue
}
}
end:
return kid
}
func (e *Entry) getType(d *Data, name string, r typeReader, typeCache offsetMap, fixups *typeFixer) (t Type, err error) {
tval := e.Val(AttrType)
switch toff := tval.(type) {
case Offset:
t, err = d.readType(name, r.clone(), toff, typeCache, fixups)
if err != nil {
t = nil // just in case, probably not needed
goto end
}
case uint64:
t, err = d.sigToType(toff)
if err != nil {
t = nil // just in case, probably not needed
goto end
}
default:
// It appears that no Type means "void".
t = new(VoidType)
goto end
}
end:
return t, err
}
func (e *Entry) readArrayTag(name string, off Offset, typeCache offsetMap, next entryChildFunc, typeOf entryTypeFunc) (typ Type, err error) {
var dims []int64
// Multi-dimensional array. (DWARF v2 §5.4)
// Attributes:
// AttrType:subtype [required]
// AttrStrideSize: size in bits of each element of the array
// AttrByteSize: size of entire array
// Children:
// TagSubrangeType or TagEnumerationType giving one dimension.
// dimensions are in left to right order.
t := new(ArrayType)
typ = t
typeCache[off] = t
t.Type, err = typeOf(e)
if err != nil {
goto end
}
t.StrideBitSize, _ = e.Val(AttrStrideSize).(int64)
// Accumulate dimensions,
for kid := next(); kid != nil; kid = next() {
// TODO(rsc): Can also be TagEnumerationType
// but haven't seen that in the wild yet.
switch kid.Tag {
case TagSubrangeType:
count, ok := kid.Val(AttrCount).(int64)
if !ok {
// Old binaries may have an upper bound instead.
count, ok = kid.Val(AttrUpperBound).(int64)
if ok {
count++ // Length is one more than upper bound.
} else if len(dims) == 0 {
count = -1 // As in x[].
}
}
dims = append(dims, count)
case TagEnumerationType:
err = DecodeError{name, kid.Offset, "cannot handle enumeration type as array bound"}
goto end
}
}
if len(dims) == 0 {
// LLVM generates this for x[].
dims = []int64{-1}
}
t.Count = dims[0]
for i := len(dims) - 1; i >= 1; i-- {
t.Type = &ArrayType{Type: t.Type, Count: dims[i]}
}
end:
return typ, err
}
func (e *Entry) readBaseTag(name string, off Offset, typeCache offsetMap) (typ Type, err error) {
var t *BasicType
var haveBitOffset, haveDataBitOffset bool
type basicGetter interface {
Basic() *BasicType
}
// Basic type. (DWARF v2 §5.1)
// Attributes:
// AttrName: name of base type in programming language of the compilation unit [required]
// AttrEncoding: encoding value for type (encFloat etc) [required]
// AttrByteSize: size of type in bytes [required]
// AttrBitOffset: bit offset of value within containing storage unit
// AttrDataBitOffset: bit offset of value within containing storage unit
// AttrBitSize: size in bits
//
// For most languages BitOffset/DataBitOffset/BitSize will not be present
// for base types.
name, _ = e.Val(AttrName).(string)
enc, ok := e.Val(AttrEncoding).(int64)
if !ok {
err = DecodeError{name, e.Offset, "missing encoding attribute for " + name}
goto end
}
switch enc {
default:
err = DecodeError{name, e.Offset, "unrecognized encoding attribute value"}
goto end
case encAddress:
typ = new(AddrType)
case encBoolean:
typ = new(BoolType)
case encComplexFloat:
typ = new(ComplexType)
if name == "complex" {
// clang writes out 'complex' instead of 'complex float' or 'complex double'.
// clang also writes out a byte size that we can use to distinguish.
// See issue 8694.
switch byteSize, _ := e.Val(AttrByteSize).(int64); byteSize {
case 8:
name = "complex float"
case 16:
name = "complex double"
}
}
case encFloat:
typ = new(FloatType)
case encSigned:
typ = new(IntType)
case encUnsigned:
typ = new(UintType)
case encSignedChar:
typ = new(CharType)
case encUnsignedChar:
typ = new(UcharType)
}
typeCache[off] = typ
t = typ.(basicGetter).Basic()
t.Name = name
t.BitSize, _ = e.Val(AttrBitSize).(int64)
t.BitOffset, haveBitOffset = e.Val(AttrBitOffset).(int64)
t.DataBitOffset, haveDataBitOffset = e.Val(AttrDataBitOffset).(int64)
if haveBitOffset && haveDataBitOffset {
err = DecodeError{name, e.Offset, "duplicate bit offset attributes"}
goto end
}
end:
return typ, err
}
func (e *Entry) readClassStructUnionTag(d *Data, name string, off Offset, typeCache offsetMap, fixups *typeFixer, next entryChildFunc, typeOf entryTypeFunc) (typ Type, err error) {
// Structure, union, or class type. (DWARF v2 §5.5)
// Attributes:
// AttrName: name of struct, union, or class
// AttrByteSize: byte size [required]
// AttrDeclaration: if true, struct/union/class is incomplete
// Children:
// TagMember to describe one member.
// AttrName: name of member [required]
// AttrType: type of member [required]
// AttrByteSize: size in bytes
// AttrBitOffset: bit offset within bytes for bit fields
// AttrDataBitOffset: field bit offset relative to struct start
// AttrBitSize: bit size for bit fields
// AttrDataMemberLoc: location within struct [required for struct, class]
// There is much more to handle C++, all ignored for now.
t := new(StructType)
typ = t
typeCache[off] = t
switch e.Tag {
case TagClassType:
t.Kind = "class"
case TagStructType:
t.Kind = "struct"
case TagUnionType:
t.Kind = "union"
}
t.StructName, _ = e.Val(AttrName).(string)
t.Incomplete = e.Val(AttrDeclaration) != nil
t.Field = make([]*StructField, 0, 8)
var lastFieldType *Type
var lastFieldBitSize int64
var lastFieldByteOffset int64
for kid := next(); kid != nil; kid = next() {
if kid.Tag != TagMember {
continue
}
f := new(StructField)
f.Type, err = typeOf(kid)
if err != nil {
goto end
}
switch loc := kid.Val(AttrDataMemberLoc).(type) {
case []byte:
// TODO: Should have original compilation
// unit here, not unknownFormat.
b := makeBuf(d, unknownFormat{}, "location", 0, loc)
if b.uint8() != opPlusUconst {
err = DecodeError{name, kid.Offset, "unexpected opcode"}
goto end
}
f.ByteOffset = int64(b.uint())
if b.err != nil {
err = b.err
goto end
}
case int64:
f.ByteOffset = loc
}
f.Name, _ = kid.Val(AttrName).(string)
f.ByteSize, _ = kid.Val(AttrByteSize).(int64)
haveBitOffset := false
haveDataBitOffset := false
f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64)
f.DataBitOffset, haveDataBitOffset = kid.Val(AttrDataBitOffset).(int64)
if haveBitOffset && haveDataBitOffset {
err = DecodeError{name, e.Offset, "duplicate bit offset attributes"}
goto end
}
f.BitSize, _ = kid.Val(AttrBitSize).(int64)
t.Field = append(t.Field, f)
if lastFieldBitSize == 0 && lastFieldByteOffset == f.ByteOffset && t.Kind != "union" {
// Last field was zero width. Fix array length.
// (DWARF writes out 0-length arrays as if they were 1-length arrays.)
fixups.recordArrayType(lastFieldType)
}
lastFieldType = &f.Type
lastFieldByteOffset = f.ByteOffset
lastFieldBitSize = f.BitSize
}
if t.Kind != "union" {
b, ok := e.Val(AttrByteSize).(int64)
if ok && b == lastFieldByteOffset {
// Final field must be zero width. Fix array length.
fixups.recordArrayType(lastFieldType)
}
}
end:
return typ, err
}
func (e *Entry) readConstVolatileRestrictTag(d *Data, name string, off Offset, typeCache offsetMap, fixups *typeFixer, next entryChildFunc, typeOf entryTypeFunc) (typ Type, err error) {
// Structure, union, or class type. (DWARF v2 §5.5)
// Attributes:
// AttrName: name of struct, union, or class
// AttrByteSize: byte size [required]
// AttrDeclaration: if true, struct/union/class is incomplete
// Children:
// TagMember to describe one member.
// AttrName: name of member [required]
// AttrType: type of member [required]
// AttrByteSize: size in bytes
// AttrBitOffset: bit offset within bytes for bit fields
// AttrDataBitOffset: field bit offset relative to struct start
// AttrBitSize: bit size for bit fields
// AttrDataMemberLoc: location within struct [required for struct, class]
// There is much more to handle C++, all ignored for now.
t := new(StructType)
typ = t
typeCache[off] = t
switch e.Tag {
case TagClassType:
t.Kind = "class"
case TagStructType:
t.Kind = "struct"
case TagUnionType:
t.Kind = "union"
}
t.StructName, _ = e.Val(AttrName).(string)
t.Incomplete = e.Val(AttrDeclaration) != nil
t.Field = make([]*StructField, 0, 8)
var lastFieldType *Type
var lastFieldBitSize int64
var lastFieldByteOffset int64
for kid := next(); kid != nil; kid = next() {
if kid.Tag != TagMember {
continue
}
f := new(StructField)
f.Type, err = typeOf(kid)
if err != nil {
goto end
}
switch loc := kid.Val(AttrDataMemberLoc).(type) {
case []byte:
// TODO: Should have original compilation
// unit here, not unknownFormat.
b := makeBuf(d, unknownFormat{}, "location", 0, loc)
if b.uint8() != opPlusUconst {
err = DecodeError{name, kid.Offset, "unexpected opcode"}
goto end
}
f.ByteOffset = int64(b.uint())
if b.err != nil {
err = b.err
goto end
}
case int64:
f.ByteOffset = loc
}
f.Name, _ = kid.Val(AttrName).(string)
f.ByteSize, _ = kid.Val(AttrByteSize).(int64)
haveBitOffset := false
haveDataBitOffset := false
f.BitOffset, haveBitOffset = kid.Val(AttrBitOffset).(int64)
f.DataBitOffset, haveDataBitOffset = kid.Val(AttrDataBitOffset).(int64)
if haveBitOffset && haveDataBitOffset {
err = DecodeError{name, e.Offset, "duplicate bit offset attributes"}
goto end
}
f.BitSize, _ = kid.Val(AttrBitSize).(int64)
t.Field = append(t.Field, f)
if lastFieldBitSize == 0 && lastFieldByteOffset == f.ByteOffset && t.Kind != "union" {
// Last field was zero width. Fix array length.
// (DWARF writes out 0-length arrays as if they were 1-length arrays.)
fixups.recordArrayType(lastFieldType)
}
lastFieldType = &f.Type
lastFieldByteOffset = f.ByteOffset
lastFieldBitSize = f.BitSize
}
if t.Kind != "union" {
b, ok := e.Val(AttrByteSize).(int64)
if ok && b == lastFieldByteOffset {
// Final field must be zero width. Fix array length.
fixups.recordArrayType(lastFieldType)
}
}
end:
return typ, err
}
func (e *Entry) readEnumerationTag(off Offset, typeCache offsetMap, next entryChildFunc) (typ Type, err error) {
// Enumeration type (DWARF v2 §5.6)
// Attributes:
// AttrName: enum name if any
// AttrByteSize: bytes required to represent largest value
// Children:
// TagEnumerator:
// AttrName: name of constant
// AttrConstValue: value of constant
t := new(EnumType)
typ = t
typeCache[off] = t
t.EnumName, _ = e.Val(AttrName).(string)
t.Val = make([]*EnumValue, 0, 8)
for kid := next(); kid != nil; kid = next() {
if kid.Tag == TagEnumerator {
f := new(EnumValue)
f.Name, _ = kid.Val(AttrName).(string)
f.Val, _ = kid.Val(AttrConstValue).(int64)
n := len(t.Val)
if n >= cap(t.Val) {
val := make([]*EnumValue, n, n*2)
copy(val, t.Val)
t.Val = val
}
t.Val = t.Val[0 : n+1]
t.Val[n] = f
}
}
return typ, err
}
func (e *Entry) readPointerTag(off Offset, typeCache offsetMap, typeOf entryTypeFunc) (_ Type, err error) {
// Type modifier (DWARF v2 §5.2)
// Attributes:
// AttrType: subtype [not required! void* has no AttrType]
// AttrAddrClass: address class [ignored]
t := new(PtrType)
typeCache[off] = t
if e.Val(AttrType) == nil {
t.Type = &VoidType{}
goto end
}
t.Type, err = typeOf(e)
end:
return t.Type, err
}
func (e *Entry) readSubroutineTag(off Offset, typeCache offsetMap, next entryChildFunc, typeOf entryTypeFunc) (typ Type, err error) {
var t *FuncType
// Subroutine type. (DWARF v2 §5.7)
typ, err = e.readPointerTag(off, typeCache, typeOf)
if err != nil {
goto end
}
// Attributes:
// AttrType: type of return value if any
// AttrName: possible name of type [ignored]
// AttrPrototyped: whether used ANSI C prototype [ignored]
// Children:
// TagFormalParameter: typed parameter
// AttrType: type of parameter
// TagUnspecifiedParameter: final ...
t = new(FuncType)
typ = t
typeCache[off] = t
t.ReturnType, err = typeOf(e)
if err != nil {
goto end
}
t.ParamType = make([]Type, 0, 8)
for kid := next(); kid != nil; kid = next() {
var tkid Type
switch kid.Tag {
default:
continue
case TagFormalParameter:
tkid, err = typeOf(kid)
if err != nil {
goto end
}
case TagUnspecifiedParameters:
tkid = &DotDotDotType{}
}
t.ParamType = append(t.ParamType, tkid)
}
end:
return typ, err
}
func (e *Entry) readTypedefTag(off Offset, typeCache offsetMap, typeOf entryTypeFunc) (_ Type, err error) {
// Typedef (DWARF v2 §5.3)
// Attributes:
// AttrName: name [required]
// AttrType: type definition [required]
t := new(TypedefType)
typeCache[off] = t
t.Name, _ = e.Val(AttrName).(string)
t.Type, err = typeOf(e)
return t.Type, err
}
func (e *Entry) readUnspecifiedTypeTag(off Offset, typeCache offsetMap) (typ Type, err error) {
// Unspecified type (DWARF v3 §5.2)
// Attributes:
// AttrName: name
t := new(UnspecifiedType)
typ = t
typeCache[off] = t
t.Name, _ = e.Val(AttrName).(string)
return typ, nil
}
func (e *Entry) readUnsupportedTypeTag(off Offset, typeCache offsetMap) (typ Type, err error) {
// This is some other type DIE that we're currently not
// equipped to handle. Return an abstract "unsupported type"
// object in such cases.
t := new(UnsupportedType)
typ = t
typeCache[off] = t
t.Tag = e.Tag
t.Name, _ = e.Val(AttrName).(string)
return typ, nil
}
func (e *Entry) fixupByteSize(typ Type, fixups *typeFixer, addressSize int) (Type, *typeFixer) {
b, ok := e.Val(AttrByteSize).(int64)
if !ok {
b = -1
switch t := typ.(type) {
case *TypedefType:
// Record that we need to resolve this
// type's size once the type graph is
// constructed.
fixups.typedefs = append(fixups.typedefs, t)
case *PtrType:
b = int64(addressSize)
}
}
typ.Common().ByteSize = b
return typ, fixups
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment