diff --git a/_fixtures/testvariables3.go b/_fixtures/testvariables3.go index fe03556b..869dd84f 100644 --- a/_fixtures/testvariables3.go +++ b/_fixtures/testvariables3.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "go/constant" "runtime" "unsafe" ) @@ -109,15 +110,20 @@ func main() { var err2 error = c1.pb var errnil error = nil var iface1 interface{} = c1.sa[0] + var iface2 interface{} = "test" + var iface3 interface{} = map[string]constant.Value{} + var iface4 interface{} = []constant.Value{constant.MakeInt64(4)} var ifacenil interface{} = nil arr1 := [4]int{0, 1, 2, 3} parr := &arr1 cpx1 := complex(1, 2) + const1 := constant.MakeInt64(3) var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { fmt.Println(amb1) } - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, ifacenil, arr1, parr, cpx1) + runtime.Breakpoint() + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4) } diff --git a/proc/eval.go b/proc/eval.go index 7bc4e6f2..1c328470 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -932,15 +932,6 @@ func equalChildren(xv, yv *Variable, shortcircuit bool) (bool, error) { return r, nil } -func (dbp *Process) findType(name string) (dwarf.Type, error) { - reader := dbp.DwarfReader() - typentry, err := reader.SeekToTypeNamed(name) - if err != nil { - return nil, err - } - return dbp.dwarf.Type(typentry.Offset) -} - func (v *Variable) asInt() (int64, error) { if v.DwarfType == nil { if v.Value.Kind() != constant.Int { diff --git a/proc/proc.go b/proc/proc.go index ee450232..f4ec75cb 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -39,6 +39,9 @@ type Process struct { // Normally SelectedGoroutine is CurrentThread.GetG, it will not be only if SwitchGoroutine is called with a goroutine that isn't attached to a thread SelectedGoroutine *G + // Maps package names to package paths, needed to lookup types inside DWARF info + packageMap map[string]string + allGCache []*G dwarf *dwarf.Data goSymTable *gosym.Table diff --git a/proc/types.go b/proc/types.go new file mode 100644 index 00000000..c482716c --- /dev/null +++ b/proc/types.go @@ -0,0 +1,123 @@ +package proc + +import ( + "debug/dwarf" + "go/ast" + "strings" +) + +// 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) { + reader := dbp.DwarfReader() + typentry, err := reader.SeekToTypeNamed(name) + if err != nil { + return nil, err + } + return dbp.dwarf.Type(typentry.Offset) +} + +func (dbp *Process) pointerTo(typ dwarf.Type) dwarf.Type { + return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), ""}, typ} +} + +func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) { + dbp.loadPackageMap() + dbp.expandPackagesInType(expr) + if snode, ok := expr.(*ast.StarExpr); ok { + // Pointer types only appear in the dwarf informations when + // a pointer to the type is used in the target program, here + // we create a pointer type on the fly so that the user can + // specify a pointer to any variable used in the target program + ptyp, err := dbp.findType(exprToString(snode.X)) + if err != nil { + return nil, err + } + return dbp.pointerTo(ptyp), nil + } + return dbp.findType(exprToString(expr)) +} + +func complexType(typename string) bool { + dot := 0 + for _, ch := range typename { + switch ch { + case '*', '[', '<', '{', '(', ' ': + return true + case '.': + dot++ + if dot > 1 { + return true + } + } + } + return false +} + +func (dbp *Process) loadPackageMap() error { + if dbp.packageMap != nil { + return nil + } + dbp.packageMap = map[string]string{} + reader := dbp.DwarfReader() + for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { + if err != nil { + return err + } + + if entry.Tag != dwarf.TagTypedef && entry.Tag != dwarf.TagBaseType && entry.Tag != dwarf.TagClassType && entry.Tag != dwarf.TagStructType { + continue + } + + typename, ok := entry.Val(dwarf.AttrName).(string) + if !ok || complexType(typename) { + continue + } + + dot := strings.Index(typename, ".") + if dot < 0 { + continue + } + path := typename[:dot] + slash := strings.LastIndex(path, "/") + if slash < 0 || slash+1 >= len(path) { + continue + } + name := path[slash+1:] + dbp.packageMap[name] = path + } + return nil +} + +func (dbp *Process) expandPackagesInType(expr ast.Expr) { + switch e := expr.(type) { + case *ast.ArrayType: + dbp.expandPackagesInType(e.Elt) + case *ast.ChanType: + dbp.expandPackagesInType(e.Value) + case *ast.FuncType: + for i := range e.Params.List { + dbp.expandPackagesInType(e.Params.List[i].Type) + } + for i := range e.Results.List { + dbp.expandPackagesInType(e.Results.List[i].Type) + } + case *ast.MapType: + dbp.expandPackagesInType(e.Key) + dbp.expandPackagesInType(e.Value) + case *ast.ParenExpr: + dbp.expandPackagesInType(e.X) + case *ast.SelectorExpr: + switch x := e.X.(type) { + case *ast.Ident: + if path, ok := dbp.packageMap[x.Name]; ok { + x.Name = path + } + default: + dbp.expandPackagesInType(e.X) + } + case *ast.StarExpr: + dbp.expandPackagesInType(e.X) + default: + // nothing to do + } +} diff --git a/proc/variables.go b/proc/variables.go index 677c3ab0..cf5019b6 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -5,7 +5,6 @@ import ( "debug/dwarf" "encoding/binary" "fmt" - "go/ast" "go/constant" "go/parser" "go/token" @@ -1451,10 +1450,16 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) { typ, err := v.thread.dbp.findTypeExpr(t) if err != nil { - v.Unreadable = fmt.Errorf("invalid interface type: %v", err) + v.Unreadable = fmt.Errorf("interface type \"%s\" not found for 0x%x: %v", constant.StringVal(typestring.Value), 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.thread.dbp.pointerTo(typ) + } + data = newVariable("data", data.Addr, typ, data.thread) v.Children = []Variable{*data} @@ -1464,21 +1469,6 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) { return } -func (dbp *Process) findTypeExpr(expr ast.Expr) (dwarf.Type, error) { - if snode, ok := expr.(*ast.StarExpr); ok { - // Pointer types only appear in the dwarf informations when - // a pointer to the type is used in the target program, here - // we create a pointer type on the fly so that the user can - // specify a pointer to any variable used in the target program - ptyp, err := dbp.findType(exprToString(snode.X)) - if err != nil { - return nil, err - } - return &dwarf.PtrType{dwarf.CommonType{int64(dbp.arch.PtrSize()), exprToString(expr)}, ptyp}, nil - } - return dbp.findType(exprToString(expr)) -} - // Fetches all variables of a specific type in the current function scope func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) { reader := scope.DwarfReader() diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 4dbb4cbd..6a84bda6 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -404,6 +404,9 @@ func TestEvalExpression(t *testing.T) { {"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "", "error", nil}, {"errnil", true, "error nil", "", "error", nil}, {"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "", "interface {}", nil}, + {"iface2", true, "interface {}(*struct string) *\"test\"", "", "interface {}", nil}, + {"iface3", true, "interface {}(map[string]go/constant.Value) []", "", "interface {}", nil}, + {"iface4", true, "interface {}(*struct []go/constant.Value) *[*4]", "", "interface {}", nil}, {"ifacenil", true, "interface {} nil", "", "interface {}", nil}, {"err1 == err2", false, "false", "", "", nil}, {"err1 == iface1", false, "", "", "", fmt.Errorf("mismatched types \"error\" and \"interface {}\"")}, @@ -412,6 +415,7 @@ func TestEvalExpression(t *testing.T) { {"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "", "*struct main.astruct", nil}, {"err1.(*main.bstruct)", false, "", "", "", fmt.Errorf("interface conversion: error is *struct main.astruct, not *struct main.bstruct")}, {"errnil.(*main.astruct)", false, "", "", "", fmt.Errorf("interface conversion: error is nil, not *main.astruct")}, + {"const1", true, "go/constant.Value(*go/constant.int64Val) *3", "", "go/constant.Value", nil}, // combined expressions {"c1.pb.a.A", true, "1", "", "int", nil},