proc: remove old method to resolve the type of an interface to a DIE (#3150)

Before 1.11 we used to read a bunch of runtime structures to determine
the runtime type of an interface variable. This had significant
dependencies on private structs of the Go runtime and the code is
broken for versions of Go after 1.17.
Remove all this code, since it is no longer used and doesn't work with
newer versions of Go anyway.
This commit is contained in:
Alessandro Arzilli 2022-09-29 19:06:15 +02:00 committed by GitHub
parent ec5fcc07fe
commit 02d46b059e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 2 additions and 621 deletions

@ -96,11 +96,6 @@ type BinaryInfo struct {
gStructOffset uint64
// nameOfRuntimeType maps an address of a runtime._type struct to its
// decoded name. Used with versions of Go <= 1.10 to figure out the DIE of
// the concrete type of interfaces.
nameOfRuntimeType map[uint64]nameOfRuntimeTypeEntry
// consts[off] lists all the constants with the type defined at offset off.
consts constantsMap
@ -639,7 +634,7 @@ type ElfDynamicSection struct {
// NewBinaryInfo returns an initialized but unloaded BinaryInfo struct.
func NewBinaryInfo(goos, goarch string) *BinaryInfo {
r := &BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uint64]nameOfRuntimeTypeEntry), logger: logflags.DebuggerLogger()}
r := &BinaryInfo{GOOS: goos, logger: logflags.DebuggerLogger()}
// TODO: find better way to determine proc arch (perhaps use executable file info).
switch goarch {

@ -1,10 +1,5 @@
package proc
import (
"go/constant"
"unsafe"
)
// delve counterpart to runtime.moduledata
type moduleData struct {
text, etext uint64
@ -83,127 +78,3 @@ func findModuleDataForType(bi *BinaryInfo, mds []moduleData, typeAddr uint64, me
}
return nil
}
func resolveTypeOff(bi *BinaryInfo, mds []moduleData, typeAddr, off uint64, mem MemoryReadWriter) (*Variable, error) {
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
md := findModuleDataForType(bi, mds, typeAddr, mem)
rtyp, err := bi.findType("runtime._type")
if err != nil {
return nil, err
}
if md == nil {
v, err := reflectOffsMapAccess(bi, off, mem)
if err != nil {
return nil, err
}
v.loadValue(LoadConfig{false, 1, 0, 0, -1, 0})
addr, _ := constant.Int64Val(v.Value)
return v.newVariable(v.Name, uint64(addr), rtyp, mem), nil
}
if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem)); t != nil {
return t, nil
}
res := md.types + off
return newVariable("", uint64(res), rtyp, bi, mem), nil
}
func resolveNameOff(bi *BinaryInfo, mds []moduleData, typeAddr, off uint64, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
for _, md := range mds {
if typeAddr >= md.types && typeAddr < md.etypes {
return loadName(bi, md.types+off, mem)
}
}
v, err := reflectOffsMapAccess(bi, off, mem)
if err != nil {
return "", "", 0, err
}
resv := v.maybeDereference()
if resv.Unreadable != nil {
return "", "", 0, resv.Unreadable
}
return loadName(bi, resv.Addr, mem)
}
func reflectOffsMapAccess(bi *BinaryInfo, off uint64, mem MemoryReadWriter) (*Variable, error) {
scope := globalScope(nil, bi, bi.Images[0], mem)
reflectOffs, err := scope.findGlobal("runtime", "reflectOffs")
if err != nil {
return nil, err
}
reflectOffsm, err := reflectOffs.structMember("m")
if err != nil {
return nil, err
}
return reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), mem))
}
const (
// flags for the name struct (see 'type name struct' in $GOROOT/src/reflect/type.go)
nameflagExported = 1 << 0
nameflagHasTag = 1 << 1
nameflagHasPkg = 1 << 2
)
func loadName(bi *BinaryInfo, addr uint64, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
off := addr
namedata := make([]byte, 3)
_, err = mem.ReadMemory(namedata, off)
off += 3
if err != nil {
return "", "", 0, err
}
namelen := uint16(namedata[1])<<8 | uint16(namedata[2])
rawstr := make([]byte, int(namelen))
_, err = mem.ReadMemory(rawstr, off)
off += uint64(namelen)
if err != nil {
return "", "", 0, err
}
name = string(rawstr)
if namedata[0]&nameflagHasTag != 0 {
taglendata := make([]byte, 2)
_, err = mem.ReadMemory(taglendata, off)
off += 2
if err != nil {
return "", "", 0, err
}
taglen := uint16(taglendata[0])<<8 | uint16(taglendata[1])
rawstr := make([]byte, int(taglen))
_, err = mem.ReadMemory(rawstr, off)
off += uint64(taglen)
if err != nil {
return "", "", 0, err
}
tag = string(rawstr)
}
if namedata[0]&nameflagHasPkg != 0 {
pkgdata := make([]byte, 4)
_, err = mem.ReadMemory(pkgdata, off)
if err != nil {
return "", "", 0, err
}
// see func pkgPath in $GOROOT/src/reflect/type.go
copy((*[4]byte)(unsafe.Pointer(&pkgpathoff))[:], pkgdata)
}
return name, tag, pkgpathoff, nil
}

@ -1,17 +1,14 @@
package proc
import (
"bytes"
"debug/dwarf"
"errors"
"fmt"
"go/constant"
"reflect"
"strings"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/goversion"
)
// The kind field in runtime._type is a reflect.Kind value plus
@ -24,24 +21,6 @@ const (
kindMask = (1 << 5) - 1 // +rtype kindMask
)
// Value of tflag field in runtime._type.
// See $GOROOT/reflect/type.go for a description of these flags.
const (
tflagUncommon = 1 << 0
tflagExtraStar = 1 << 1
tflagNamed = 1 << 2
)
// These constants contain the names of the fields of runtime.interfacetype
// and runtime.imethod.
// runtime.interfacetype.mhdr is a slice of runtime.imethod describing the
// methods of the interface.
const (
imethodFieldName = "name"
imethodFieldItyp = "ityp"
interfacetypeFieldMhdr = "mhdr"
)
type runtimeTypeDIE struct {
offset dwarf.Offset
kind int64
@ -150,28 +129,7 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
}
}
// go1.7 to go1.10 implementation: convert runtime._type structs to type names
if goversion.ProducerAfterOrEqual(_type.bi.Producer(), 1, 17) {
// Go 1.17 changed the encoding of names in runtime._type breaking the
// code below, but the codepath above, using runtimeTypeToDIE should be
// enough.
// The change happened in commit 287025925f66f90ad9b30aea2e533928026a8376
// reviewed in https://go-review.googlesource.com/c/go/+/318249
return nil, 0, fmt.Errorf("could not resolve interface type")
}
typename, kind, err := nameOfRuntimeType(mds, _type)
if err != nil {
return nil, 0, fmt.Errorf("invalid interface type: %v", err)
}
typ, err = bi.findType(typename)
if err != nil {
return nil, 0, fmt.Errorf("interface type %q not found for %#x: %v", typename, dataAddr, err)
}
return typ, kind, nil
return nil, 0, fmt.Errorf("could not resolve interface type")
}
// resolveParametricType returns the real type of t if t is a parametric
@ -202,449 +160,6 @@ func resolveParametricType(tgt *Target, bi *BinaryInfo, mem MemoryReadWriter, t
return typ, nil
}
type nameOfRuntimeTypeEntry struct {
typename string
kind int64
}
// Returns the type name of the type described in _type.
// _type is a non-loaded Variable pointing to runtime._type struct in the target.
// The returned string is in the format that's used in DWARF data
func nameOfRuntimeType(mds []moduleData, _type *Variable) (typename string, kind int64, err error) {
if e, ok := _type.bi.nameOfRuntimeType[_type.Addr]; ok {
return e.typename, e.kind, nil
}
var tflag int64
if tflagField := _type.loadFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil {
tflag, _ = constant.Int64Val(tflagField.Value)
}
if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
kind, _ = constant.Int64Val(kindField.Value)
}
// Named types are defined by a 'type' expression, everything else
// (for example pointers to named types) are not considered named.
if tflag&tflagNamed != 0 {
typename, err = nameOfNamedRuntimeType(mds, _type, kind, tflag)
if err == nil {
_type.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename: typename, kind: kind}
}
return typename, kind, err
}
typename, err = nameOfUnnamedRuntimeType(mds, _type, kind, tflag)
if err == nil {
_type.bi.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename: typename, kind: kind}
}
return typename, kind, err
}
// The layout of a runtime._type struct is as follows:
//
// <runtime._type><kind specific struct fields><runtime.uncommontype>
//
// with the 'uncommon type struct' being optional
//
// For named types first we extract the type name from the 'str'
// field in the runtime._type struct.
// Then we prepend the package path from the runtime.uncommontype
// struct, when it exists.
//
// To find out the memory address of the runtime.uncommontype struct
// we first cast the Variable pointing to the runtime._type struct
// to a struct specific to the type's kind (for example, if the type
// being described is a slice type the variable will be specialized
// to a runtime.slicetype).
func nameOfNamedRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (typename string, err error) {
var strOff int64
if strField := _type.loadFieldNamed("str"); strField != nil && strField.Value != nil {
strOff, _ = constant.Int64Val(strField.Value)
} else {
return "", errors.New("could not find str field")
}
// The following code is adapted from reflect.(*rtype).Name.
// For a description of how memory is organized for type names read
// the comment to 'type name struct' in $GOROOT/src/reflect/type.go
typename, _, _, err = resolveNameOff(_type.bi, mds, _type.Addr, uint64(strOff), _type.mem)
if err != nil {
return "", err
}
if tflag&tflagExtraStar != 0 {
typename = typename[1:]
}
if i := strings.Index(typename, "."); i >= 0 {
typename = typename[i+1:]
} else {
return typename, nil
}
// The following code is adapted from reflect.(*rtype).PkgPath in
// $GOROOT/src/reflect/type.go
_type, err = specificRuntimeType(_type, kind)
if err != nil {
return "", err
}
if ut := uncommon(_type, tflag); ut != nil {
if pkgPathField := ut.loadFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil {
pkgPathOff, _ := constant.Int64Val(pkgPathField.Value)
pkgPath, _, _, err := resolveNameOff(_type.bi, mds, _type.Addr, uint64(pkgPathOff), _type.mem)
if err != nil {
return "", err
}
if slash := strings.LastIndex(pkgPath, "/"); slash >= 0 {
fixedName := strings.Replace(pkgPath[slash+1:], ".", "%2e", -1)
if fixedName != pkgPath[slash+1:] {
pkgPath = pkgPath[:slash+1] + fixedName
}
}
typename = pkgPath + "." + typename
}
}
return typename, nil
}
func nameOfUnnamedRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) {
_type, err := specificRuntimeType(_type, kind)
if err != nil {
return "", err
}
// The types referred to here are defined in $GOROOT/src/runtime/type.go
switch reflect.Kind(kind & kindMask) {
case reflect.Array:
var len int64
if lenField := _type.loadFieldNamed("len"); lenField != nil && lenField.Value != nil {
len, _ = constant.Int64Val(lenField.Value)
}
elemname, err := fieldToType(mds, _type, "elem")
if err != nil {
return "", err
}
return fmt.Sprintf("[%d]%s", len, elemname), nil
case reflect.Chan:
elemname, err := fieldToType(mds, _type, "elem")
if err != nil {
return "", err
}
return "chan " + elemname, nil
case reflect.Func:
return nameOfFuncRuntimeType(mds, _type, tflag, true)
case reflect.Interface:
return nameOfInterfaceRuntimeType(mds, _type, kind, tflag)
case reflect.Map:
keyname, err := fieldToType(mds, _type, "key")
if err != nil {
return "", err
}
elemname, err := fieldToType(mds, _type, "elem")
if err != nil {
return "", err
}
return "map[" + keyname + "]" + elemname, nil
case reflect.Ptr:
elemname, err := fieldToType(mds, _type, "elem")
if err != nil {
return "", err
}
return "*" + elemname, nil
case reflect.Slice:
elemname, err := fieldToType(mds, _type, "elem")
if err != nil {
return "", err
}
return "[]" + elemname, nil
case reflect.Struct:
return nameOfStructRuntimeType(mds, _type, kind, tflag)
default:
return nameOfNamedRuntimeType(mds, _type, kind, tflag)
}
}
// Returns the expression describing an anonymous function type.
// A runtime.functype is followed by a runtime.uncommontype
// (optional) and then by an array of pointers to runtime._type,
// one for each input and output argument.
func nameOfFuncRuntimeType(mds []moduleData, _type *Variable, tflag int64, anonymous bool) (string, error) {
rtyp, err := _type.bi.findType("runtime._type")
if err != nil {
return "", err
}
prtyp := pointerTo(rtyp, _type.bi.Arch)
uadd := _type.RealType.Common().ByteSize
if ut := uncommon(_type, tflag); ut != nil {
uadd += ut.RealType.Common().ByteSize
}
var inCount, outCount int64
if inCountField := _type.loadFieldNamed("inCount"); inCountField != nil && inCountField.Value != nil {
inCount, _ = constant.Int64Val(inCountField.Value)
}
if outCountField := _type.loadFieldNamed("outCount"); outCountField != nil && outCountField.Value != nil {
outCount, _ = constant.Int64Val(outCountField.Value)
// only the lowest 15 bits of outCount are used, rest are flags
outCount = outCount & (1<<15 - 1)
}
cursortyp := _type.newVariable("", _type.Addr+uint64(uadd), prtyp, _type.mem)
var buf bytes.Buffer
if anonymous {
buf.WriteString("func(")
} else {
buf.WriteString("(")
}
for i := int64(0); i < inCount; i++ {
argtype := cursortyp.maybeDereference()
cursortyp.Addr += uint64(_type.bi.Arch.PtrSize())
argtypename, _, err := nameOfRuntimeType(mds, argtype)
if err != nil {
return "", err
}
buf.WriteString(argtypename)
if i != inCount-1 {
buf.WriteString(", ")
}
}
buf.WriteString(")")
switch outCount {
case 0:
// nothing to do
case 1:
buf.WriteString(" ")
argtype := cursortyp.maybeDereference()
argtypename, _, err := nameOfRuntimeType(mds, argtype)
if err != nil {
return "", err
}
buf.WriteString(argtypename)
default:
buf.WriteString(" (")
for i := int64(0); i < outCount; i++ {
argtype := cursortyp.maybeDereference()
cursortyp.Addr += uint64(_type.bi.Arch.PtrSize())
argtypename, _, err := nameOfRuntimeType(mds, argtype)
if err != nil {
return "", err
}
buf.WriteString(argtypename)
if i != inCount-1 {
buf.WriteString(", ")
}
}
buf.WriteString(")")
}
return buf.String(), nil
}
func nameOfInterfaceRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) {
var buf bytes.Buffer
buf.WriteString("interface {")
methods, _ := _type.structMember(interfacetypeFieldMhdr)
methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1, 0})
if methods.Unreadable != nil {
return "", nil
}
if len(methods.Children) == 0 {
buf.WriteString("}")
return buf.String(), nil
}
buf.WriteString(" ")
for i, im := range methods.Children {
var methodname, methodtype string
for i := range im.Children {
switch im.Children[i].Name {
case imethodFieldName:
nameoff, _ := constant.Int64Val(im.Children[i].Value)
var err error
methodname, _, _, err = resolveNameOff(_type.bi, mds, _type.Addr, uint64(nameoff), _type.mem)
if err != nil {
return "", err
}
case imethodFieldItyp:
typeoff, _ := constant.Int64Val(im.Children[i].Value)
typ, err := resolveTypeOff(_type.bi, mds, _type.Addr, uint64(typeoff), _type.mem)
if err != nil {
return "", err
}
typ, err = specificRuntimeType(typ, int64(reflect.Func))
if err != nil {
return "", err
}
var tflag int64
if tflagField := typ.loadFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil {
tflag, _ = constant.Int64Val(tflagField.Value)
}
methodtype, err = nameOfFuncRuntimeType(mds, typ, tflag, false)
if err != nil {
return "", err
}
}
}
buf.WriteString(methodname)
buf.WriteString(methodtype)
if i != len(methods.Children)-1 {
buf.WriteString("; ")
} else {
buf.WriteString(" }")
}
}
return buf.String(), nil
}
func nameOfStructRuntimeType(mds []moduleData, _type *Variable, kind, tflag int64) (string, error) {
var buf bytes.Buffer
buf.WriteString("struct {")
fields, _ := _type.structMember("fields")
fields.loadArrayValues(0, LoadConfig{false, 2, 0, 4096, -1, 0})
if fields.Unreadable != nil {
return "", fields.Unreadable
}
if len(fields.Children) == 0 {
buf.WriteString("}")
return buf.String(), nil
}
buf.WriteString(" ")
for i, field := range fields.Children {
var fieldname, fieldtypename string
var typeField *Variable
isembed := false
for i := range field.Children {
switch field.Children[i].Name {
case "name":
var nameoff int64
switch field.Children[i].Kind {
case reflect.Struct:
nameoff = int64(field.Children[i].fieldVariable("bytes").Children[0].Addr)
default:
nameoff, _ = constant.Int64Val(field.Children[i].Value)
}
var err error
fieldname, _, _, err = loadName(_type.bi, uint64(nameoff), _type.mem)
if err != nil {
return "", err
}
case "typ":
typeField = field.Children[i].maybeDereference()
var err error
fieldtypename, _, err = nameOfRuntimeType(mds, typeField)
if err != nil {
return "", err
}
case "offsetAnon":
// The offsetAnon field of runtime.structfield combines the offset of
// the struct field from the base address of the struct with a flag
// determining whether the field is anonymous (i.e. an embedded struct).
//
// offsetAnon = (offset<<1) | (anonFlag)
//
// Here we are only interested in the anonymous flag.
offsetAnon, _ := constant.Int64Val(field.Children[i].Value)
isembed = offsetAnon%2 != 0
}
}
// fieldname will be the empty string for anonymous fields
if fieldname != "" && !isembed {
buf.WriteString(fieldname)
buf.WriteString(" ")
}
buf.WriteString(fieldtypename)
if i != len(fields.Children)-1 {
buf.WriteString("; ")
} else {
buf.WriteString(" }")
}
}
return buf.String(), nil
}
func fieldToType(mds []moduleData, _type *Variable, fieldName string) (string, error) {
typeField, err := _type.structMember(fieldName)
if err != nil {
return "", err
}
typeField = typeField.maybeDereference()
typename, _, err := nameOfRuntimeType(mds, typeField)
return typename, err
}
func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) {
typ, err := typeForKind(kind, _type.bi)
if err != nil {
return nil, err
}
if typ == nil {
return _type, nil
}
return _type.newVariable(_type.Name, _type.Addr, typ, _type.mem), nil
}
// See reflect.(*rtype).uncommon in $GOROOT/src/reflect/type.go
func uncommon(_type *Variable, tflag int64) *Variable {
if tflag&tflagUncommon == 0 {
return nil
}
typ, err := _type.bi.findType("runtime.uncommontype")
if err != nil {
return nil
}
return _type.newVariable(_type.Name, _type.Addr+uint64(_type.RealType.Size()), typ, _type.mem)
}
var kindToRuntimeTypeName = map[reflect.Kind]string{
reflect.Array: "runtime.arraytype",
reflect.Chan: "runtime.chantype",
reflect.Func: "runtime.functype",
reflect.Interface: "runtime.interfacetype",
reflect.Map: "runtime.maptype",
reflect.Ptr: "runtime.ptrtype",
reflect.Slice: "runtime.slicetype",
reflect.Struct: "runtime.structtype",
}
// typeForKind returns a *dwarf.StructType describing the specialization of
// runtime._type for the specified type kind. For example if kind is
// reflect.ArrayType it will return runtime.arraytype
func typeForKind(kind int64, bi *BinaryInfo) (*godwarf.StructType, error) {
typename, ok := kindToRuntimeTypeName[reflect.Kind(kind&kindMask)]
if !ok {
return nil, nil
}
typ, err := bi.findType(typename)
if err != nil {
return nil, err
}
typ = resolveTypedef(typ)
return typ.(*godwarf.StructType), nil
}
func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type) (typeAddr uint64, typeKind uint64, found bool, err error) {
so := bi.typeToImage(typ)
rdr := so.DwarfReader()