From f62bf8d1e3d6cd0ebe9e327fe9bbba31356fa1e9 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Thu, 8 Sep 2016 18:11:20 +0200 Subject: [PATCH] proc: Names of concrete types of interfaces parsing their runtime._type Generate names of the concrete types stored inside interface variables by fully parsing their runtime._type instead of simply using the str field. This allows delve to read the contents of an interface variable when the program imports multiple packages that have the same name. It also allows delve to correctly interpret some complex anonymous types. Fixes #455 --- _fixtures/pkgrenames.go | 55 ++ _fixtures/vendor/dir0/pkg/main.go | 13 + _fixtures/vendor/dir0/renamedpackage/main.go | 5 + _fixtures/vendor/dir1/pkg/main.go | 6 + proc/eval.go | 2 +- proc/moduledata.go | 138 ++++- proc/proc.go | 16 +- proc/types.go | 568 ++++++++++++++++++- proc/variables.go | 75 +-- service/test/variables_test.go | 69 ++- 10 files changed, 871 insertions(+), 76 deletions(-) create mode 100644 _fixtures/pkgrenames.go create mode 100644 _fixtures/vendor/dir0/pkg/main.go create mode 100644 _fixtures/vendor/dir0/renamedpackage/main.go create mode 100644 _fixtures/vendor/dir1/pkg/main.go diff --git a/_fixtures/pkgrenames.go b/_fixtures/pkgrenames.go new file mode 100644 index 00000000..45e25bc0 --- /dev/null +++ b/_fixtures/pkgrenames.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "reflect" + "runtime" + + pkg1 "go/ast" + pkg2 "net/http" + + "dir0/pkg" + "dir0/renamedpackage" + dir1pkg "dir1/pkg" +) + +func main() { + var badexpr interface{} = &pkg1.BadExpr{1, 2} + var req interface{} = &pkg2.Request{Method: "amethod"} + var amap interface{} = map[pkg1.BadExpr]pkg2.Request{pkg1.BadExpr{2, 3}: pkg2.Request{Method: "othermethod"}} + var amap2 interface{} = &map[pkg1.BadExpr]pkg2.Request{pkg1.BadExpr{2, 3}: pkg2.Request{Method: "othermethod"}} + var dir0someType interface{} = &pkg.SomeType{3} + var dir1someType interface{} = dir1pkg.SomeType{1, 2} + var amap3 interface{} = map[pkg.SomeType]dir1pkg.SomeType{pkg.SomeType{4}: dir1pkg.SomeType{5, 6}} + var anarray interface{} = [2]pkg.SomeType{pkg.SomeType{1}, pkg.SomeType{2}} + var achan interface{} = make(chan pkg.SomeType) + var aslice interface{} = []pkg.SomeType{pkg.SomeType{3}, pkg.SomeType{4}} + var afunc interface{} = func(a pkg.SomeType, b dir1pkg.SomeType) {} + var astruct interface{} = &struct { + A dir1pkg.SomeType + B pkg.SomeType + }{ + A: dir1pkg.SomeType{1, 2}, + B: pkg.SomeType{3}, + } + var astruct2 interface{} = &struct { + dir1pkg.SomeType + X int + }{ + SomeType: dir1pkg.SomeType{1, 2}, + X: 10, + } + var iface interface { + AMethod(x int) int + AnotherMethod(x int) int + } = &pkg.SomeType{4} + var iface2iface interface{} = &iface + var iface3 interface{} = &realname.SomeType{A: true} + + runtime.Breakpoint() + t := reflect.ValueOf(iface2iface).Elem().Type() + m := t.Method(0) + fmt.Println(m.Type.In(0)) + fmt.Println(m.Type.String()) + fmt.Println(badexpr, req, amap, amap2, dir0someType, dir1someType, amap3, anarray, achan, aslice, afunc, astruct, astruct2, iface2iface, iface3) +} diff --git a/_fixtures/vendor/dir0/pkg/main.go b/_fixtures/vendor/dir0/pkg/main.go new file mode 100644 index 00000000..053f4f50 --- /dev/null +++ b/_fixtures/vendor/dir0/pkg/main.go @@ -0,0 +1,13 @@ +package pkg + +type SomeType struct { + X float64 +} + +func (s *SomeType) AMethod(x int) int { + return x + 3 +} + +func (s *SomeType) AnotherMethod(x int) int { + return x + 4 +} diff --git a/_fixtures/vendor/dir0/renamedpackage/main.go b/_fixtures/vendor/dir0/renamedpackage/main.go new file mode 100644 index 00000000..41d6ed92 --- /dev/null +++ b/_fixtures/vendor/dir0/renamedpackage/main.go @@ -0,0 +1,5 @@ +package realname + +type SomeType struct { + A bool +} diff --git a/_fixtures/vendor/dir1/pkg/main.go b/_fixtures/vendor/dir1/pkg/main.go new file mode 100644 index 00000000..99ad1435 --- /dev/null +++ b/_fixtures/vendor/dir1/pkg/main.go @@ -0,0 +1,6 @@ +package pkg + +type SomeType struct { + X int + Y int +} diff --git a/proc/eval.go b/proc/eval.go index 24245ac3..01213fa2 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -493,7 +493,7 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err if err != nil { return nil, err } - if xv.Children[0].DwarfType.String() != typ.String() { + if xv.Children[0].DwarfType.Common().Name != typ.Common().Name { return nil, fmt.Errorf("interface conversion: %s is %s, not %s", xv.DwarfType.Common().Name, xv.Children[0].TypeString(), typ.Common().Name) } return &xv.Children[0], nil diff --git a/proc/moduledata.go b/proc/moduledata.go index 6579bf69..8652a5a5 100644 --- a/proc/moduledata.go +++ b/proc/moduledata.go @@ -2,11 +2,13 @@ package proc import ( "go/constant" + "unsafe" ) // delve counterpart to runtime.moduledata type moduleData struct { types, etypes uintptr + typemapVar *Variable } func (dbp *Process) loadModuleData() (err error) { @@ -19,7 +21,7 @@ func (dbp *Process) loadModuleData() (err error) { } for md.Addr != 0 { - var typesVar, etypesVar, nextVar *Variable + var typesVar, etypesVar, nextVar, typemapVar *Variable var types, etypes uint64 if typesVar, err = md.structMember("types"); err != nil { @@ -31,6 +33,9 @@ func (dbp *Process) loadModuleData() (err error) { if nextVar, err = md.structMember("next"); err != nil { return } + if typemapVar, err = md.structMember("typemap"); err != nil { + return + } if types, err = typesVar.asUint(); err != nil { return } @@ -38,7 +43,7 @@ func (dbp *Process) loadModuleData() (err error) { return } - dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes)}) + dbp.moduleData = append(dbp.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar}) md = nextVar.maybeDereference() if md.Unreadable != nil { @@ -51,37 +56,134 @@ func (dbp *Process) loadModuleData() (err error) { return } -func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (uintptr, error) { - // See runtime.resolveNameOff in $GOROOT/src/runtime/type.go +func (dbp *Process) resolveTypeOff(typeAddr uintptr, off uintptr) (*Variable, error) { + // See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go if err := dbp.loadModuleData(); err != nil { - return 0, err + return nil, err } - for _, md := range dbp.moduleData { - if typeAddr >= md.types && typeAddr < md.etypes { - return md.types + off, nil + + var md *moduleData + for i := range dbp.moduleData { + if typeAddr >= dbp.moduleData[i].types && typeAddr < dbp.moduleData[i].etypes { + md = &dbp.moduleData[i] } } - scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0} - reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") + rtyp, err := dbp.findType("runtime._type") if err != nil { - return 0, err + return nil, err } - reflectOffsm, err := reflectOffs.structMember("m") - if err != nil { - return 0, err + if md == nil { + v, err := dbp.reflectOffsMapAccess(off) + if err != nil { + return nil, err + } + v.loadValue(LoadConfig{false, 1, 0, 0, -1}) + addr, _ := constant.Int64Val(v.Value) + return v.newVariable(v.Name, uintptr(addr), rtyp), nil } - v, err := reflectOffsm.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)) + if t, _ := md.typemapVar.mapAccess(newConstant(constant.MakeUint64(uint64(off)), dbp.CurrentThread)); t != nil { + return t, nil + } + + res := md.types + uintptr(off) + + return dbp.CurrentThread.newVariable("", res, rtyp), nil +} + +func (dbp *Process) resolveNameOff(typeAddr uintptr, off uintptr) (name, tag string, pkgpathoff int32, err error) { + // See runtime.resolveNameOff in $GOROOT/src/runtime/type.go + if err = dbp.loadModuleData(); err != nil { + return "", "", 0, err + } + + for _, md := range dbp.moduleData { + if typeAddr >= md.types && typeAddr < md.etypes { + return dbp.loadName(md.types + off) + } + } + + v, err := dbp.reflectOffsMapAccess(off) if err != nil { - return 0, err + return "", "", 0, err } resv := v.maybeDereference() if resv.Unreadable != nil { - return 0, resv.Unreadable + return "", "", 0, resv.Unreadable } - return resv.Addr, nil + return dbp.loadName(resv.Addr) +} + +func (dbp *Process) reflectOffsMapAccess(off uintptr) (*Variable, error) { + scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0} + reflectOffs, err := scope.packageVarAddr("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)), dbp.CurrentThread)) +} + +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 (dbp *Process) loadName(addr uintptr) (name, tag string, pkgpathoff int32, err error) { + off := addr + namedata, err := dbp.CurrentThread.readMemory(off, 3) + off += 3 + if err != nil { + return "", "", 0, err + } + + namelen := uint16(namedata[1]<<8) | uint16(namedata[2]) + + rawstr, err := dbp.CurrentThread.readMemory(off, int(namelen)) + off += uintptr(namelen) + if err != nil { + return "", "", 0, err + } + + name = string(rawstr) + + if namedata[0]&nameflagHasTag != 0 { + taglendata, err := dbp.CurrentThread.readMemory(off, 2) + off += 2 + if err != nil { + return "", "", 0, err + } + taglen := uint16(taglendata[0]<<8) | uint16(taglendata[1]) + + rawstr, err := dbp.CurrentThread.readMemory(off, int(taglen)) + off += uintptr(taglen) + if err != nil { + return "", "", 0, err + } + + tag = string(rawstr) + } + + if namedata[0]&nameflagHasPkg != 0 { + pkgdata, err := dbp.CurrentThread.readMemory(off, 4) + 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 } diff --git a/proc/proc.go b/proc/proc.go index 65f4138c..7dcd43fd 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -62,6 +62,7 @@ type Process struct { loadModuleDataOnce sync.Once moduleData []moduleData + nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry } var NotExecutableErr = errors.New("not an executable file") @@ -72,13 +73,14 @@ var NotExecutableErr = errors.New("not an executable file") // `handlePtraceFuncs`. func New(pid int) *Process { dbp := &Process{ - Pid: pid, - Threads: make(map[int]*Thread), - Breakpoints: make(map[uint64]*Breakpoint), - firstStart: true, - os: new(OSProcessDetails), - ptraceChan: make(chan func()), - ptraceDoneChan: make(chan interface{}), + Pid: pid, + Threads: make(map[int]*Thread), + Breakpoints: make(map[uint64]*Breakpoint), + firstStart: true, + os: new(OSProcessDetails), + ptraceChan: make(chan func()), + ptraceDoneChan: make(chan interface{}), + nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), } // TODO: find better way to determine proc arch (perhaps use executable file info) switch runtime.GOARCH { diff --git a/proc/types.go b/proc/types.go index 27cf77ed..50ee0545 100644 --- a/proc/types.go +++ b/proc/types.go @@ -1,17 +1,41 @@ package proc import ( - "github.com/derekparker/delve/dwarf/reader" + "bytes" + "errors" + "fmt" "go/ast" + "go/constant" "go/token" "reflect" "strconv" "strings" "sync" + "unsafe" + + "github.com/derekparker/delve/dwarf/reader" "golang.org/x/debug/dwarf" ) +// The kind field in runtime._type is a reflect.Kind value plus +// some extra flags defined here. +// See equivalent declaration in $GOROOT/src/reflect/type.go +const ( + kindDirectIface = 1 << 5 + kindGCProg = 1 << 6 // Type.gc points to GC program + kindNoPointers = 1 << 7 + kindMask = (1 << 5) - 1 +) + +// 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 +) + // Do not call this function directly it isn't able to deal correctly with package paths func (dbp *Process) findType(name string) (dwarf.Type, error) { off, found := dbp.types[name] @@ -147,3 +171,545 @@ func (dbp *Process) expandPackagesInType(expr ast.Expr) { // nothing to do } } + +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(_type *Variable) (typename string, kind int64, err error) { + if e, ok := _type.dbp.nameOfRuntimeType[_type.Addr]; ok { + return e.typename, e.kind, nil + } + + var tflag int64 + + if tflagField := _type.toFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil { + tflag, _ = constant.Int64Val(tflagField.Value) + } + if kindField := _type.toFieldNamed("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(_type, kind, tflag) + return typename, kind, err + } else { + typename, err = nameOfUnnamedRuntimeType(_type, kind, tflag) + return typename, kind, err + } + + _type.dbp.nameOfRuntimeType[_type.Addr] = nameOfRuntimeTypeEntry{typename, kind} + + return typename, kind, nil +} + +// The layout of a runtime._type struct is as follows: +// +// +// +// 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(_type *Variable, kind, tflag int64) (typename string, err error) { + var strOff int64 + if strField := _type.toFieldNamed("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 = _type.dbp.resolveNameOff(_type.Addr, uintptr(strOff)) + 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.toFieldNamed("pkgpath"); pkgPathField != nil && pkgPathField.Value != nil { + pkgPathOff, _ := constant.Int64Val(pkgPathField.Value) + pkgPath, _, _, err := _type.dbp.resolveNameOff(_type.Addr, uintptr(pkgPathOff)) + if err != nil { + return "", err + } + typename = pkgPath + "." + typename + } + } + + return typename, nil +} + +func nameOfUnnamedRuntimeType(_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.toFieldNamed("len"); lenField != nil && lenField.Value != nil { + len, _ = constant.Int64Val(lenField.Value) + } + elemname, err := fieldToType(_type, "elem") + if err != nil { + return "", err + } + return fmt.Sprintf("[%d]%s", len, elemname), nil + case reflect.Chan: + elemname, err := fieldToType(_type, "elem") + if err != nil { + return "", err + } + return "chan " + elemname, nil + case reflect.Func: + return nameOfFuncRuntimeType(_type, tflag, true) + case reflect.Interface: + return nameOfInterfaceRuntimeType(_type, kind, tflag) + case reflect.Map: + keyname, err := fieldToType(_type, "key") + if err != nil { + return "", err + } + elemname, err := fieldToType(_type, "elem") + if err != nil { + return "", err + } + return "map[" + keyname + "]" + elemname, nil + case reflect.Ptr: + elemname, err := fieldToType(_type, "elem") + if err != nil { + return "", err + } + return "*" + elemname, nil + case reflect.Slice: + elemname, err := fieldToType(_type, "elem") + if err != nil { + return "", err + } + return "[]" + elemname, nil + case reflect.Struct: + return nameOfStructRuntimeType(_type, kind, tflag) + default: + return nameOfNamedRuntimeType(_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(_type *Variable, tflag int64, anonymous bool) (string, error) { + rtyp, err := _type.dbp.findType("runtime._type") + if err != nil { + return "", err + } + prtyp := _type.dbp.pointerTo(rtyp) + + uadd := _type.RealType.Common().ByteSize + if ut := uncommon(_type, tflag); ut != nil { + uadd += ut.RealType.Common().ByteSize + } + + var inCount, outCount int64 + if inCountField := _type.toFieldNamed("inCount"); inCountField != nil && inCountField.Value != nil { + inCount, _ = constant.Int64Val(inCountField.Value) + } + if outCountField := _type.toFieldNamed("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+uintptr(uadd), prtyp) + var buf bytes.Buffer + if anonymous { + buf.WriteString("func(") + } else { + buf.WriteString("(") + } + + for i := int64(0); i < inCount; i++ { + argtype := cursortyp.maybeDereference() + cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize()) + argtypename, _, err := nameOfRuntimeType(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(argtype) + if err != nil { + return "", err + } + buf.WriteString(argtypename) + default: + buf.WriteString(" (") + for i := int64(0); i < outCount; i++ { + argtype := cursortyp.maybeDereference() + cursortyp.Addr += uintptr(_type.dbp.arch.PtrSize()) + argtypename, _, err := nameOfRuntimeType(argtype) + if err != nil { + return "", err + } + buf.WriteString(argtypename) + if i != inCount-1 { + buf.WriteString(", ") + } + } + buf.WriteString(")") + } + return buf.String(), nil +} + +func nameOfInterfaceRuntimeType(_type *Variable, kind, tflag int64) (string, error) { + var buf bytes.Buffer + buf.WriteString("interface {") + + methods, _ := _type.structMember("methods") + methods.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1}) + if methods.Unreadable != nil { + return "", nil + } + + if len(methods.Children) == 0 { + buf.WriteString("}") + return buf.String(), nil + } else { + buf.WriteString(" ") + } + + for i, im := range methods.Children { + var methodname, methodtype string + for i := range im.Children { + switch im.Children[i].Name { + case "name": + nameoff, _ := constant.Int64Val(im.Children[i].Value) + var err error + methodname, _, _, err = _type.dbp.resolveNameOff(_type.Addr, uintptr(nameoff)) + if err != nil { + return "", err + } + + case "typ": + typeoff, _ := constant.Int64Val(im.Children[i].Value) + typ, err := _type.dbp.resolveTypeOff(_type.Addr, uintptr(typeoff)) + if err != nil { + return "", err + } + typ, err = specificRuntimeType(typ, int64(reflect.Func)) + if err != nil { + return "", err + } + var tflag int64 + if tflagField := typ.toFieldNamed("tflag"); tflagField != nil && tflagField.Value != nil { + tflag, _ = constant.Int64Val(tflagField.Value) + } + methodtype, err = nameOfFuncRuntimeType(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(_type *Variable, kind, tflag int64) (string, error) { + var buf bytes.Buffer + buf.WriteString("struct {") + + fields, _ := _type.structMember("fields") + fields.loadArrayValues(0, LoadConfig{false, 1, 0, 4096, -1}) + if fields.Unreadable != nil { + return "", fields.Unreadable + } + + if len(fields.Children) == 0 { + buf.WriteString("}") + return buf.String(), nil + } else { + buf.WriteString(" ") + } + + for i, field := range fields.Children { + var fieldname, fieldtypename string + var typeField *Variable + for i := range field.Children { + switch field.Children[i].Name { + case "name": + nameoff, _ := constant.Int64Val(field.Children[i].Value) + var err error + fieldname, _, _, err = _type.dbp.loadName(uintptr(nameoff)) + if err != nil { + return "", err + } + + case "typ": + typeField = field.Children[i].maybeDereference() + var err error + fieldtypename, _, err = nameOfRuntimeType(typeField) + if err != nil { + return "", err + } + } + } + + // fieldname will be the empty string for anonymous fields + if fieldname != "" { + 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(_type *Variable, fieldName string) (string, error) { + typeField, err := _type.structMember(fieldName) + if err != nil { + return "", err + } + typeField = typeField.maybeDereference() + typename, _, err := nameOfRuntimeType(typeField) + return typename, err +} + +func specificRuntimeType(_type *Variable, kind int64) (*Variable, error) { + rtyp, err := _type.dbp.findType("runtime._type") + if err != nil { + return nil, err + } + prtyp := _type.dbp.pointerTo(rtyp) + + uintptrtyp, err := _type.dbp.findType("uintptr") + if err != nil { + return nil, err + } + + uint32typ := &dwarf.UintType{dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 4, Name: "uint32"}}} + uint16typ := &dwarf.UintType{dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 2, Name: "uint16"}}} + + newStructType := func(name string, sz uintptr) *dwarf.StructType { + return &dwarf.StructType{dwarf.CommonType{Name: name, ByteSize: int64(sz)}, name, "struct", nil, false} + } + + appendField := func(typ *dwarf.StructType, name string, fieldtype dwarf.Type, off uintptr) { + typ.Field = append(typ.Field, &dwarf.StructField{Name: name, ByteOffset: int64(off), Type: fieldtype}) + } + + newSliceType := func(elemtype dwarf.Type) *dwarf.SliceType { + r := newStructType("[]"+elemtype.Common().Name, uintptr(3*uintptrtyp.Size())) + appendField(r, "array", _type.dbp.pointerTo(elemtype), 0) + appendField(r, "len", uintptrtyp, uintptr(uintptrtyp.Size())) + appendField(r, "cap", uintptrtyp, uintptr(2*uintptrtyp.Size())) + return &dwarf.SliceType{StructType: *r, ElemType: elemtype} + } + + var typ *dwarf.StructType + + type rtype struct { + size uintptr + ptrdata uintptr + hash uint32 // hash of type; avoids computation in hash tables + tflag uint8 // extra type information flags + align uint8 // alignment of variable with this type + fieldAlign uint8 // alignment of struct field with this type + kind uint8 // enumeration for C + alg *byte // algorithm table + gcdata *byte // garbage collection data + str int32 // string form + ptrToThis int32 // type for pointer to this type, may be zero + } + + switch reflect.Kind(kind & kindMask) { + case reflect.Array: + // runtime.arraytype + var a struct { + rtype + elem *rtype // array element type + slice *rtype // slice type + len uintptr + } + typ = newStructType("runtime.arraytype", unsafe.Sizeof(a)) + appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem)) + appendField(typ, "len", uintptrtyp, unsafe.Offsetof(a.len)) + case reflect.Chan: + // runtime.chantype + var a struct { + rtype + elem *rtype // channel element type + dir uintptr // channel direction (ChanDir) + } + typ = newStructType("runtime.chantype", unsafe.Sizeof(a)) + appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem)) + case reflect.Func: + // runtime.functype + var a struct { + rtype `reflect:"func"` + inCount uint16 + outCount uint16 // top bit is set if last input parameter is ... + } + typ = newStructType("runtime.functype", unsafe.Sizeof(a)) + appendField(typ, "inCount", uint16typ, unsafe.Offsetof(a.inCount)) + appendField(typ, "outCount", uint16typ, unsafe.Offsetof(a.outCount)) + case reflect.Interface: + // runtime.imethod + type imethod struct { + name uint32 // name of method + typ uint32 // .(*FuncType) underneath + } + + var im imethod + + // runtime.interfacetype + var a struct { + rtype `reflect:"interface"` + pkgPath *byte // import path + methods []imethod // sorted by hash + } + + imethodtype := newStructType("runtime.imethod", unsafe.Sizeof(im)) + appendField(imethodtype, "name", uint32typ, unsafe.Offsetof(im.name)) + appendField(imethodtype, "typ", uint32typ, unsafe.Offsetof(im.typ)) + typ = newStructType("runtime.interfacetype", unsafe.Sizeof(a)) + appendField(typ, "methods", newSliceType(imethodtype), unsafe.Offsetof(a.methods)) + case reflect.Map: + // runtime.maptype + var a struct { + rtype `reflect:"map"` + key *rtype // map key type + elem *rtype // map element (value) type + bucket *rtype // internal bucket structure + hmap *rtype // internal map header + keysize uint8 // size of key slot + indirectkey uint8 // store ptr to key instead of key itself + valuesize uint8 // size of value slot + indirectvalue uint8 // store ptr to value instead of value itself + bucketsize uint16 // size of bucket + reflexivekey bool // true if k==k for all keys + needkeyupdate bool // true if we need to update key on an overwrite + } + typ = newStructType("runtime.maptype", unsafe.Sizeof(a)) + appendField(typ, "key", prtyp, unsafe.Offsetof(a.key)) + appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem)) + case reflect.Ptr: + // runtime.ptrtype + var a struct { + rtype `reflect:"ptr"` + elem *rtype // pointer element (pointed at) type + } + typ = newStructType("runtime.ptrtype", unsafe.Sizeof(a)) + appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem)) + case reflect.Slice: + // runtime.slicetype + var a struct { + rtype `reflect:"slice"` + elem *rtype // slice element type + } + + typ = newStructType("runtime.slicetype", unsafe.Sizeof(a)) + appendField(typ, "elem", prtyp, unsafe.Offsetof(a.elem)) + case reflect.Struct: + // runtime.structtype + type structField struct { + name *byte // name is empty for embedded fields + typ *rtype // type of field + offset uintptr // byte offset of field within struct + } + + var sf structField + + var a struct { + rtype `reflect:"struct"` + pkgPath *byte + fields []structField // sorted by offset + } + + fieldtype := newStructType("runtime.structtype", unsafe.Sizeof(sf)) + appendField(fieldtype, "name", uintptrtyp, unsafe.Offsetof(sf.name)) + appendField(fieldtype, "typ", prtyp, unsafe.Offsetof(sf.typ)) + typ = newStructType("runtime.structtype", unsafe.Sizeof(a)) + appendField(typ, "fields", newSliceType(fieldtype), unsafe.Offsetof(a.fields)) + default: + return _type, nil + } + + return _type.newVariable(_type.Name, _type.Addr, typ), 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.dbp.findType("runtime.uncommontype") + if err != nil { + return nil + } + + return _type.newVariable(_type.Name, _type.Addr+uintptr(_type.RealType.Size()), typ) +} diff --git a/proc/variables.go b/proc/variables.go index fbc04b1f..cf31ccd1 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -1393,8 +1393,8 @@ func mapEvacuated(b *Variable) bool { } func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) { - var _type, str, typestring, data *Variable - var typename string + var _type, typestring, data *Variable + var typ dwarf.Type var err error isnil := false @@ -1446,11 +1446,6 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig typestring = typestring.maybeDereference() } else { go17 = true - str, err = _type.structMember("str") - if err != nil { - v.Unreadable = fmt.Errorf("invalid interface type: %v", err) - return - } } } case "_type": // for runtime.eface @@ -1463,11 +1458,6 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig typestring = typestring.maybeDereference() } else { go17 = true - str, err = _type.structMember("str") - if err != nil { - v.Unreadable = fmt.Errorf("invalid interface type: %v", err) - return - } } } case "data": @@ -1490,37 +1480,25 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } + var kind int64 + if go17 { // No 'string' field use 'str' and 'runtime.firstmoduledata' to // find out what the concrete type is + _type = _type.maybeDereference() - typeAddr := _type.maybeDereference().Addr - strOff, err := str.asInt() + var typename string + typename, kind, err = nameOfRuntimeType(_type) if err != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", err) return } - res, err := v.dbp.resolveNameOff(typeAddr, uintptr(strOff)) + typ, err = v.dbp.findType(typename) if err != nil { - v.Unreadable = fmt.Errorf("could not resolve concrete type (data: %#x): %v", data.Addr, err) + v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) return } - - // For a description of how memory is organized for type names read - // the comment to 'type name struct' in $GOROOT/src/reflect/type.go - - typdata, err := v.dbp.CurrentThread.readMemory(res, 3+v.dbp.arch.PtrSize()) - if err != nil { - v.Unreadable = fmt.Errorf("could not read concrete type (data: %#v): %v", data.Addr, err) - return - } - - nl := int(typdata[1]<<8 | typdata[2]) - - rawstr, err := v.dbp.CurrentThread.readMemory(res+3, nl) - - typename = string(rawstr) } else { if typestring == nil || typestring.Addr == 0 || typestring.Kind != reflect.String { v.Unreadable = fmt.Errorf("invalid interface type") @@ -1532,25 +1510,26 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig return } - typename = constant.StringVal(typestring.Value) + typename := constant.StringVal(typestring.Value) + + t, err := parser.ParseExpr(typename) + if err != nil { + v.Unreadable = fmt.Errorf("invalid interface type, unparsable data type: %v", err) + return + } + + typ, err = v.dbp.findTypeExpr(t) + if err != nil { + v.Unreadable = fmt.Errorf("interface type %q not found for %#x: %v", typename, data.Addr, err) + return + } } - t, err := parser.ParseExpr(typename) - if err != nil { - v.Unreadable = fmt.Errorf("invalid interface type, unparsable data type: %v", err) - return - } - - typ, err := v.dbp.findTypeExpr(t) - if err != nil { - v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", typename, data.Addr, err) - return - } - - realtyp := resolveTypedef(typ) - if _, isptr := realtyp.(*dwarf.PtrType); !isptr { - // interface to non-pointer types are pointers even if the type says otherwise - typ = v.dbp.pointerTo(typ) + if kind&kindDirectIface == 0 { + realtyp := resolveTypedef(typ) + if _, isptr := realtyp.(*dwarf.PtrType); !isptr { + typ = v.dbp.pointerTo(typ) + } } data = data.newVariable("data", data.Addr, typ) diff --git a/service/test/variables_test.go b/service/test/variables_test.go index f6a53381..9c410220 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -474,7 +474,7 @@ func TestEvalExpression(t *testing.T) { {"errnil", true, "error nil", "error nil", "error", nil}, {"iface1", true, "interface {}(*main.astruct) *{A: 1, B: 2}", "interface {}(*main.astruct) 0x…", "interface {}", nil}, {"iface2", true, "interface {}(*string) *\"test\"", "interface {}(*string) 0x…", "interface {}", nil}, - {"iface3", true, "interface {}(*map[string]go/constant.Value) *[]", "interface {}(*map[string]go/constant.Value) 0x…", "interface {}", nil}, + {"iface3", true, "interface {}(map[string]go/constant.Value) []", "interface {}(map[string]go/constant.Value) []", "interface {}", nil}, {"iface4", true, "interface {}(*[]go/constant.Value) *[*4]", "interface {}(*[]go/constant.Value) 0x…", "interface {}", nil}, {"ifacenil", true, "interface {} nil", "interface {} nil", "interface {}", nil}, {"err1 == err2", false, "false", "false", "", nil}, @@ -628,6 +628,16 @@ func TestEvalExpression(t *testing.T) { {"efacearr", false, `[]interface {} len: 3, cap: 3, [*main.astruct {A: 0, B: 0},*"test",nil]`, "[]interface {} len: 3, cap: 3, [...]", "[]interface {}", nil}, } + ver, _ := proc.ParseVersionString(runtime.Version()) + if ver.Major >= 0 && !ver.AfterOrEqual(proc.GoVersion{1, 7, -1, 0, 0}) { + for i := range testcases { + if testcases[i].name == "iface3" { + testcases[i].value = "interface {}(*map[string]go/constant.Value) *[]" + testcases[i].alternate = "interface {}(*map[string]go/constant.Value) 0x…" + } + } + } + withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue() returned an error") for _, tc := range testcases { @@ -761,3 +771,60 @@ func TestIssue426(t *testing.T) { } }) } + +func TestPackageRenames(t *testing.T) { + // Tests that the concrete type of an interface variable is resolved + // correctly in a few edge cases, in particular: + // - in the presence of renamed imports + // - when two packages with the same name are imported + // - when a package has a canonical name that's different from its + // path (for example the last element of the path contains a '.' or a + // '-' or because the package name is different) + // all of those edge cases are tested within composite types + testcases := []varTest{ + // Renamed imports + {"badexpr", true, `interface {}(*go/ast.BadExpr) *{From: 1, To: 2}`, "", "interface {}", nil}, + {"req", true, `interface {}(*net/http.Request) *{Method: "amethod", …`, "", "interface {}", nil}, + {"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil}, + + // Package name that doesn't match import path + {"iface3", true, `interface {}(*github.com/derekparker/delve/_fixtures/vendor/dir0/renamedpackage.SomeType) *{A: true}`, "", "interface {}", nil}, + + // Interfaces to anonymous types + {"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil}, + {"dir0someType", true, "interface {}(*github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType) *{X: 3}", "", "interface {}", nil}, + {"dir1someType", true, "interface {}(*github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType) *{X: 1, Y: 2}", "", "interface {}", nil}, + {"amap3", true, "interface {}(map[github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType]github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType) [{X: 4}: {X: 5, Y: 6}, ]", "", "interface {}", nil}, + {"anarray", true, `interface {}(*[2]github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType) *[{X: 1},{X: 2}]`, "", "interface {}", nil}, + {"achan", true, `interface {}(chan github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType) chan github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType 0/0`, "", "interface {}", nil}, + {"aslice", true, `interface {}(*[]github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType) *[{X: 3},{X: 4}]`, "", "interface {}", nil}, + {"afunc", true, `interface {}(func(github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType, github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType)) main.main.func1`, "", "interface {}", nil}, + {"astruct", true, `interface {}(*struct { A github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType; B github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType }) *{A: github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType {X: 1, Y: 2}, B: github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType {X: 3}}`, "", "interface {}", nil}, + {"astruct2", true, `interface {}(*struct { github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType; X int }) *{github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType: github.com/derekparker/delve/_fixtures/vendor/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil}, + {"iface2iface", true, `interface {}(*interface { AMethod(int) int; AnotherMethod(int) int }) **github.com/derekparker/delve/_fixtures/vendor/dir0/pkg.SomeType {X: 4}`, "", "interface {}", nil}, + } + + ver, _ := proc.ParseVersionString(runtime.Version()) + if ver.Major > 0 && !ver.AfterOrEqual(proc.GoVersion{1, 7, -1, 0, 0}) { + // Not supported on 1.6 or earlier + return + } + + withTestProcess("pkgrenames", t, func(p *proc.Process, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue() returned an error") + for _, tc := range testcases { + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) + if tc.err == nil { + assertNoError(err, t, fmt.Sprintf("EvalExpression(%s) returned an error", tc.name)) + assertVariable(t, variable, tc) + } else { + if err == nil { + t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name) + } + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + } + }) +}