Last active
February 19, 2025 06:41
-
-
Save mikeschinkel/6396526d22fa64b91222c777ba4f3ca6 to your computer and use it in GitHub Desktop.
A refactoring of https://github.com/golang/go/blob/279da965329a74cd75320f15cb9672a282690ab7/src/debug/dwarf/type.go#L407-L859
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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