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()) + } + } + } + }) +}