diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index f7e7cff7..9dea8875 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -40,6 +40,10 @@ type B struct { ptr *A } +type D struct { + u1, u2, u3, u4, u5, u6 uint32 +} + func afunc(x int) int { return x + 2 } @@ -181,6 +185,7 @@ func main() { aas[0].aas = aas b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}} b2 := B{A: A{42}, a: A{47}} + var sd D for i := range benchparr { benchparr[i] = &benchstruct{} @@ -192,5 +197,5 @@ func main() { fmt.Println(amb1) } 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, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2) + 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, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd) } diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 0c81c542..c7dee2b2 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -259,7 +259,7 @@ func traceCmd(cmd *cobra.Command, args []string) { return 1 } for i := range funcs { - _, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth}) + _, err = client.CreateBreakpoint(&api.Breakpoint{FunctionName: funcs[i], Tracepoint: true, Line: -1, Stacktrace: traceStackDepth, LoadArgs: &terminal.ShortLoadConfig}) if err != nil { fmt.Fprintln(os.Stderr, err) return 1 diff --git a/proc/breakpoints.go b/proc/breakpoints.go index 235f2633..1b55e306 100644 --- a/proc/breakpoints.go +++ b/proc/breakpoints.go @@ -28,6 +28,8 @@ type Breakpoint struct { Goroutine bool // Retrieve goroutine information Stacktrace int // Number of stack frames to retrieve Variables []string // Variables to evaluate + LoadArgs *LoadConfig + LoadLocals *LoadConfig HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine TotalHitCount uint64 // Number of times a breakpoint has been reached diff --git a/proc/eval.go b/proc/eval.go index 1297c045..8bd6e807 100644 --- a/proc/eval.go +++ b/proc/eval.go @@ -16,7 +16,7 @@ import ( ) // EvalExpression returns the value of the given expression. -func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) { +func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) { t, err := parser.ParseExpr(expr) if err != nil { return nil, err @@ -26,7 +26,10 @@ func (scope *EvalScope) EvalExpression(expr string) (*Variable, error) { if err != nil { return nil, err } - ev.loadValue() + ev.loadValue(cfg) + if ev.Name == "" { + ev.Name = expr + } return ev, nil } @@ -113,7 +116,7 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { if err != nil { return nil, err } - argv.loadValue() + argv.loadValue(loadSingleValue) if argv.Unreadable != nil { return nil, argv.Unreadable } @@ -277,7 +280,7 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { case reflect.Slice: return newConstant(constant.MakeInt64(arg.Cap), arg.mem), nil case reflect.Chan: - arg.loadValue() + arg.loadValue(loadFullValue) if arg.Unreadable != nil { return nil, arg.Unreadable } @@ -310,7 +313,7 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { } return newConstant(constant.MakeInt64(arg.Len), arg.mem), nil case reflect.Chan: - arg.loadValue() + arg.loadValue(loadFullValue) if arg.Unreadable != nil { return nil, arg.Unreadable } @@ -340,8 +343,8 @@ func complexBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { realev := args[0] imagev := args[1] - realev.loadValue() - imagev.loadValue() + realev.loadValue(loadSingleValue) + imagev.loadValue(loadSingleValue) if realev.Unreadable != nil { return nil, realev.Unreadable @@ -387,7 +390,7 @@ func imagBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { } arg := args[0] - arg.loadValue() + arg.loadValue(loadSingleValue) if arg.Unreadable != nil { return nil, arg.Unreadable @@ -406,7 +409,7 @@ func realBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) { } arg := args[0] - arg.loadValue() + arg.loadValue(loadSingleValue) if arg.Unreadable != nil { return nil, arg.Unreadable @@ -470,7 +473,7 @@ func (scope *EvalScope) evalTypeAssert(node *ast.TypeAssertExpr) (*Variable, err if xv.Kind != reflect.Interface { return nil, fmt.Errorf("expression \"%s\" not an interface", exprToString(node.X)) } - xv.loadInterface(0, false) + xv.loadInterface(0, false, loadFullValue) if xv.Unreadable != nil { return nil, xv.Unreadable } @@ -517,7 +520,7 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) { return xev.sliceAccess(int(n)) case reflect.Map: - idxev.loadValue() + idxev.loadValue(loadFullValue) if idxev.Unreadable != nil { return nil, idxev.Unreadable } @@ -576,7 +579,7 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) { return nil, fmt.Errorf("second slice argument must be empty for maps") } xev.mapSkip += int(low) - xev.loadValue() + xev.loadValue(loadFullValue) if xev.Unreadable != nil { return nil, xev.Unreadable } @@ -675,7 +678,7 @@ func (scope *EvalScope) evalUnary(node *ast.UnaryExpr) (*Variable, error) { return nil, err } - xv.loadValue() + xv.loadValue(loadSingleValue) if xv.Unreadable != nil { return nil, xv.Unreadable } @@ -774,8 +777,8 @@ func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { return nil, err } - xv.loadValue() - yv.loadValue() + xv.loadValue(loadFullValue) + yv.loadValue(loadFullValue) if xv.Unreadable != nil { return nil, xv.Unreadable @@ -943,7 +946,7 @@ func (v *Variable) asInt() (int64, error) { return 0, fmt.Errorf("can not convert constant %s to int", v.Value) } } else { - v.loadValue() + v.loadValue(loadSingleValue) if v.Unreadable != nil { return 0, v.Unreadable } @@ -961,7 +964,7 @@ func (v *Variable) asUint() (uint64, error) { return 0, fmt.Errorf("can not convert constant %s to uint", v.Value) } } else { - v.loadValue() + v.loadValue(loadSingleValue) if v.Unreadable != nil { return 0, v.Unreadable } @@ -1048,7 +1051,7 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) { first := true for it.next() { key := it.key() - key.loadValue() + key.loadValue(loadFullValue) if key.Unreadable != nil { return nil, fmt.Errorf("can not access unreadable map: %v", key.Unreadable) } diff --git a/proc/proc.go b/proc/proc.go index 79e77a3a..b39c1f1a 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -805,7 +805,7 @@ func (dbp *Process) execPtraceFunc(fn func()) { } func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error) { - vv, err := dbp.EvalPackageVariable("runtime.buildVersion") + vv, err := dbp.EvalPackageVariable("runtime.buildVersion", LoadConfig{true, 0, 64, 0, 0}) if err != nil { err = fmt.Errorf("Could not determine version number: %v\n", err) return diff --git a/proc/proc_test.go b/proc/proc_test.go index f4ad7e34..7f3a5096 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -19,6 +19,8 @@ import ( protest "github.com/derekparker/delve/proc/test" ) +var normalLoadConfig = LoadConfig{true, 1, 64, 64, -1} + func init() { runtime.GOMAXPROCS(4) os.Setenv("GOMAXPROCS", "4") @@ -960,7 +962,7 @@ func evalVariable(p *Process, symbol string) (*Variable, error) { if err != nil { return nil, err } - return scope.EvalVariable(symbol) + return scope.EvalVariable(symbol, normalLoadConfig) } func setVariable(p *Process, symbol, value string) error { @@ -1085,7 +1087,7 @@ func TestFrameEvaluation(t *testing.T) { scope, err := p.ConvertEvalScope(g.ID, frame) assertNoError(err, t, "ConvertEvalScope()") t.Logf("scope = %v", scope) - v, err := scope.EvalVariable("i") + v, err := scope.EvalVariable("i", normalLoadConfig) t.Logf("v = %v", v) if err != nil { t.Logf("Goroutine %d: %v\n", g.ID, err) @@ -1109,7 +1111,7 @@ func TestFrameEvaluation(t *testing.T) { for i := 0; i <= 3; i++ { scope, err := p.ConvertEvalScope(g.ID, i+1) assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1)) - v, err := scope.EvalVariable("n") + v, err := scope.EvalVariable("n", normalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1)) n, _ := constant.Int64Val(v.Value) t.Logf("frame %d n %d\n", i+1, n) @@ -1138,7 +1140,7 @@ func TestPointerSetting(t *testing.T) { // change p1 to point to i2 scope, err := p.CurrentThread.Scope() assertNoError(err, t, "Scope()") - i2addr, err := scope.EvalExpression("i2") + i2addr, err := scope.EvalExpression("i2", normalLoadConfig) assertNoError(err, t, "EvalExpression()") assertNoError(setVariable(p, "p1", fmt.Sprintf("(*int)(0x%x)", i2addr.Addr)), t, "SetVariable()") pval(2) @@ -1278,10 +1280,10 @@ func TestBreakpointCountsWithDetection(t *testing.T) { } scope, err := th.Scope() assertNoError(err, t, "Scope()") - v, err := scope.EvalVariable("i") + v, err := scope.EvalVariable("i", normalLoadConfig) assertNoError(err, t, "evalVariable") i, _ := constant.Int64Val(v.Value) - v, err = scope.EvalVariable("id") + v, err = scope.EvalVariable("id", normalLoadConfig) assertNoError(err, t, "evalVariable") id, _ := constant.Int64Val(v.Value) m[id] = i @@ -1409,7 +1411,7 @@ func BenchmarkLocalVariables(b *testing.B) { scope, err := p.CurrentThread.Scope() assertNoError(err, b, "Scope()") for i := 0; i < b.N; i++ { - _, err := scope.LocalVariables() + _, err := scope.LocalVariables(normalLoadConfig) assertNoError(err, b, "LocalVariables()") } }) @@ -1634,7 +1636,7 @@ func TestPackageVariables(t *testing.T) { assertNoError(err, t, "Continue()") scope, err := p.CurrentThread.Scope() assertNoError(err, t, "Scope()") - vars, err := scope.PackageVariables() + vars, err := scope.PackageVariables(normalLoadConfig) assertNoError(err, t, "PackageVariables()") failed := false for _, v := range vars { diff --git a/proc/variables.go b/proc/variables.go index 73ac2898..768254e5 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -19,9 +19,7 @@ import ( ) const ( - maxVariableRecurse = 1 // How far to recurse when evaluating nested types. - maxArrayValues = 64 // Max value for reading large arrays. - maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs + maxErrCount = 3 // Max number of read errors to accept while evaluating slices, arrays and structs maxArrayStridePrefetch = 1024 // Maximum size of array stride for which we will prefetch the array contents @@ -68,6 +66,22 @@ type Variable struct { Unreadable error } +type LoadConfig struct { + // FollowPointers requests pointers to be automatically dereferenced. + FollowPointers bool + // MaxVariableRecurse is how far to recurse when evaluating nested types. + MaxVariableRecurse int + // MaxStringLen is the maximum number of bytes read from a string + MaxStringLen int + // MaxArrayValues is the maximum number of elements read from an array, a slice or a map. + MaxArrayValues int + // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. + MaxStructFields int +} + +var loadSingleValue = LoadConfig{false, 0, 64, 0, 0} +var loadFullValue = LoadConfig{true, 1, 64, 64, -1} + // M represents a runtime M (OS thread) structure. type M struct { procid int // Thread ID or port. @@ -250,6 +264,7 @@ func newConstant(val constant.Value, mem memoryReadWriter) *Variable { } var nilVariable = &Variable{ + Name: "nil", Addr: 0, Base: 0, Kind: reflect.Ptr, @@ -355,7 +370,7 @@ func (gvar *Variable) parseG() (*G, error) { } return nil, NoGError{ tid: id } } - gvar.loadValue() + gvar.loadValue(loadFullValue) if gvar.Unreadable != nil { return nil, gvar.Unreadable } @@ -393,7 +408,7 @@ func (v *Variable) toFieldNamed(name string) *Variable { if err != nil { return nil } - v.loadValue() + v.loadValue(loadFullValue) if v.Unreadable != nil { return nil } @@ -435,8 +450,8 @@ func (g *G) Go() Location { } // EvalVariable returns the value of the given expression (backwards compatibility). -func (scope *EvalScope) EvalVariable(name string) (*Variable, error) { - return scope.EvalExpression(name) +func (scope *EvalScope) EvalVariable(name string, cfg LoadConfig) (*Variable, error) { + return scope.EvalExpression(name, cfg) } // SetVariable sets the value of the named variable @@ -469,7 +484,7 @@ func (scope *EvalScope) SetVariable(name, value string) error { return err } - yv.loadValue() + yv.loadValue(loadSingleValue) if err := yv.isType(xv.RealType, xv.Kind); err != nil { return err @@ -482,13 +497,13 @@ func (scope *EvalScope) SetVariable(name, value string) error { return xv.setValue(yv) } -func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) { +func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry, cfg LoadConfig) (*Variable, error) { rdr := scope.DwarfReader() v, err := scope.extractVarInfoFromEntry(entry, rdr) if err != nil { return nil, err } - v.loadValue() + v.loadValue(cfg) return v, nil } @@ -518,17 +533,17 @@ func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) { } // LocalVariables returns all local variables from the current function scope. -func (scope *EvalScope) LocalVariables() ([]*Variable, error) { - return scope.variablesByTag(dwarf.TagVariable) +func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) { + return scope.variablesByTag(dwarf.TagVariable, cfg) } // FunctionArguments returns the name, value, and type of all current function arguments. -func (scope *EvalScope) FunctionArguments() ([]*Variable, error) { - return scope.variablesByTag(dwarf.TagFormalParameter) +func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) { + return scope.variablesByTag(dwarf.TagFormalParameter, cfg) } // PackageVariables returns the name, value, and type of all package variables in the application. -func (scope *EvalScope) PackageVariables() ([]*Variable, error) { +func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { var vars []*Variable reader := scope.DwarfReader() @@ -548,7 +563,7 @@ func (scope *EvalScope) PackageVariables() ([]*Variable, error) { } // Ignore errors trying to extract values - val, err := scope.extractVariableFromEntry(entry) + val, err := scope.extractVariableFromEntry(entry, cfg) if err != nil { continue } @@ -560,14 +575,14 @@ func (scope *EvalScope) PackageVariables() ([]*Variable, error) { // EvalPackageVariable will evaluate the package level variable // specified by 'name'. -func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) { +func (dbp *Process) EvalPackageVariable(name string, cfg LoadConfig) (*Variable, error) { scope := &EvalScope{Thread: dbp.CurrentThread, PC: 0, CFA: 0} v, err := scope.packageVarAddr(name) if err != nil { return nil, err } - v.loadValue() + v.loadValue(cfg) return v, nil } @@ -708,11 +723,11 @@ func (v *Variable) maybeDereference() *Variable { } // Extracts the value of the variable at the given address. -func (v *Variable) loadValue() { - v.loadValueInternal(0) +func (v *Variable) loadValue(cfg LoadConfig) { + v.loadValueInternal(0, cfg) } -func (v *Variable) loadValueInternal(recurseLevel int) { +func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.Base == 0) { return } @@ -722,30 +737,34 @@ func (v *Variable) loadValueInternal(recurseLevel int) { case reflect.Ptr, reflect.UnsafePointer: v.Len = 1 v.Children = []Variable{*v.maybeDereference()} - // Don't increase the recursion level when dereferencing pointers - v.Children[0].loadValueInternal(recurseLevel) + if cfg.FollowPointers { + // Don't increase the recursion level when dereferencing pointers + v.Children[0].loadValueInternal(recurseLevel, cfg) + } else { + v.Children[0].OnlyAddr = true + } case reflect.Chan: sv := v.clone() sv.RealType = resolveTypedef(&(sv.RealType.(*dwarf.ChanType).TypedefType)) sv = sv.maybeDereference() - sv.loadValueInternal(recurseLevel) + sv.loadValueInternal(0, loadFullValue) v.Children = sv.Children v.Len = sv.Len v.Base = sv.Addr case reflect.Map: - if recurseLevel <= maxVariableRecurse { - v.loadMap(recurseLevel) + if recurseLevel <= cfg.MaxVariableRecurse { + v.loadMap(recurseLevel, cfg) } case reflect.String: var val string - val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len) + val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len, cfg) v.Value = constant.MakeString(val) case reflect.Slice, reflect.Array: - v.loadArrayValues(recurseLevel) + v.loadArrayValues(recurseLevel, cfg) case reflect.Struct: v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size())) @@ -753,18 +772,21 @@ func (v *Variable) loadValueInternal(recurseLevel int) { v.Len = int64(len(t.Field)) // Recursively call extractValue to grab // the value of all the members of the struct. - if recurseLevel <= maxVariableRecurse { + if recurseLevel <= cfg.MaxVariableRecurse { v.Children = make([]Variable, 0, len(t.Field)) for i, field := range t.Field { + if cfg.MaxStructFields >= 0 && len(v.Children) >= cfg.MaxStructFields { + break + } f, _ := v.toField(field) v.Children = append(v.Children, *f) v.Children[i].Name = field.Name - v.Children[i].loadValueInternal(recurseLevel + 1) + v.Children[i].loadValueInternal(recurseLevel+1, cfg) } } case reflect.Interface: - v.loadInterface(recurseLevel, true) + v.loadInterface(recurseLevel, true, cfg) case reflect.Complex64, reflect.Complex128: v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) @@ -853,10 +875,10 @@ func readStringInfo(mem memoryReadWriter, arch Arch, addr uintptr) (uintptr, int return addr, strlen, nil } -func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64) (string, error) { +func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64, cfg LoadConfig) (string, error) { count := strlen - if count > maxArrayValues { - count = maxArrayValues + if count > int64(cfg.MaxStringLen) { + count = int64(cfg.MaxStringLen) } val, err := mem.readMemory(addr, int(count)) @@ -869,16 +891,6 @@ func readStringValue(mem memoryReadWriter, addr uintptr, strlen int64) (string, return retstr, nil } -func readString(mem memoryReadWriter, arch Arch, addr uintptr) (string, int64, error) { - addr, strlen, err := readStringInfo(mem, arch, addr) - if err != nil { - return "", 0, err - } - - retstr, err := readStringValue(mem, addr, strlen) - return retstr, strlen, err -} - func (v *Variable) loadSliceInfo(t *dwarf.SliceType) { v.mem = cacheMemory(v.mem, v.Addr, int(t.Size())) @@ -900,14 +912,14 @@ func (v *Variable) loadSliceInfo(t *dwarf.SliceType) { } case "len": lstrAddr, _ := v.toField(f) - lstrAddr.loadValue() + lstrAddr.loadValue(loadSingleValue) err = lstrAddr.Unreadable if err == nil { v.Len, _ = constant.Int64Val(lstrAddr.Value) } case "cap": cstrAddr, _ := v.toField(f) - cstrAddr.loadValue() + cstrAddr.loadValue(loadSingleValue) err = cstrAddr.Unreadable if err == nil { v.Cap, _ = constant.Int64Val(cstrAddr.Value) @@ -927,7 +939,7 @@ func (v *Variable) loadSliceInfo(t *dwarf.SliceType) { return } -func (v *Variable) loadArrayValues(recurseLevel int) { +func (v *Variable) loadArrayValues(recurseLevel int, cfg LoadConfig) { if v.Unreadable != nil { return } @@ -938,8 +950,8 @@ func (v *Variable) loadArrayValues(recurseLevel int) { count := v.Len // Cap number of elements - if count > maxArrayValues { - count = maxArrayValues + if count > int64(cfg.MaxArrayValues) { + count = int64(cfg.MaxArrayValues) } if v.stride < maxArrayStridePrefetch { @@ -950,7 +962,7 @@ func (v *Variable) loadArrayValues(recurseLevel int) { for i := int64(0); i < count; i++ { fieldvar := v.newVariable("", uintptr(int64(v.Base)+(i*v.stride)), v.fieldType) - fieldvar.loadValueInternal(recurseLevel + 1) + fieldvar.loadValueInternal(recurseLevel+1, cfg) if fieldvar.Unreadable != nil { errcount++ @@ -979,8 +991,8 @@ func (v *Variable) readComplex(size int64) { realvar := v.newVariable("real", v.Addr, ftyp) imagvar := v.newVariable("imaginary", v.Addr+uintptr(fs), ftyp) - realvar.loadValue() - imagvar.loadValue() + realvar.loadValue(loadSingleValue) + imagvar.loadValue(loadSingleValue) v.Value = constant.BinaryOp(realvar.Value, token.ADD, constant.MakeImag(imagvar.Value)) } @@ -1131,7 +1143,7 @@ func (v *Variable) readFunctionPtr() { v.Value = constant.MakeString(fn.Name) } -func (v *Variable) loadMap(recurseLevel int) { +func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) { it := v.mapIterator() if it == nil { return @@ -1147,13 +1159,13 @@ func (v *Variable) loadMap(recurseLevel int) { count := 0 errcount := 0 for it.next() { - if count >= maxArrayValues { + if count >= cfg.MaxArrayValues { break } key := it.key() val := it.value() - key.loadValueInternal(recurseLevel + 1) - val.loadValueInternal(recurseLevel + 1) + key.loadValueInternal(recurseLevel+1, cfg) + val.loadValueInternal(recurseLevel+1, cfg) if key.Unreadable != nil || val.Unreadable != nil { errcount++ } @@ -1381,7 +1393,7 @@ func mapEvacuated(b *Variable) bool { return true } -func (v *Variable) loadInterface(recurseLevel int, loadData bool) { +func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig) { var typestring, data *Variable isnil := false @@ -1431,7 +1443,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) { data = data.maybeDereference() v.Children = []Variable{*data} if loadData { - v.Children[0].loadValueInternal(recurseLevel) + v.Children[0].loadValueInternal(recurseLevel, cfg) } return } @@ -1440,7 +1452,7 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) { v.Unreadable = fmt.Errorf("invalid interface type") return } - typestring.loadValue() + typestring.loadValue(LoadConfig{false, 0, 512, 0, 0}) if typestring.Unreadable != nil { v.Unreadable = fmt.Errorf("invalid interface type: %v", typestring.Unreadable) return @@ -1468,13 +1480,15 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool) { v.Children = []Variable{*data} if loadData { - v.Children[0].loadValueInternal(recurseLevel) + v.Children[0].loadValueInternal(recurseLevel, cfg) + } else { + v.Children[0].OnlyAddr = true } return } // Fetches all variables of a specific type in the current function scope -func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) { +func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) { reader := scope.DwarfReader() _, err := reader.SeekToFunction(scope.PC) @@ -1489,7 +1503,7 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag) ([]*Variable, error) { } if entry.Tag == tag { - val, err := scope.extractVariableFromEntry(entry) + val, err := scope.extractVariableFromEntry(entry, cfg) if err != nil { // skip variables that we can't parse yet continue diff --git a/service/api/conversions.go b/service/api/conversions.go index 49f2014c..bf44f4ef 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -27,6 +27,8 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint { Stacktrace: bp.Stacktrace, Goroutine: bp.Goroutine, Variables: bp.Variables, + LoadArgs: LoadConfigFromProc(bp.LoadArgs), + LoadLocals: LoadConfigFromProc(bp.LoadLocals), TotalHitCount: bp.TotalHitCount, } @@ -217,3 +219,29 @@ func ConvertAsmInstruction(inst proc.AsmInstruction, text string) AsmInstruction AtPC: inst.AtPC, } } + +func LoadConfigToProc(cfg *LoadConfig) *proc.LoadConfig { + if cfg == nil { + return nil + } + return &proc.LoadConfig{ + cfg.FollowPointers, + cfg.MaxVariableRecurse, + cfg.MaxStringLen, + cfg.MaxArrayValues, + cfg.MaxStructFields, + } +} + +func LoadConfigFromProc(cfg *proc.LoadConfig) *LoadConfig { + if cfg == nil { + return nil + } + return &LoadConfig{ + cfg.FollowPointers, + cfg.MaxVariableRecurse, + cfg.MaxStringLen, + cfg.MaxArrayValues, + cfg.MaxStructFields, + } +} diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go index 38f48891..563dec80 100644 --- a/service/api/prettyprint.go +++ b/service/api/prettyprint.go @@ -24,7 +24,6 @@ func (v *Variable) SinglelineString() string { // MultilineString returns a representation of v on multiple lines. func (v *Variable) MultilineString(indent string) string { var buf bytes.Buffer - buf.WriteString(indent) v.writeTo(&buf, true, true, true, indent) return buf.String() } @@ -48,7 +47,7 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden case reflect.Ptr: if v.Type == "" { fmt.Fprintf(buf, "nil") - } else if v.Children[0].OnlyAddr { + } else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 { fmt.Fprintf(buf, "(%s)(0x%x)", v.Type, v.Children[0].Addr) } else { fmt.Fprintf(buf, "*") @@ -82,7 +81,18 @@ func (v *Variable) writeTo(buf io.Writer, top, newlines, includeType bool, inden fmt.Fprintf(buf, "%s(%s) ", v.Type, v.Children[0].Type) } } - v.Children[0].writeTo(buf, false, newlines, false, indent) + data := v.Children[0] + if data.Kind == reflect.Ptr { + if data.Children[0].Addr == 0 { + fmt.Fprintf(buf, "nil") + } else if data.Children[0].OnlyAddr { + fmt.Fprintf(buf, "0x%x", v.Children[0].Addr) + } else { + v.Children[0].writeTo(buf, false, newlines, false, indent) + } + } else { + v.Children[0].writeTo(buf, false, newlines, false, indent) + } case reflect.Map: v.writeMapTo(buf, newlines, includeType, indent) case reflect.Func: @@ -125,7 +135,7 @@ func (v *Variable) writeArrayTo(buf io.Writer, newlines, includeType bool, inden } func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, indent string) { - if int(v.Len) != len(v.Children) { + if int(v.Len) != len(v.Children) && len(v.Children) == 0 { fmt.Fprintf(buf, "(*%s)(0x%x)", v.Type, v.Addr) return } @@ -152,9 +162,15 @@ func (v *Variable) writeStructTo(buf io.Writer, newlines, includeType bool, inde } } - if nl { - fmt.Fprintf(buf, "\n%s", indent) + if len(v.Children) != int(v.Len) { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } else { + fmt.Fprintf(buf, ",") + } + fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children)) } + fmt.Fprintf(buf, "}") } @@ -184,12 +200,16 @@ func (v *Variable) writeMapTo(buf io.Writer, newlines, includeType bool, indent } if len(v.Children)/2 != int(v.Len) { - if nl { - fmt.Fprintf(buf, "\n%s%s", indent, indentString) + if len(v.Children) != 0 { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } else { + fmt.Fprintf(buf, ",") + } + fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2)) } else { - fmt.Fprintf(buf, ",") + fmt.Fprintf(buf, "...") } - fmt.Fprintf(buf, "...+%d more", int(v.Len)-(len(v.Children)/2)) } if nl { @@ -277,12 +297,16 @@ func (v *Variable) writeSliceOrArrayTo(buf io.Writer, newlines bool, indent stri } if len(v.Children) != int(v.Len) { - if nl { - fmt.Fprintf(buf, "\n%s%s", indent, indentString) + if len(v.Children) != 0 { + if nl { + fmt.Fprintf(buf, "\n%s%s", indent, indentString) + } else { + fmt.Fprintf(buf, ",") + } + fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children)) } else { - fmt.Fprintf(buf, ",") + fmt.Fprintf(buf, "...") } - fmt.Fprintf(buf, "...+%d more", int(v.Len)-len(v.Children)) } if nl { diff --git a/service/api/types.go b/service/api/types.go index f619a3b5..ea113c95 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -51,8 +51,12 @@ type Breakpoint struct { Goroutine bool `json:"goroutine"` // number of stack frames to retrieve Stacktrace int `json:"stacktrace"` - // variables to evaluate + // expressions to evaluate Variables []string `json:"variables,omitempty"` + // LoadArgs requests loading function arguments when the breakpoint is hit + LoadArgs *LoadConfig + // LoadLocals requests loading function locals when the breakpoint is hit + LoadLocals *LoadConfig // number of times a breakpoint has been reached in a certain goroutine HitCount map[string]uint64 `json:"hitCount"` // number of times a breakpoint has been reached @@ -129,10 +133,6 @@ type Function struct { Value uint64 `json:"value"` Type byte `json:"type"` GoType uint64 `json:"goType"` - // Args are the function arguments in a thread context. - Args []Variable `json:"args"` - // Locals are the thread local variables. - Locals []Variable `json:"locals"` } // Variable describes a variable. @@ -170,6 +170,20 @@ type Variable struct { Unreadable string `json:"unreadable"` } +// LoadConfig describes how to load values from target's memory +type LoadConfig struct { + // FollowPointers requests pointers to be automatically dereferenced. + FollowPointers bool + // MaxVariableRecurse is how far to recurse when evaluating nested types. + MaxVariableRecurse int + // MaxStringLen is the maximum number of bytes read from a string + MaxStringLen int + // MaxArrayValues is the maximum number of elements read from an array, a slice or a map. + MaxArrayValues int + // MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields. + MaxStructFields int +} + // Goroutine represents the information relevant to Delve from the runtime's // internal G structure. type Goroutine struct { @@ -201,6 +215,7 @@ type BreakpointInfo struct { Goroutine *Goroutine `json:"goroutine,omitempty"` Variables []Variable `json:"variables,omitempty"` Arguments []Variable `json:"arguments,omitempty"` + Locals []Variable `json:"locals,omitempty"` } type EvalScope struct { diff --git a/service/client.go b/service/client.go index 4e26c18a..1e328f23 100644 --- a/service/client.go +++ b/service/client.go @@ -56,9 +56,9 @@ type Client interface { GetThread(id int) (*api.Thread, error) // ListPackageVariables lists all package variables in the context of the current thread. - ListPackageVariables(filter string) ([]api.Variable, error) + ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error) // EvalVariable returns a variable in the context of the current thread. - EvalVariable(scope api.EvalScope, symbol string) (*api.Variable, error) + EvalVariable(scope api.EvalScope, symbol string, cfg api.LoadConfig) (*api.Variable, error) // SetVariable sets the value of a variable SetVariable(scope api.EvalScope, symbol, value string) error @@ -70,9 +70,9 @@ type Client interface { // ListTypes lists all types in the process matching filter. ListTypes(filter string) ([]string, error) // ListLocals lists all local variables in scope. - ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) + ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) // ListFunctionArgs lists all arguments to the current function. - ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) + ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) // ListRegisters lists registers and their values. ListRegisters() (string, error) @@ -80,7 +80,7 @@ type Client interface { ListGoroutines() ([]*api.Goroutine, error) // Returns stacktrace - Stacktrace(int, int, bool) ([]api.Stackframe, error) + Stacktrace(int, int, *api.LoadConfig) ([]api.Stackframe, error) // Returns whether we attached to a running process or not AttachedToExistingProcess() bool diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 0cdb5569..fea6e59e 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -249,6 +249,8 @@ func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err err bp.Goroutine = requested.Goroutine bp.Stacktrace = requested.Stacktrace bp.Variables = requested.Variables + bp.LoadArgs = api.LoadConfigToProc(requested.LoadArgs) + bp.LoadLocals = api.LoadConfigToProc(requested.LoadLocals) bp.Cond = nil if requested.Cond != "" { bp.Cond, err = parser.ParseExpr(requested.Cond) @@ -444,7 +446,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error if err != nil { return err } - bpi.Stacktrace, err = d.convertStacktrace(rawlocs, false) + bpi.Stacktrace, err = d.convertStacktrace(rawlocs, nil) if err != nil { return err } @@ -459,15 +461,21 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error bpi.Variables = make([]api.Variable, len(bp.Variables)) } for i := range bp.Variables { - v, err := s.EvalVariable(bp.Variables[i]) + v, err := s.EvalVariable(bp.Variables[i], proc.LoadConfig{true, 1, 64, 64, -1}) if err != nil { return err } bpi.Variables[i] = *api.ConvertVar(v) } - vars, err := s.FunctionArguments() - if err == nil { - bpi.Arguments = convertVars(vars) + if bp.LoadArgs != nil { + if vars, err := s.FunctionArguments(*api.LoadConfigToProc(bp.LoadArgs)); err == nil { + bpi.Arguments = convertVars(vars) + } + } + if bp.LoadLocals != nil { + if locals, err := s.LocalVariables(*api.LoadConfigToProc(bp.LoadLocals)); err == nil { + bpi.Locals = convertVars(locals) + } } } @@ -542,7 +550,7 @@ func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) { // PackageVariables returns a list of package variables for the thread, // optionally regexp filtered using regexp described in 'filter'. -func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable, error) { +func (d *Debugger) PackageVariables(threadID int, filter string, cfg proc.LoadConfig) ([]api.Variable, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -560,7 +568,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable if err != nil { return nil, err } - pv, err := scope.PackageVariables() + pv, err := scope.PackageVariables(cfg) if err != nil { return nil, err } @@ -597,7 +605,7 @@ func convertVars(pv []*proc.Variable) []api.Variable { } // LocalVariables returns a list of the local variables. -func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) { +func (d *Debugger) LocalVariables(scope api.EvalScope, cfg proc.LoadConfig) ([]api.Variable, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -605,7 +613,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) { if err != nil { return nil, err } - pv, err := s.LocalVariables() + pv, err := s.LocalVariables(cfg) if err != nil { return nil, err } @@ -613,7 +621,7 @@ func (d *Debugger) LocalVariables(scope api.EvalScope) ([]api.Variable, error) { } // FunctionArguments returns the arguments to the current function. -func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error) { +func (d *Debugger) FunctionArguments(scope api.EvalScope, cfg proc.LoadConfig) ([]api.Variable, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -621,7 +629,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error if err != nil { return nil, err } - pv, err := s.FunctionArguments() + pv, err := s.FunctionArguments(cfg) if err != nil { return nil, err } @@ -630,7 +638,7 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error // EvalVariableInScope will attempt to evaluate the variable represented by 'symbol' // in the scope provided. -func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) { +func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string, cfg proc.LoadConfig) (*api.Variable, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -638,7 +646,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api if err != nil { return nil, err } - v, err := s.EvalVariable(symbol) + v, err := s.EvalVariable(symbol, cfg) if err != nil { return nil, err } @@ -677,7 +685,7 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) { // Stacktrace returns a list of Stackframes for the given goroutine. The // length of the returned list will be min(stack_len, depth). // If 'full' is true, then local vars, function args, etc will be returned as well. -func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackframe, error) { +func (d *Debugger) Stacktrace(goroutineID, depth int, cfg *proc.LoadConfig) ([]api.Stackframe, error) { d.processMutex.Lock() defer d.processMutex.Unlock() @@ -697,21 +705,21 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, full bool) ([]api.Stackfra return nil, err } - return d.convertStacktrace(rawlocs, full) + return d.convertStacktrace(rawlocs, cfg) } -func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]api.Stackframe, error) { +func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadConfig) ([]api.Stackframe, error) { locations := make([]api.Stackframe, 0, len(rawlocs)) for i := range rawlocs { frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)} - if full { + if cfg != nil { var err error scope := rawlocs[i].Scope(d.process.CurrentThread) - locals, err := scope.LocalVariables() + locals, err := scope.LocalVariables(*cfg) if err != nil { return nil, err } - arguments, err := scope.FunctionArguments() + arguments, err := scope.FunctionArguments(*cfg) if err != nil { return nil, err } diff --git a/service/debugger/locations.go b/service/debugger/locations.go index acecbd1b..d37578dc 100644 --- a/service/debugger/locations.go +++ b/service/debugger/locations.go @@ -256,7 +256,7 @@ func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str } return []api.Location{{PC: uint64(addr)}}, nil } else { - v, err := scope.EvalExpression(loc.AddrExpr) + v, err := scope.EvalExpression(loc.AddrExpr, proc.LoadConfig{true, 0, 0, 0, 0}) if err != nil { return nil, err } diff --git a/service/rpc1/client.go b/service/rpc1/client.go index 0bd71ca6..0241a226 100644 --- a/service/rpc1/client.go +++ b/service/rpc1/client.go @@ -6,7 +6,6 @@ import ( "net/rpc" "net/rpc/jsonrpc" - "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" ) @@ -17,9 +16,6 @@ type RPCClient struct { client *rpc.Client } -// Ensure the implementation satisfies the interface. -var _ service.Client = &RPCClient{} - // NewClient creates a new RPCClient. func NewClient(addr string) *RPCClient { client, err := jsonrpc.Dial("tcp", addr) diff --git a/service/rpc1/server.go b/service/rpc1/server.go index ffc8e8ac..3c0c757b 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -9,11 +9,14 @@ import ( grpc "net/rpc" "net/rpc/jsonrpc" + "github.com/derekparker/delve/proc" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/debugger" ) +var defaultLoadConfig = proc.LoadConfig{ true, 1, 64, 64, -1 } + type ServerImpl struct { s *RPCServer } @@ -160,7 +163,11 @@ type StacktraceGoroutineArgs struct { } func (s *RPCServer) StacktraceGoroutine(args *StacktraceGoroutineArgs, locations *[]api.Stackframe) error { - locs, err := s.debugger.Stacktrace(args.Id, args.Depth, args.Full) + var loadcfg *proc.LoadConfig = nil + if args.Full { + loadcfg = &defaultLoadConfig + } + locs, err := s.debugger.Stacktrace(args.Id, args.Depth, loadcfg) if err != nil { return err } @@ -241,7 +248,7 @@ func (s *RPCServer) ListPackageVars(filter string, variables *[]api.Variable) er return fmt.Errorf("no current thread") } - vars, err := s.debugger.PackageVariables(current.ID, filter) + vars, err := s.debugger.PackageVariables(current.ID, filter, defaultLoadConfig) if err != nil { return err } @@ -263,7 +270,7 @@ func (s *RPCServer) ListThreadPackageVars(args *ThreadListArgs, variables *[]api return fmt.Errorf("no thread with id %d", args.Id) } - vars, err := s.debugger.PackageVariables(args.Id, args.Filter) + vars, err := s.debugger.PackageVariables(args.Id, args.Filter, defaultLoadConfig) if err != nil { return err } @@ -286,7 +293,7 @@ func (s *RPCServer) ListRegisters(arg interface{}, registers *string) error { } func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable) error { - vars, err := s.debugger.LocalVariables(scope) + vars, err := s.debugger.LocalVariables(scope, defaultLoadConfig) if err != nil { return err } @@ -295,7 +302,7 @@ func (s *RPCServer) ListLocalVars(scope api.EvalScope, variables *[]api.Variable } func (s *RPCServer) ListFunctionArgs(scope api.EvalScope, variables *[]api.Variable) error { - vars, err := s.debugger.FunctionArguments(scope) + vars, err := s.debugger.FunctionArguments(scope, defaultLoadConfig) if err != nil { return err } @@ -309,7 +316,7 @@ type EvalSymbolArgs struct { } func (s *RPCServer) EvalSymbol(args EvalSymbolArgs, variable *api.Variable) error { - v, err := s.debugger.EvalVariableInScope(args.Scope, args.Symbol) + v, err := s.debugger.EvalVariableInScope(args.Scope, args.Symbol, defaultLoadConfig) if err != nil { return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 9d79e7e2..017589dd 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -190,9 +190,9 @@ func (c *RPCClient) GetThread(id int) (*api.Thread, error) { return out.Thread, err } -func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string) (*api.Variable, error) { +func (c *RPCClient) EvalVariable(scope api.EvalScope, expr string, cfg api.LoadConfig) (*api.Variable, error) { var out EvalOut - err := c.call("Eval", EvalIn{scope, expr}, &out) + err := c.call("Eval", EvalIn{scope, expr, &cfg}, &out) return out.Variable, err } @@ -219,15 +219,15 @@ func (c *RPCClient) ListTypes(filter string) ([]string, error) { return types.Types, err } -func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) { +func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]api.Variable, error) { var out ListPackageVarsOut - err := c.call("ListPackageVars", ListPackageVarsIn{filter}, &out) + err := c.call("ListPackageVars", ListPackageVarsIn{filter, cfg}, &out) return out.Variables, err } -func (c *RPCClient) ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) { +func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) { var out ListLocalVarsOut - err := c.call("ListLocalVars", ListLocalVarsIn{scope}, &out) + err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out) return out.Variables, err } @@ -237,9 +237,9 @@ func (c *RPCClient) ListRegisters() (string, error) { return out.Registers, err } -func (c *RPCClient) ListFunctionArgs(scope api.EvalScope) ([]api.Variable, error) { +func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) { var out ListFunctionArgsOut - err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope}, &out) + err := c.call("ListFunctionArgs", ListFunctionArgsIn{scope, cfg}, &out) return out.Args, err } @@ -249,9 +249,9 @@ func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) { return out.Goroutines, err } -func (c *RPCClient) Stacktrace(goroutineId, depth int, full bool) ([]api.Stackframe, error) { +func (c *RPCClient) Stacktrace(goroutineId, depth int, cfg *api.LoadConfig) ([]api.Stackframe, error) { var out StacktraceOut - err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, full}, &out) + err := c.call("Stacktrace", StacktraceIn{goroutineId, depth, false, cfg}, &out) return out.Locations, err } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 4ee5b65c..cb62bddd 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -195,6 +195,7 @@ type StacktraceIn struct { Id int Depth int Full bool + Cfg *api.LoadConfig } type StacktraceOut struct { @@ -202,7 +203,11 @@ type StacktraceOut struct { } func (s *RPCServer) Stacktrace(arg StacktraceIn, out *StacktraceOut) error { - locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, arg.Full) + cfg := arg.Cfg + if cfg == nil && arg.Full { + cfg = &api.LoadConfig{ true, 1, 64, 64, -1 } + } + locs, err := s.debugger.Stacktrace(arg.Id, arg.Depth, api.LoadConfigToProc(cfg)) if err != nil { return err } @@ -314,6 +319,7 @@ func (s *RPCServer) GetThread(arg GetThreadIn, out *GetThreadOut) error { type ListPackageVarsIn struct { Filter string + Cfg api.LoadConfig } type ListPackageVarsOut struct { @@ -331,7 +337,7 @@ func (s *RPCServer) ListPackageVars(arg ListPackageVarsIn, out *ListPackageVarsO return fmt.Errorf("no current thread") } - vars, err := s.debugger.PackageVariables(current.ID, arg.Filter) + vars, err := s.debugger.PackageVariables(current.ID, arg.Filter, *api.LoadConfigToProc(&arg.Cfg)) if err != nil { return err } @@ -362,6 +368,7 @@ func (s *RPCServer) ListRegisters(arg ListRegistersIn, out *ListRegistersOut) er type ListLocalVarsIn struct { Scope api.EvalScope + Cfg api.LoadConfig } type ListLocalVarsOut struct { @@ -369,7 +376,7 @@ type ListLocalVarsOut struct { } func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) error { - vars, err := s.debugger.LocalVariables(arg.Scope) + vars, err := s.debugger.LocalVariables(arg.Scope, *api.LoadConfigToProc(&arg.Cfg)) if err != nil { return err } @@ -379,6 +386,7 @@ func (s *RPCServer) ListLocalVars(arg ListLocalVarsIn, out *ListLocalVarsOut) er type ListFunctionArgsIn struct { Scope api.EvalScope + Cfg api.LoadConfig } type ListFunctionArgsOut struct { @@ -386,7 +394,7 @@ type ListFunctionArgsOut struct { } func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionArgsOut) error { - vars, err := s.debugger.FunctionArguments(arg.Scope) + vars, err := s.debugger.FunctionArguments(arg.Scope, *api.LoadConfigToProc(&arg.Cfg)) if err != nil { return err } @@ -397,6 +405,7 @@ func (s *RPCServer) ListFunctionArgs(arg ListFunctionArgsIn, out *ListFunctionAr type EvalIn struct { Scope api.EvalScope Expr string + Cfg *api.LoadConfig } type EvalOut struct { @@ -404,7 +413,11 @@ type EvalOut struct { } func (s *RPCServer) Eval(arg EvalIn, out *EvalOut) error { - v, err := s.debugger.EvalVariableInScope(arg.Scope, arg.Expr) + cfg := arg.Cfg + if cfg == nil { + cfg = &api.LoadConfig{ true, 1, 64, 64, -1 } + } + v, err := s.debugger.EvalVariableInScope(arg.Scope, arg.Expr, *api.LoadConfigToProc(cfg)) if err != nil { return err } diff --git a/service/test/common_test.go b/service/test/common_test.go index f980dd5a..9000c53c 100644 --- a/service/test/common_test.go +++ b/service/test/common_test.go @@ -7,7 +7,6 @@ import ( "runtime" "testing" - "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" ) @@ -35,18 +34,6 @@ type nextTest struct { begin, end int } -func countBreakpoints(t *testing.T, c service.Client) int { - bps, err := c.ListBreakpoints() - assertNoError(err, t, "ListBreakpoints()") - bpcount := 0 - for _, bp := range bps { - if bp.ID >= 0 { - bpcount++ - } - } - return bpcount -} - func testProgPath(t *testing.T, name string) string { fp, err := filepath.Abs(fmt.Sprintf("_fixtures/%s.go", name)) if err != nil { @@ -61,7 +48,27 @@ func testProgPath(t *testing.T, name string) string { return fp } -func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { +type BreakpointLister interface { + ListBreakpoints() ([]*api.Breakpoint, error) +} + +func countBreakpoints(t *testing.T, c BreakpointLister) int { + bps, err := c.ListBreakpoints() + assertNoError(err, t, "ListBreakpoints()") + bpcount := 0 + for _, bp := range bps { + if bp.ID >= 0 { + bpcount++ + } + } + return bpcount +} + +type LocationFinder interface { + FindLocation(api.EvalScope, string) ([]api.Location, error) +} + +func findLocationHelper(t *testing.T, c LocationFinder, loc string, shouldErr bool, count int, checkAddr uint64) []uint64 { locs, err := c.FindLocation(api.EvalScope{-1, 0}, loc) t.Logf("FindLocation(\"%s\") → %v\n", loc, locs) diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 132ed941..27bea994 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -19,7 +19,7 @@ import ( "github.com/derekparker/delve/service/rpc1" ) -func withTestClient1(name string, t *testing.T, fn func(c service.Client)) { +func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { listener, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatalf("couldn't start listener: %s\n", err) @@ -56,7 +56,7 @@ func Test1RunWithInvalidPath(t *testing.T) { } func Test1Restart_afterExit(t *testing.T) { - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { origPid := c.ProcessPid() state := <-c.Continue() if !state.Exited { @@ -76,7 +76,7 @@ func Test1Restart_afterExit(t *testing.T) { } func Test1Restart_breakpointPreservation(t *testing.T) { - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true}) assertNoError(err, t, "CreateBreakpoint()") stateCh := c.Continue() @@ -105,7 +105,7 @@ func Test1Restart_breakpointPreservation(t *testing.T) { } func Test1Restart_duringStop(t *testing.T) { - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { origPid := c.ProcessPid() _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1}) if err != nil { @@ -144,7 +144,7 @@ func Test1Restart_attachPid(t *testing.T) { } func Test1ClientServer_exit(t *testing.T) { - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { state, err := c.GetState() if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -167,7 +167,7 @@ func Test1ClientServer_exit(t *testing.T) { } func Test1ClientServer_step(t *testing.T) { - withTestClient1("testprog", t, func(c service.Client) { + withTestClient1("testprog", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -190,7 +190,7 @@ func Test1ClientServer_step(t *testing.T) { } func testnext(testcases []nextTest, initialLocation string, t *testing.T) { - withTestClient1("testnextprog", t, func(c service.Client) { + withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: initialLocation, Line: -1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -281,7 +281,7 @@ func Test1NextFunctionReturn(t *testing.T) { } func Test1ClientServer_breakpointInMainThread(t *testing.T) { - withTestClient1("testprog", t, func(c service.Client) { + withTestClient1("testprog", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.helloworld", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -302,7 +302,7 @@ func Test1ClientServer_breakpointInMainThread(t *testing.T) { } func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) { - withTestClient1("testthreads", t, func(c service.Client) { + withTestClient1("testthreads", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.anotherthread", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -321,7 +321,7 @@ func Test1ClientServer_breakpointInSeparateGoroutine(t *testing.T) { } func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) { - withTestClient1("testprog", t, func(c service.Client) { + withTestClient1("testprog", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "nowhere", Line: 1}) if err == nil { t.Fatal("Should not be able to break at non existent function") @@ -330,7 +330,7 @@ func Test1ClientServer_breakAtNonexistentPoint(t *testing.T) { } func Test1ClientServer_clearBreakpoint(t *testing.T) { - withTestClient1("testprog", t, func(c service.Client) { + withTestClient1("testprog", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sleepytime", Line: 1}) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -356,7 +356,7 @@ func Test1ClientServer_clearBreakpoint(t *testing.T) { } func Test1ClientServer_switchThread(t *testing.T) { - withTestClient1("testnextprog", t, func(c service.Client) { + withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) { // With invalid thread id _, err := c.SwitchThread(-1) if err == nil { @@ -399,7 +399,7 @@ func Test1ClientServer_switchThread(t *testing.T) { } func Test1ClientServer_infoLocals(t *testing.T) { - withTestClient1("testnextprog", t, func(c service.Client) { + withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) { fp := testProgPath(t, "testnextprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23}) if err != nil { @@ -420,7 +420,7 @@ func Test1ClientServer_infoLocals(t *testing.T) { } func Test1ClientServer_infoArgs(t *testing.T) { - withTestClient1("testnextprog", t, func(c service.Client) { + withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) { fp := testProgPath(t, "testnextprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 47}) if err != nil { @@ -448,7 +448,7 @@ func Test1ClientServer_infoArgs(t *testing.T) { } func Test1ClientServer_traceContinue(t *testing.T) { - withTestClient1("integrationprog", t, func(c service.Client) { + withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) { fp := testProgPath(t, "integrationprog") _, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 15, Tracepoint: true, Goroutine: true, Stacktrace: 5, Variables: []string{"i"}}) if err != nil { @@ -505,7 +505,7 @@ func Test1ClientServer_traceContinue(t *testing.T) { } func Test1ClientServer_traceContinue2(t *testing.T) { - withTestClient1("integrationprog", t, func(c service.Client) { + withTestClient1("integrationprog", t, func(c *rpc1.RPCClient) { bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Tracepoint: true}) if err != nil { t.Fatalf("Unexpected error: %v\n", err) @@ -548,7 +548,7 @@ func Test1ClientServer_traceContinue2(t *testing.T) { } func Test1ClientServer_FindLocations(t *testing.T) { - withTestClient1("locationsprog", t, func(c service.Client) { + withTestClient1("locationsprog", t, func(c *rpc1.RPCClient) { someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0] someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0] findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1) @@ -595,17 +595,17 @@ func Test1ClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "-1", false, 1, findLocationHelper(t, c, "locationsprog.go:32", false, 1, 0)[0]) }) - withTestClient1("testnextdefer", t, func(c service.Client) { + withTestClient1("testnextdefer", t, func(c *rpc1.RPCClient) { firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0] findLocationHelper(t, c, "main.main", false, 1, firstMainLine) }) - withTestClient1("stacktraceprog", t, func(c service.Client) { + withTestClient1("stacktraceprog", t, func(c *rpc1.RPCClient) { stacktracemeAddr := findLocationHelper(t, c, "stacktraceprog.go:4", false, 1, 0)[0] findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) }) - withTestClient1("locationsUpperCase", t, func(c service.Client) { + withTestClient1("locationsUpperCase", t, func(c *rpc1.RPCClient) { // Upper case findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) @@ -645,7 +645,7 @@ func Test1ClientServer_FindLocations(t *testing.T) { } func Test1ClientServer_FindLocationsAddr(t *testing.T) { - withTestClient1("locationsprog2", t, func(c service.Client) { + withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) { <-c.Continue() afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] @@ -657,7 +657,7 @@ func Test1ClientServer_FindLocationsAddr(t *testing.T) { } func Test1ClientServer_EvalVariable(t *testing.T) { - withTestClient1("testvariables", t, func(c service.Client) { + withTestClient1("testvariables", t, func(c *rpc1.RPCClient) { state := <-c.Continue() if state.Err != nil { @@ -676,7 +676,7 @@ func Test1ClientServer_EvalVariable(t *testing.T) { } func Test1ClientServer_SetVariable(t *testing.T) { - withTestClient1("testvariables", t, func(c service.Client) { + withTestClient1("testvariables", t, func(c *rpc1.RPCClient) { state := <-c.Continue() if state.Err != nil { @@ -698,7 +698,7 @@ func Test1ClientServer_SetVariable(t *testing.T) { } func Test1ClientServer_FullStacktrace(t *testing.T) { - withTestClient1("goroutinestackprog", t, func(c service.Client) { + withTestClient1("goroutinestackprog", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.stacktraceme", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") state := <-c.Continue() @@ -771,7 +771,7 @@ func Test1ClientServer_FullStacktrace(t *testing.T) { func Test1Issue355(t *testing.T) { // After the target process has terminated should return an error but not crash - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() @@ -832,7 +832,7 @@ func Test1Disasm(t *testing.T) { // Tests that disassembly by current PC will return a disassembly containing the instruction at PC // Tests that stepping on a calculated CALL instruction will yield a disassembly that contains the // effective destination of the CALL instruction - withTestClient1("locationsprog2", t, func(c service.Client) { + withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) { ch := c.Continue() state := <-ch assertNoError(state.Err, t, "Continue()") @@ -934,7 +934,7 @@ func Test1Disasm(t *testing.T) { func Test1NegativeStackDepthBug(t *testing.T) { // After the target process has terminated should return an error but not crash - withTestClient1("continuetestprog", t, func(c service.Client) { + withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() @@ -946,7 +946,7 @@ func Test1NegativeStackDepthBug(t *testing.T) { } func Test1ClientServer_CondBreakpoint(t *testing.T) { - withTestClient1("parallel_next", t, func(c service.Client) { + withTestClient1("parallel_next", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) assertNoError(err, t, "CreateBreakpoint()") bp.Cond = "n == 7" @@ -976,7 +976,7 @@ func Test1ClientServer_CondBreakpoint(t *testing.T) { } func Test1SkipPrologue(t *testing.T) { - withTestClient1("locationsprog2", t, func(c service.Client) { + withTestClient1("locationsprog2", t, func(c *rpc1.RPCClient) { <-c.Continue() afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0] @@ -992,7 +992,7 @@ func Test1SkipPrologue(t *testing.T) { } func Test1SkipPrologue2(t *testing.T) { - withTestClient1("callme", t, func(c service.Client) { + withTestClient1("callme", t, func(c *rpc1.RPCClient) { callme := findLocationHelper(t, c, "main.callme", false, 1, 0)[0] callmeZ := findLocationHelper(t, c, "main.callme:0", false, 1, 0)[0] findLocationHelper(t, c, "callme.go:5", false, 1, callme) @@ -1020,7 +1020,7 @@ func Test1SkipPrologue2(t *testing.T) { func Test1Issue419(t *testing.T) { // Calling service/rpc.(*Client).Halt could cause a crash because both Halt and Continue simultaneously // try to read 'runtime.g' and debug/dwarf.Data.Type is not thread safe - withTestClient1("issue419", t, func(c service.Client) { + withTestClient1("issue419", t, func(c *rpc1.RPCClient) { go func() { rand.Seed(time.Now().Unix()) d := time.Duration(rand.Intn(4) + 1) @@ -1035,7 +1035,7 @@ func Test1Issue419(t *testing.T) { } func Test1TypesCommand(t *testing.T) { - withTestClient1("testvariables2", t, func(c service.Client) { + withTestClient1("testvariables2", t, func(c *rpc1.RPCClient) { state := <-c.Continue() assertNoError(state.Err, t, "Continue()") types, err := c.ListTypes("") @@ -1061,7 +1061,7 @@ func Test1TypesCommand(t *testing.T) { } func Test1Issue406(t *testing.T) { - withTestClient1("issue406", t, func(c service.Client) { + withTestClient1("issue406", t, func(c *rpc1.RPCClient) { locs, err := c.FindLocation(api.EvalScope{-1, 0}, "issue406.go:146") assertNoError(err, t, "FindLocation()") _, err = c.CreateBreakpoint(&api.Breakpoint{Addr: locs[0].PC}) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 209d80f8..d8d36c94 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" "time" + "os" protest "github.com/derekparker/delve/proc/test" @@ -19,6 +20,12 @@ import ( "github.com/derekparker/delve/service/rpc2" ) +var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} + +func TestMain(m *testing.M) { + os.Exit(protest.RunTestsWithFixtures(m)) +} + func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { listener, err := net.Listen("tcp", "localhost:0") if err != nil { @@ -409,7 +416,7 @@ func TestClientServer_infoLocals(t *testing.T) { if state.Err != nil { t.Fatalf("Unexpected error: %v, state: %#v", state.Err, state) } - locals, err := c.ListLocalVariables(api.EvalScope{-1, 0}) + locals, err := c.ListLocalVariables(api.EvalScope{-1, 0}, normalLoadConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -437,7 +444,7 @@ func TestClientServer_infoArgs(t *testing.T) { if regs == "" { t.Fatal("Expected string showing registers values, got empty string") } - locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0}) + locals, err := c.ListFunctionArgs(api.EvalScope{-1, 0}, normalLoadConfig) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -664,7 +671,7 @@ func TestClientServer_EvalVariable(t *testing.T) { t.Fatalf("Continue(): %v\n", state.Err) } - var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1") + var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1", normalLoadConfig) assertNoError(err, t, "EvalVariable") t.Logf("var1: %s", var1.SinglelineString()) @@ -685,7 +692,7 @@ func TestClientServer_SetVariable(t *testing.T) { assertNoError(c.SetVariable(api.EvalScope{-1, 0}, "a2", "8"), t, "SetVariable()") - a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2") + a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2", normalLoadConfig) t.Logf("a2: %v", a2) @@ -710,7 +717,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { assertNoError(err, t, "GoroutinesInfo()") found := make([]bool, 10) for _, g := range gs { - frames, err := c.Stacktrace(g.ID, 10, true) + frames, err := c.Stacktrace(g.ID, 10, &normalLoadConfig) assertNoError(err, t, fmt.Sprintf("Stacktrace(%d)", g.ID)) for i, frame := range frames { if frame.Function == nil { @@ -744,7 +751,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { t.Fatalf("Continue(): %v\n", state.Err) } - frames, err := c.Stacktrace(-1, 10, true) + frames, err := c.Stacktrace(-1, 10, &normalLoadConfig) assertNoError(err, t, "Stacktrace") cur := 3 @@ -810,15 +817,15 @@ func TestIssue355(t *testing.T) { _, err = c.GetThread(tid) assertError(err, t, "GetThread()") assertError(c.SetVariable(api.EvalScope{gid, 0}, "a", "10"), t, "SetVariable()") - _, err = c.ListLocalVariables(api.EvalScope{gid, 0}) + _, err = c.ListLocalVariables(api.EvalScope{gid, 0}, normalLoadConfig) assertError(err, t, "ListLocalVariables()") - _, err = c.ListFunctionArgs(api.EvalScope{gid, 0}) + _, err = c.ListFunctionArgs(api.EvalScope{gid, 0}, normalLoadConfig) assertError(err, t, "ListFunctionArgs()") _, err = c.ListRegisters() assertError(err, t, "ListRegisters()") _, err = c.ListGoroutines() assertError(err, t, "ListGoroutines()") - _, err = c.Stacktrace(gid, 10, false) + _, err = c.Stacktrace(gid, 10, &normalLoadConfig) assertError(err, t, "Stacktrace()") _, err = c.FindLocation(api.EvalScope{gid, 0}, "+1") assertError(err, t, "FindLocation()") @@ -940,7 +947,7 @@ func TestNegativeStackDepthBug(t *testing.T) { ch := c.Continue() state := <-ch assertNoError(state.Err, t, "Continue()") - _, err = c.Stacktrace(-1, -2, true) + _, err = c.Stacktrace(-1, -2, &normalLoadConfig) assertError(err, t, "Stacktrace()") }) } @@ -966,7 +973,7 @@ func TestClientServer_CondBreakpoint(t *testing.T) { state := <-c.Continue() assertNoError(state.Err, t, "Continue()") - nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n") + nvar, err := c.EvalVariable(api.EvalScope{-1, 0}, "n", normalLoadConfig) assertNoError(err, t, "EvalVariable()") if nvar.SinglelineString() != "7" { @@ -1069,9 +1076,27 @@ func TestIssue406(t *testing.T) { ch := c.Continue() state := <-ch assertNoError(state.Err, t, "Continue()") - v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree") + v, err := c.EvalVariable(api.EvalScope{-1, 0}, "cfgtree", normalLoadConfig) assertNoError(err, t, "EvalVariable()") vs := v.MultilineString("") t.Logf("cfgtree formats to: %s\n", vs) }) } + +func TestEvalExprName(t *testing.T) { + withTestClient2("testvariables2", t, func(c service.Client) { + state := <-c.Continue() + assertNoError(state.Err, t, "Continue()") + + var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "i1+1", normalLoadConfig) + assertNoError(err, t, "EvalVariable") + + const name = "i1+1" + + t.Logf("i1+1 → %#v", var1) + + if var1.Name != name { + t.Fatalf("Wrong variable name %q, expected %q", var1.Name, name) + } + }) +} diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 695f40ef..505385bd 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -12,11 +12,14 @@ import ( protest "github.com/derekparker/delve/proc/test" ) +var pnormalLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1} +var pshortLoadConfig = proc.LoadConfig{false, 0, 64, 0, 3} + type varTest struct { name string preserveName bool value string - setTo string + alternate string varType string err error } @@ -49,21 +52,17 @@ func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { } } -func evalVariable(p *proc.Process, symbol string) (*proc.Variable, error) { +func evalVariable(p *proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) { scope, err := p.CurrentThread.Scope() if err != nil { return nil, err } - return scope.EvalVariable(symbol) + return scope.EvalVariable(symbol, cfg) } -func (tc *varTest) settable() bool { - return tc.setTo != "" -} - -func (tc *varTest) afterSet() varTest { +func (tc *varTest) alternateVarTest() varTest { r := *tc - r.value = r.setTo + r.value = r.alternate return r } @@ -142,7 +141,7 @@ func TestVariableEvaluation(t *testing.T) { assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { - variable, err := evalVariable(p, tc.name) + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) if tc.err == nil { assertNoError(err, t, "EvalVariable() returned an error") assertVariable(t, variable, tc) @@ -155,14 +154,14 @@ func TestVariableEvaluation(t *testing.T) { } } - if tc.settable() { - assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()") - variable, err = evalVariable(p, tc.name) + if tc.alternate != "" { + assertNoError(setVariable(p, tc.name, tc.alternate), t, "SetVariable()") + variable, err = evalVariable(p, tc.name, pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") - assertVariable(t, variable, tc.afterSet()) + assertVariable(t, variable, tc.alternateVarTest()) assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()") - variable, err := evalVariable(p, tc.name) + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") assertVariable(t, variable, tc) } @@ -170,6 +169,72 @@ func TestVariableEvaluation(t *testing.T) { }) } +func TestVariableEvaluationShort(t *testing.T) { + testcases := []varTest{ + {"a1", true, "\"foofoofoofoofoofoo\"", "", "string", nil}, + {"a11", true, "[3]main.FooBar [...]", "", "[3]main.FooBar", nil}, + {"a12", true, "[]main.FooBar len: 2, cap: 2, [...]", "", "[]main.FooBar", nil}, + {"a13", true, "[]*main.FooBar len: 3, cap: 3, [...]", "", "[]*main.FooBar", nil}, + {"a2", true, "6", "", "int", nil}, + {"a3", true, "7.23", "", "float64", nil}, + {"a4", true, "[2]int [...]", "", "[2]int", nil}, + {"a5", true, "[]int len: 5, cap: 5, [...]", "", "[]int", nil}, + {"a6", true, "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil}, + {"a7", true, "(*main.FooBar)(0x…", "", "*main.FooBar", nil}, + {"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, + {"a9", true, "*main.FooBar nil", "", "*main.FooBar", nil}, + {"baz", true, "\"bazburzum\"", "", "string", nil}, + {"neg", true, "-1", "", "int", nil}, + {"f32", true, "1.2", "", "float32", nil}, + {"c64", true, "(1 + 2i)", "", "complex64", nil}, + {"c128", true, "(2 + 3i)", "", "complex128", nil}, + {"a6.Baz", true, "8", "", "int", nil}, + {"a7.Baz", true, "5", "", "int", nil}, + {"a8.Baz", true, "\"feh\"", "", "string", nil}, + {"a9.Baz", true, "nil", "", "int", fmt.Errorf("a9 is nil")}, + {"a9.NonExistent", true, "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")}, + {"a8", true, "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member + {"i32", true, "[2]int32 [...]", "", "[2]int32", nil}, + {"b1", true, "true", "false", "bool", nil}, + {"b2", true, "false", "true", "bool", nil}, + {"i8", true, "1", "2", "int8", nil}, + {"u16", true, "65535", "0", "uint16", nil}, + {"u32", true, "4294967295", "1", "uint32", nil}, + {"u64", true, "18446744073709551615", "2", "uint64", nil}, + {"u8", true, "255", "3", "uint8", nil}, + {"up", true, "5", "4", "uintptr", nil}, + {"f", true, "main.barfoo", "", "func()", nil}, + {"ba", true, "[]int len: 200, cap: 200, [...]", "", "[]int", nil}, + {"ms", true, "main.Nest {Level: 0, Nest: (*main.Nest)(0x…", "", "main.Nest", nil}, + {"ms.Nest.Nest", true, "(*main.Nest)(0x…", "", "*main.Nest", nil}, + {"ms.Nest.Nest.Nest.Nest.Nest", true, "*main.Nest nil", "", "*main.Nest", nil}, + {"ms.Nest.Nest.Nest.Nest.Nest.Nest", true, "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")}, + {"main.p1", true, "10", "", "int", nil}, + {"p1", true, "10", "", "int", nil}, + {"NonExistent", true, "", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, + } + + withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) { + err := p.Continue() + assertNoError(err, t, "Continue() returned an error") + + for _, tc := range testcases { + variable, err := evalVariable(p, tc.name, pshortLoadConfig) + if tc.err == nil { + assertNoError(err, t, "EvalVariable() returned an error") + assertVariable(t, variable, tc) + } else { + if err == nil { + t.Fatalf("Expected error %s, got no error: %s\n", tc.err.Error(), api.ConvertVar(variable).SinglelineString()) + } + if tc.err.Error() != err.Error() { + t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) + } + } + } + }) +} + func TestMultilineVariableEvaluation(t *testing.T) { testcases := []varTest{ {"a1", true, "\"foofoofoofoofoofoo\"", "", "string", nil}, @@ -209,7 +274,7 @@ func TestMultilineVariableEvaluation(t *testing.T) { assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { - variable, err := evalVariable(p, tc.name) + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) assertNoError(err, t, "EvalVariable() returned an error") if ms := api.ConvertVar(variable).MultilineString(""); !matchStringOrPrefix(ms, tc.value) { t.Fatalf("Expected %s got %s (variable %s)\n", tc.value, ms, variable.Name) @@ -237,7 +302,7 @@ func (s varArray) Less(i, j int) bool { func TestLocalVariables(t *testing.T) { testcases := []struct { - fn func(*proc.EvalScope) ([]*proc.Variable, error) + fn func(*proc.EvalScope, proc.LoadConfig) ([]*proc.Variable, error) output []varTest }{ {(*proc.EvalScope).LocalVariables, @@ -284,7 +349,7 @@ func TestLocalVariables(t *testing.T) { for _, tc := range testcases { scope, err := p.CurrentThread.Scope() assertNoError(err, t, "AsScope()") - vars, err := tc.fn(scope) + vars, err := tc.fn(scope, pnormalLoadConfig) assertNoError(err, t, "LocalVariables() returned an error") sort.Sort(varArray(vars)) @@ -303,21 +368,24 @@ func TestLocalVariables(t *testing.T) { func TestEmbeddedStruct(t *testing.T) { withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { testcases := []varTest{ - {"b.val", true, "-314", "", "int", nil}, - {"b.A.val", true, "-314", "", "int", nil}, - {"b.a.val", true, "42", "", "int", nil}, - {"b.ptr.val", true, "1337", "", "int", nil}, - {"b.C.s", true, "\"hello\"", "", "string", nil}, - {"b.s", true, "\"hello\"", "", "string", nil}, - {"b2", true, "main.B {main.A: struct main.A {val: 42}, *main.C: *struct main.C nil, a: main.A {val: 47}, ptr: *main.A nil}", "", "main.B", nil}, + {"b.val", true, "-314", "-314", "int", nil}, + {"b.A.val", true, "-314", "-314", "int", nil}, + {"b.a.val", true, "42", "42", "int", nil}, + {"b.ptr.val", true, "1337", "1337", "int", nil}, + {"b.C.s", true, "\"hello\"", "\"hello\"", "string", nil}, + {"b.s", true, "\"hello\"", "\"hello\"", "string", nil}, + {"b2", true, "main.B {main.A: struct main.A {val: 42}, *main.C: *struct main.C nil, a: main.A {val: 47}, ptr: *main.A nil}", "main.B {main.A: (*struct main.A)(0x…", "main.B", nil}, } assertNoError(p.Continue(), t, "Continue()") for _, tc := range testcases { - variable, err := evalVariable(p, tc.name) + variable, err := evalVariable(p, tc.name, pnormalLoadConfig) if tc.err == nil { - assertNoError(err, t, "EvalVariable() returned an error") + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s) returned an error", tc.name)) assertVariable(t, variable, tc) + variable, err = evalVariable(p, tc.name, pshortLoadConfig) + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s, pshortLoadConfig) returned an error", tc.name)) + assertVariable(t, variable, tc.alternateVarTest()) } else { if tc.err.Error() != err.Error() { t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) @@ -334,7 +402,7 @@ func TestComplexSetting(t *testing.T) { h := func(setExpr, value string) { assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()") - variable, err := evalVariable(p, "c128") + variable, err := evalVariable(p, "c128", pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") if s := api.ConvertVar(variable).SinglelineString(); s != value { t.Fatalf("Wrong value of c128: \"%s\", expected \"%s\" after setting it to \"%s\"", s, value, setExpr) @@ -351,171 +419,171 @@ func TestComplexSetting(t *testing.T) { func TestEvalExpression(t *testing.T) { testcases := []varTest{ // slice/array/string subscript - {"s1[0]", false, "\"one\"", "", "string", nil}, - {"s1[1]", false, "\"two\"", "", "string", nil}, - {"s1[2]", false, "\"three\"", "", "string", nil}, - {"s1[3]", false, "\"four\"", "", "string", nil}, - {"s1[4]", false, "\"five\"", "", "string", nil}, + {"s1[0]", false, "\"one\"", "\"one\"", "string", nil}, + {"s1[1]", false, "\"two\"", "\"two\"", "string", nil}, + {"s1[2]", false, "\"three\"", "\"three\"", "string", nil}, + {"s1[3]", false, "\"four\"", "\"four\"", "string", nil}, + {"s1[4]", false, "\"five\"", "\"five\"", "string", nil}, {"s1[5]", false, "", "", "string", fmt.Errorf("index out of bounds")}, - {"a1[0]", false, "\"one\"", "", "string", nil}, - {"a1[1]", false, "\"two\"", "", "string", nil}, - {"a1[2]", false, "\"three\"", "", "string", nil}, - {"a1[3]", false, "\"four\"", "", "string", nil}, - {"a1[4]", false, "\"five\"", "", "string", nil}, + {"a1[0]", false, "\"one\"", "\"one\"", "string", nil}, + {"a1[1]", false, "\"two\"", "\"two\"", "string", nil}, + {"a1[2]", false, "\"three\"", "\"three\"", "string", nil}, + {"a1[3]", false, "\"four\"", "\"four\"", "string", nil}, + {"a1[4]", false, "\"five\"", "\"five\"", "string", nil}, {"a1[5]", false, "", "", "string", fmt.Errorf("index out of bounds")}, - {"str1[0]", false, "48", "", "byte", nil}, - {"str1[1]", false, "49", "", "byte", nil}, - {"str1[2]", false, "50", "", "byte", nil}, - {"str1[10]", false, "48", "", "byte", nil}, + {"str1[0]", false, "48", "48", "byte", nil}, + {"str1[1]", false, "49", "49", "byte", nil}, + {"str1[2]", false, "50", "50", "byte", nil}, + {"str1[10]", false, "48", "48", "byte", nil}, {"str1[11]", false, "", "", "byte", fmt.Errorf("index out of bounds")}, // slice/array/string reslicing - {"a1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "", "[]string", nil}, - {"s1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "", "[]string", nil}, - {"str1[2:4]", false, "\"23\"", "", "string", nil}, - {"str1[0:11]", false, "\"01234567890\"", "", "string", nil}, - {"str1[:3]", false, "\"012\"", "", "string", nil}, - {"str1[3:]", false, "\"34567890\"", "", "string", nil}, + {"a1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "[]string len: 2, cap: 2, [...]", "[]string", nil}, + {"s1[2:4]", false, "[]string len: 2, cap: 2, [\"three\",\"four\"]", "[]string len: 2, cap: 2, [...]", "[]string", nil}, + {"str1[2:4]", false, "\"23\"", "\"23\"", "string", nil}, + {"str1[0:11]", false, "\"01234567890\"", "\"01234567890\"", "string", nil}, + {"str1[:3]", false, "\"012\"", "\"012\"", "string", nil}, + {"str1[3:]", false, "\"34567890\"", "\"34567890\"", "string", nil}, {"str1[0:12]", false, "", "", "string", fmt.Errorf("index out of bounds")}, {"str1[5:3]", false, "", "", "string", fmt.Errorf("index out of bounds")}, // pointers - {"*p2", false, "5", "", "int", nil}, - {"p2", true, "*5", "", "*int", nil}, - {"p3", true, "*int nil", "", "*int", nil}, + {"*p2", false, "5", "5", "int", nil}, + {"p2", true, "*5", "(*int)(0x…", "*int", nil}, + {"p3", true, "*int nil", "*int nil", "*int", nil}, {"*p3", false, "", "", "int", fmt.Errorf("nil pointer dereference")}, // channels - {"ch1", true, "chan int 0/2", "", "chan int", nil}, - {"chnil", true, "chan int nil", "", "chan int", nil}, + {"ch1", true, "chan int 0/2", "chan int 0/2", "chan int", nil}, + {"chnil", true, "chan int nil", "chan int nil", "chan int", nil}, {"ch1+1", false, "", "", "", fmt.Errorf("can not convert 1 constant to chan int")}, // maps - {"m1[\"Malone\"]", false, "struct main.astruct {A: 2, B: 3}", "", "struct main.astruct", nil}, - {"m2[1].B", false, "11", "", "int", nil}, - {"m2[c1.sa[2].B-4].A", false, "10", "", "int", nil}, - {"m2[*p1].B", false, "11", "", "int", nil}, - {"m3[as1]", false, "42", "", "int", nil}, + {"m1[\"Malone\"]", false, "struct main.astruct {A: 2, B: 3}", "struct main.astruct {A: 2, B: 3}", "struct main.astruct", nil}, + {"m2[1].B", false, "11", "11", "int", nil}, + {"m2[c1.sa[2].B-4].A", false, "10", "10", "int", nil}, + {"m2[*p1].B", false, "11", "11", "int", nil}, + {"m3[as1]", false, "42", "42", "int", nil}, {"mnil[\"Malone\"]", false, "", "", "", fmt.Errorf("key not found")}, {"m1[80:]", false, "", "", "", fmt.Errorf("map index out of bounds")}, // interfaces - {"err1", true, "error(*struct main.astruct) *{A: 1, B: 2}", "", "error", nil}, - {"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 {}(*string) *\"test\"", "", "interface {}", nil}, - {"iface3", true, "interface {}(*map[string]go/constant.Value) *[]", "", "interface {}", nil}, - {"iface4", true, "interface {}(*[]go/constant.Value) *[*4]", "", "interface {}", nil}, - {"ifacenil", true, "interface {} nil", "", "interface {}", nil}, - {"err1 == err2", false, "false", "", "", nil}, + {"err1", true, "error(*struct main.astruct) *{A: 1, B: 2}", "error(*struct main.astruct) 0x…", "error", nil}, + {"err2", true, "error(*struct main.bstruct) *{a: main.astruct {A: 1, B: 2}}", "error(*struct main.bstruct) 0x…", "error", nil}, + {"errnil", true, "error nil", "error nil", "error", nil}, + {"iface1", true, "interface {}(*struct main.astruct) *{A: 1, B: 2}", "interface {}(*struct 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}, + {"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}, {"err1 == iface1", false, "", "", "", fmt.Errorf("mismatched types \"error\" and \"interface {}\"")}, - {"errnil == nil", false, "false", "", "", nil}, - {"nil == errnil", false, "false", "", "", nil}, - {"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "", "*struct main.astruct", nil}, + {"errnil == nil", false, "false", "false", "", nil}, + {"nil == errnil", false, "false", "false", "", nil}, + {"err1.(*main.astruct)", false, "*struct main.astruct {A: 1, B: 2}", "(*struct main.astruct)(0x…", "*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}, + {"const1", true, "go/constant.Value(*go/constant.int64Val) *3", "go/constant.Value(*go/constant.int64Val) 0x…", "go/constant.Value", nil}, // combined expressions - {"c1.pb.a.A", true, "1", "", "int", nil}, - {"c1.sa[1].B", false, "3", "", "int", nil}, - {"s2[5].B", false, "12", "", "int", nil}, - {"s2[c1.sa[2].B].A", false, "11", "", "int", nil}, - {"s2[*p2].B", false, "12", "", "int", nil}, + {"c1.pb.a.A", true, "1", "1", "int", nil}, + {"c1.sa[1].B", false, "3", "3", "int", nil}, + {"s2[5].B", false, "12", "12", "int", nil}, + {"s2[c1.sa[2].B].A", false, "11", "11", "int", nil}, + {"s2[*p2].B", false, "12", "12", "int", nil}, // constants - {"1.1", false, "1.1", "", "", nil}, - {"10", false, "10", "", "", nil}, - {"1 + 2i", false, "(1 + 2i)", "", "", nil}, - {"true", false, "true", "", "", nil}, - {"\"test\"", false, "\"test\"", "", "", nil}, + {"1.1", false, "1.1", "1.1", "", nil}, + {"10", false, "10", "10", "", nil}, + {"1 + 2i", false, "(1 + 2i)", "(1 + 2i)", "", nil}, + {"true", false, "true", "true", "", nil}, + {"\"test\"", false, "\"test\"", "\"test\"", "", nil}, // binary operators - {"i2 + i3", false, "5", "", "int", nil}, - {"i2 - i3", false, "-1", "", "int", nil}, - {"i3 - i2", false, "1", "", "int", nil}, - {"i2 * i3", false, "6", "", "int", nil}, - {"i2/i3", false, "0", "", "int", nil}, - {"f1/2.0", false, "1.5", "", "float64", nil}, - {"i2 << 2", false, "8", "", "int", nil}, + {"i2 + i3", false, "5", "5", "int", nil}, + {"i2 - i3", false, "-1", "-1", "int", nil}, + {"i3 - i2", false, "1", "1", "int", nil}, + {"i2 * i3", false, "6", "6", "int", nil}, + {"i2/i3", false, "0", "0", "int", nil}, + {"f1/2.0", false, "1.5", "1.5", "float64", nil}, + {"i2 << 2", false, "8", "8", "int", nil}, // unary operators - {"-i2", false, "-2", "", "int", nil}, - {"+i2", false, "2", "", "int", nil}, - {"^i2", false, "-3", "", "int", nil}, + {"-i2", false, "-2", "-2", "int", nil}, + {"+i2", false, "2", "2", "int", nil}, + {"^i2", false, "-3", "-3", "int", nil}, // comparison operators - {"i2 == i3", false, "false", "", "", nil}, - {"i2 == 2", false, "true", "", "", nil}, - {"i2 == 2", false, "true", "", "", nil}, - {"i2 == 3", false, "false", "", "", nil}, - {"i2 != i3", false, "true", "", "", nil}, - {"i2 < i3", false, "true", "", "", nil}, - {"i2 <= i3", false, "true", "", "", nil}, - {"i2 > i3", false, "false", "", "", nil}, - {"i2 >= i3", false, "false", "", "", nil}, - {"i2 >= 2", false, "true", "", "", nil}, - {"str1 == \"01234567890\"", false, "true", "", "", nil}, - {"str1 < \"01234567890\"", false, "false", "", "", nil}, - {"str1 < \"11234567890\"", false, "true", "", "", nil}, - {"str1 > \"00234567890\"", false, "true", "", "", nil}, - {"str1 == str1", false, "true", "", "", nil}, - {"c1.pb.a == *(c1.sa[0])", false, "true", "", "", nil}, - {"c1.pb.a != *(c1.sa[0])", false, "false", "", "", nil}, - {"c1.pb.a == *(c1.sa[1])", false, "false", "", "", nil}, - {"c1.pb.a != *(c1.sa[1])", false, "true", "", "", nil}, + {"i2 == i3", false, "false", "false", "", nil}, + {"i2 == 2", false, "true", "true", "", nil}, + {"i2 == 2", false, "true", "true", "", nil}, + {"i2 == 3", false, "false", "false", "", nil}, + {"i2 != i3", false, "true", "true", "", nil}, + {"i2 < i3", false, "true", "true", "", nil}, + {"i2 <= i3", false, "true", "true", "", nil}, + {"i2 > i3", false, "false", "false", "", nil}, + {"i2 >= i3", false, "false", "false", "", nil}, + {"i2 >= 2", false, "true", "true", "", nil}, + {"str1 == \"01234567890\"", false, "true", "true", "", nil}, + {"str1 < \"01234567890\"", false, "false", "false", "", nil}, + {"str1 < \"11234567890\"", false, "true", "true", "", nil}, + {"str1 > \"00234567890\"", false, "true", "true", "", nil}, + {"str1 == str1", false, "true", "true", "", nil}, + {"c1.pb.a == *(c1.sa[0])", false, "true", "true", "", nil}, + {"c1.pb.a != *(c1.sa[0])", false, "false", "false", "", nil}, + {"c1.pb.a == *(c1.sa[1])", false, "false", "false", "", nil}, + {"c1.pb.a != *(c1.sa[1])", false, "true", "true", "", nil}, // builtins - {"cap(parr)", false, "4", "", "", nil}, - {"len(parr)", false, "4", "", "", nil}, + {"cap(parr)", false, "4", "4", "", nil}, + {"len(parr)", false, "4", "4", "", nil}, {"cap(p1)", false, "", "", "", fmt.Errorf("invalid argument p1 (type *int) for cap")}, {"len(p1)", false, "", "", "", fmt.Errorf("invalid argument p1 (type *int) for len")}, - {"cap(a1)", false, "5", "", "", nil}, - {"len(a1)", false, "5", "", "", nil}, - {"cap(s3)", false, "6", "", "", nil}, - {"len(s3)", false, "0", "", "", nil}, - {"cap(nilslice)", false, "0", "", "", nil}, - {"len(nilslice)", false, "0", "", "", nil}, - {"cap(ch1)", false, "2", "", "", nil}, - {"len(ch1)", false, "0", "", "", nil}, - {"cap(chnil)", false, "0", "", "", nil}, - {"len(chnil)", false, "0", "", "", nil}, - {"len(m1)", false, "41", "", "", nil}, - {"len(mnil)", false, "0", "", "", nil}, - {"imag(cpx1)", false, "2", "", "", nil}, - {"real(cpx1)", false, "1", "", "", nil}, - {"imag(3i)", false, "3", "", "", nil}, - {"real(4)", false, "4", "", "", nil}, + {"cap(a1)", false, "5", "5", "", nil}, + {"len(a1)", false, "5", "5", "", nil}, + {"cap(s3)", false, "6", "6", "", nil}, + {"len(s3)", false, "0", "0", "", nil}, + {"cap(nilslice)", false, "0", "0", "", nil}, + {"len(nilslice)", false, "0", "0", "", nil}, + {"cap(ch1)", false, "2", "2", "", nil}, + {"len(ch1)", false, "0", "0", "", nil}, + {"cap(chnil)", false, "0", "0", "", nil}, + {"len(chnil)", false, "0", "0", "", nil}, + {"len(m1)", false, "41", "41", "", nil}, + {"len(mnil)", false, "0", "0", "", nil}, + {"imag(cpx1)", false, "2", "2", "", nil}, + {"real(cpx1)", false, "1", "1", "", nil}, + {"imag(3i)", false, "3", "3", "", nil}, + {"real(4)", false, "4", "4", "", nil}, // nil - {"nil", false, "nil", "", "", nil}, + {"nil", false, "nil", "nil", "", nil}, {"nil+1", false, "", "", "", fmt.Errorf("operator + can not be applied to \"nil\"")}, - {"fn1", false, "main.afunc", "", "main.functype", nil}, - {"fn2", false, "nil", "", "main.functype", nil}, - {"nilslice", false, "[]int len: 0, cap: 0, []", "", "[]int", nil}, + {"fn1", false, "main.afunc", "main.afunc", "main.functype", nil}, + {"fn2", false, "nil", "nil", "main.functype", nil}, + {"nilslice", false, "[]int len: 0, cap: 0, []", "[]int len: 0, cap: 0, []", "[]int", nil}, {"fn1 == fn2", false, "", "", "", fmt.Errorf("can not compare func variables")}, - {"fn1 == nil", false, "false", "", "", nil}, - {"fn1 != nil", false, "true", "", "", nil}, - {"fn2 == nil", false, "true", "", "", nil}, - {"fn2 != nil", false, "false", "", "", nil}, - {"c1.sa == nil", false, "false", "", "", nil}, - {"c1.sa != nil", false, "true", "", "", nil}, - {"c1.sa[0] == nil", false, "false", "", "", nil}, - {"c1.sa[0] != nil", false, "true", "", "", nil}, - {"nilslice == nil", false, "true", "", "", nil}, - {"nil == nilslice", false, "true", "", "", nil}, - {"nilslice != nil", false, "false", "", "", nil}, - {"nilptr == nil", false, "true", "", "", nil}, - {"nilptr != nil", false, "false", "", "", nil}, - {"p1 == nil", false, "false", "", "", nil}, - {"p1 != nil", false, "true", "", "", nil}, - {"ch1 == nil", false, "false", "", "", nil}, - {"chnil == nil", false, "true", "", "", nil}, + {"fn1 == nil", false, "false", "false", "", nil}, + {"fn1 != nil", false, "true", "true", "", nil}, + {"fn2 == nil", false, "true", "true", "", nil}, + {"fn2 != nil", false, "false", "false", "", nil}, + {"c1.sa == nil", false, "false", "false", "", nil}, + {"c1.sa != nil", false, "true", "true", "", nil}, + {"c1.sa[0] == nil", false, "false", "false", "", nil}, + {"c1.sa[0] != nil", false, "true", "true", "", nil}, + {"nilslice == nil", false, "true", "true", "", nil}, + {"nil == nilslice", false, "true", "true", "", nil}, + {"nilslice != nil", false, "false", "false", "", nil}, + {"nilptr == nil", false, "true", "true", "", nil}, + {"nilptr != nil", false, "false", "false", "", nil}, + {"p1 == nil", false, "false", "false", "", nil}, + {"p1 != nil", false, "true", "true", "", nil}, + {"ch1 == nil", false, "false", "false", "", nil}, + {"chnil == nil", false, "true", "true", "", nil}, {"ch1 == chnil", false, "", "", "", fmt.Errorf("can not compare chan variables")}, - {"m1 == nil", false, "false", "", "", nil}, + {"m1 == nil", false, "false", "false", "", nil}, {"mnil == m1", false, "", "", "", fmt.Errorf("can not compare map variables")}, - {"mnil == nil", false, "true", "", "", nil}, + {"mnil == nil", false, "true", "true", "", nil}, {"nil == 2", false, "", "", "", fmt.Errorf("can not compare int to nil")}, {"2 == nil", false, "", "", "", fmt.Errorf("can not compare int to nil")}, @@ -536,32 +604,36 @@ func TestEvalExpression(t *testing.T) { {"&nil", false, "", "", "", fmt.Errorf("can not take address of \"nil\"")}, {"nil[0]", false, "", "", "", fmt.Errorf("expression \"nil\" (nil) does not support indexing")}, {"nil[2:10]", false, "", "", "", fmt.Errorf("can not slice \"nil\" (type nil)")}, - {"nil.member", false, "", "", "", fmt.Errorf("type nil is not a struct")}, + {"nil.member", false, "", "", "", fmt.Errorf("nil (type nil) is not a struct")}, {"(map[string]main.astruct)(0x4000)", false, "", "", "", fmt.Errorf("can not convert \"0x4000\" to map[string]main.astruct")}, // typecasts - {"uint(i2)", false, "2", "", "uint", nil}, - {"int8(i2)", false, "2", "", "int8", nil}, - {"int(f1)", false, "3", "", "int", nil}, - {"complex128(f1)", false, "(3 + 0i)", "", "complex128", nil}, - {"uint8(i4)", false, "32", "", "uint8", nil}, - {"uint8(i5)", false, "253", "", "uint8", nil}, - {"int8(i5)", false, "-3", "", "int8", nil}, - {"int8(i6)", false, "12", "", "int8", nil}, + {"uint(i2)", false, "2", "2", "uint", nil}, + {"int8(i2)", false, "2", "2", "int8", nil}, + {"int(f1)", false, "3", "3", "int", nil}, + {"complex128(f1)", false, "(3 + 0i)", "(3 + 0i)", "complex128", nil}, + {"uint8(i4)", false, "32", "32", "uint8", nil}, + {"uint8(i5)", false, "253", "253", "uint8", nil}, + {"int8(i5)", false, "-3", "-3", "int8", nil}, + {"int8(i6)", false, "12", "12", "int8", nil}, // misc - {"i1", true, "1", "", "int", nil}, - {"mainMenu", true, `main.Menu len: 3, cap: 3, [{Name: "home", Route: "/", Active: 1},{Name: "About", Route: "/about", Active: 1},{Name: "Login", Route: "/login", Active: 1}]`, "", "main.Menu", nil}, - {"mainMenu[0]", false, `main.Item {Name: "home", Route: "/", Active: 1}`, "", "main.Item", nil}, + {"i1", true, "1", "1", "int", nil}, + {"mainMenu", true, `main.Menu len: 3, cap: 3, [{Name: "home", Route: "/", Active: 1},{Name: "About", Route: "/about", Active: 1},{Name: "Login", Route: "/login", Active: 1}]`, `main.Menu len: 3, cap: 3, [...]`, "main.Menu", nil}, + {"mainMenu[0]", false, `main.Item {Name: "home", Route: "/", Active: 1}`, `main.Item {Name: "home", Route: "/", Active: 1}`, "main.Item", nil}, + {"sd", false, "main.D {u1: 0, u2: 0, u3: 0, u4: 0, u5: 0, u6: 0}", "main.D {u1: 0, u2: 0, u3: 0,...+3 more}", "main.D", nil}, } withTestProcess("testvariables2", 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) + 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) + variable, err := evalVariable(p, tc.name, pshortLoadConfig) + assertNoError(err, t, fmt.Sprintf("EvalExpression(%s, pshortLoadConfig) returned an error", tc.name)) + assertVariable(t, variable, tc.alternateVarTest()) } else { if err == nil { t.Fatalf("Expected error %s, got no error (%s)", tc.err.Error(), tc.name) @@ -570,6 +642,7 @@ func TestEvalExpression(t *testing.T) { t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error()) } } + } }) } @@ -577,7 +650,7 @@ func TestEvalExpression(t *testing.T) { func TestEvalAddrAndCast(t *testing.T) { withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue() returned an error") - c1addr, err := evalVariable(p, "&c1") + c1addr, err := evalVariable(p, "&c1", pnormalLoadConfig) assertNoError(err, t, "EvalExpression(&c1)") c1addrstr := api.ConvertVar(c1addr).SinglelineString() t.Logf("&c1 → %s", c1addrstr) @@ -585,7 +658,7 @@ func TestEvalAddrAndCast(t *testing.T) { t.Fatalf("Invalid value of EvalExpression(&c1) \"%s\"", c1addrstr) } - aaddr, err := evalVariable(p, "&(c1.pb.a)") + aaddr, err := evalVariable(p, "&(c1.pb.a)", pnormalLoadConfig) assertNoError(err, t, "EvalExpression(&(c1.pb.a))") aaddrstr := api.ConvertVar(aaddr).SinglelineString() t.Logf("&(c1.pb.a) → %s", aaddrstr) @@ -593,7 +666,7 @@ func TestEvalAddrAndCast(t *testing.T) { t.Fatalf("invalid value of EvalExpression(&(c1.pb.a)) \"%s\"", aaddrstr) } - a, err := evalVariable(p, "*"+aaddrstr) + a, err := evalVariable(p, "*"+aaddrstr, pnormalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalExpression(*%s)", aaddrstr)) t.Logf("*%s → %s", aaddrstr, api.ConvertVar(a).SinglelineString()) assertVariable(t, a, varTest{aaddrstr, false, "struct main.astruct {A: 1, B: 2}", "", "struct main.astruct", nil}) @@ -603,7 +676,7 @@ func TestEvalAddrAndCast(t *testing.T) { func TestMapEvaluation(t *testing.T) { withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue() returned an error") - m1v, err := evalVariable(p, "m1") + m1v, err := evalVariable(p, "m1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") m1 := api.ConvertVar(m1v) t.Logf("m1 = %v", m1.MultilineString("")) @@ -626,7 +699,7 @@ func TestMapEvaluation(t *testing.T) { t.Fatalf("Could not find Malone") } - m1sliced, err := evalVariable(p, "m1[10:]") + m1sliced, err := evalVariable(p, "m1[10:]", pnormalLoadConfig) assertNoError(err, t, "EvalVariable(m1[10:])") if len(m1sliced.Children)/2 != int(m1.Len-10) { t.Fatalf("Wrong number of children (after slicing): %d", len(m1sliced.Children)/2) @@ -637,7 +710,7 @@ func TestMapEvaluation(t *testing.T) { func TestUnsafePointer(t *testing.T) { withTestProcess("testvariables2", t, func(p *proc.Process, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue() returned an error") - up1v, err := evalVariable(p, "up1") + up1v, err := evalVariable(p, "up1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable(up1)") up1 := api.ConvertVar(up1v) if ss := up1.SinglelineString(); !strings.HasPrefix(ss, "unsafe.Pointer(") { diff --git a/terminal/command.go b/terminal/command.go index cf28a9ed..7b40f39a 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -37,7 +37,6 @@ type callContext struct { } type cmdfunc func(t *Term, ctx callContext, args string) error -type filteringFunc func(t *Term, ctx callContext, args string) ([]string, error) type command struct { aliases []string @@ -63,6 +62,11 @@ type Commands struct { client service.Client } +var ( + LongLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} + ShortLoadConfig = api.LoadConfig{false, 0, 64, 0, 3} +) + // DebugCommands returns a Commands struct with default commands defined. func DebugCommands(client service.Client) *Commands { c := &Commands{client: client} @@ -85,12 +89,12 @@ func DebugCommands(client service.Client) *Commands { {aliases: []string{"breakpoints", "bp"}, cmdFn: breakpoints, helpMsg: "Print out info for active breakpoints."}, {aliases: []string{"print", "p"}, allowedPrefixes: onPrefix | scopePrefix, cmdFn: printVar, helpMsg: "Evaluate a variable."}, {aliases: []string{"set"}, allowedPrefixes: scopePrefix, cmdFn: setVar, helpMsg: "Changes the value of a variable."}, - {aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."}, - {aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."}, - {aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."}, - {aliases: []string{"args"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(args), helpMsg: "Print function arguments, optionally filtered by a regexp."}, - {aliases: []string{"locals"}, allowedPrefixes: scopePrefix, cmdFn: filterSortAndOutput(locals), helpMsg: "Print function locals, optionally filtered by a regexp."}, - {aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."}, + {aliases: []string{"sources"}, cmdFn: sources, helpMsg: "Print list of source files, optionally filtered by a regexp."}, + {aliases: []string{"funcs"}, cmdFn: funcs, helpMsg: "Print list of functions, optionally filtered by a regexp."}, + {aliases: []string{"types"}, cmdFn: types, helpMsg: "Print list of types, optionally filtered by a regexp."}, + {aliases: []string{"args"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: args, helpMsg: "args [-v] . Print function arguments, optionally filtered by a regexp."}, + {aliases: []string{"locals"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: locals, helpMsg: "locals [-v] . Print function locals, optionally filtered by a regexp."}, + {aliases: []string{"vars"}, cmdFn: vars, helpMsg: "vars [-v] . Print package variables, optionally filtered by a regexp."}, {aliases: []string{"regs"}, cmdFn: regs, helpMsg: "Print contents of CPU registers."}, {aliases: []string{"exit", "quit", "q"}, cmdFn: exitCommand, helpMsg: "Exit the debugger."}, {aliases: []string{"list", "ls"}, allowedPrefixes: scopePrefix, cmdFn: listCommand, helpMsg: "list . Show source around current point or provided linespec."}, @@ -552,6 +556,20 @@ func breakpoints(t *Term, ctx callContext, args string) error { if bp.Goroutine { attrs = append(attrs, "\tgoroutine") } + if bp.LoadArgs != nil { + if *(bp.LoadArgs) == LongLoadConfig { + attrs = append(attrs, "\targs -v") + } else { + attrs = append(attrs, "\targs") + } + } + if bp.LoadLocals != nil { + if *(bp.LoadLocals) == LongLoadConfig { + attrs = append(attrs, "\tlocals -v") + } else { + attrs = append(attrs, "\tlocals") + } + } for i := range bp.Variables { attrs = append(attrs, fmt.Sprintf("\tprint %s", bp.Variables[i])) } @@ -624,7 +642,7 @@ func printVar(t *Term, ctx callContext, args string) error { ctx.Breakpoint.Variables = append(ctx.Breakpoint.Variables, args) return nil } - val, err := t.client.EvalVariable(ctx.Scope, args) + val, err := t.client.EvalVariable(ctx.Scope, args, LongLoadConfig) if err != nil { return err } @@ -650,55 +668,101 @@ func setVar(t *Term, ctx callContext, args string) error { return t.client.SetVariable(ctx.Scope, lexpr, rexpr) } -func filterVariables(vars []api.Variable, filter string) []string { +func printFilteredVariables(varType string, vars []api.Variable, filter string, cfg api.LoadConfig) error { reg, err := regexp.Compile(filter) if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - return nil + return err } - data := make([]string, 0, len(vars)) + match := false for _, v := range vars { if reg == nil || reg.Match([]byte(v.Name)) { - data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString())) + match = true + if cfg == ShortLoadConfig { + fmt.Printf("%s = %s\n", v.Name, v.SinglelineString()) + } else { + fmt.Printf("%s = %s\n", v.Name, v.MultilineString("")) + } } } - return data -} - -func sources(t *Term, ctx callContext, filter string) ([]string, error) { - return t.client.ListSources(filter) -} - -func funcs(t *Term, ctx callContext, filter string) ([]string, error) { - return t.client.ListFunctions(filter) -} - -func types(t *Term, ctx callContext, filter string) ([]string, error) { - return t.client.ListTypes(filter) -} - -func args(t *Term, ctx callContext, filter string) ([]string, error) { - vars, err := t.client.ListFunctionArgs(ctx.Scope) - if err != nil { - return nil, err + if !match { + fmt.Printf("(no %s)\n", varType) } - return describeNoVars("args", filterVariables(vars, filter)), nil + return nil } -func locals(t *Term, ctx callContext, filter string) ([]string, error) { - locals, err := t.client.ListLocalVariables(ctx.Scope) +func printSortedStrings(v []string, err error) error { if err != nil { - return nil, err + return err } - return describeNoVars("locals", filterVariables(locals, filter)), nil + sort.Strings(v) + for _, d := range v { + fmt.Println(d) + } + return nil } -func vars(t *Term, ctx callContext, filter string) ([]string, error) { - vars, err := t.client.ListPackageVariables(filter) - if err != nil { - return nil, err +func sources(t *Term, ctx callContext, args string) error { + return printSortedStrings(t.client.ListSources(args)) +} + +func funcs(t *Term, ctx callContext, args string) error { + return printSortedStrings(t.client.ListFunctions(args)) +} + +func types(t *Term, ctx callContext, args string) error { + return printSortedStrings(t.client.ListTypes(args)) +} + +func parseVarArguments(args string) (filter string, cfg api.LoadConfig) { + if v := strings.SplitN(args, " ", 2); len(v) >= 1 && v[0] == "-v" { + if len(v) == 2 { + return v[1], LongLoadConfig + } else { + return "", LongLoadConfig + } } - return describeNoVars("vars", filterVariables(vars, filter)), nil + return args, ShortLoadConfig +} + +func args(t *Term, ctx callContext, args string) error { + filter, cfg := parseVarArguments(args) + if ctx.Prefix == onPrefix { + if filter != "" { + return fmt.Errorf("filter not supported on breakpoint") + } + ctx.Breakpoint.LoadArgs = &cfg + return nil + } + vars, err := t.client.ListFunctionArgs(ctx.Scope, cfg) + if err != nil { + return err + } + return printFilteredVariables("args", vars, filter, cfg) +} + +func locals(t *Term, ctx callContext, args string) error { + filter, cfg := parseVarArguments(args) + if ctx.Prefix == onPrefix { + if filter != "" { + return fmt.Errorf("filter not supported on breakpoint") + } + ctx.Breakpoint.LoadLocals = &cfg + return nil + } + locals, err := t.client.ListLocalVariables(ctx.Scope, cfg) + if err != nil { + return err + } + return printFilteredVariables("locals", locals, filter, cfg) +} + +func vars(t *Term, ctx callContext, args string) error { + filter, cfg := parseVarArguments(args) + vars, err := t.client.ListPackageVariables(filter, cfg) + if err != nil { + return err + } + return printFilteredVariables("vars", vars, filter, cfg) } func regs(t *Term, ctx callContext, args string) error { @@ -710,27 +774,6 @@ func regs(t *Term, ctx callContext, args string) error { return nil } -func filterSortAndOutput(fn filteringFunc) cmdfunc { - return func(t *Term, ctx callContext, args string) error { - var filter string - if len(args) > 0 { - if _, err := regexp.Compile(args); err != nil { - return fmt.Errorf("invalid filter argument: %s", err.Error()) - } - filter = args - } - data, err := fn(t, ctx, filter) - if err != nil { - return err - } - sort.Sort(sort.StringSlice(data)) - for _, d := range data { - fmt.Println(d) - } - return nil - } -} - func stackCommand(t *Term, ctx callContext, args string) error { depth, full, err := parseStackArgs(args) if err != nil { @@ -740,7 +783,11 @@ func stackCommand(t *Term, ctx callContext, args string) error { ctx.Breakpoint.Stacktrace = depth return nil } - stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, full) + var cfg *api.LoadConfig + if full { + cfg = &ShortLoadConfig + } + stack, err := t.client.Stacktrace(ctx.Scope.GoroutineID, depth, cfg) if err != nil { return err } @@ -772,7 +819,7 @@ func parseStackArgs(argstr string) (int, bool, error) { func listCommand(t *Term, ctx callContext, args string) error { if ctx.Prefix == scopePrefix { - locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, false) + locs, err := t.client.Stacktrace(ctx.Scope.GoroutineID, ctx.Scope.Frame, nil) if err != nil { return err } @@ -885,7 +932,7 @@ func printStack(stack []api.Stackframe, ind string) { } d := digits(len(stack) - 1) fmtstr := "%s%" + strconv.Itoa(d) + "d 0x%016x in %s\n" - s := strings.Repeat(" ", d+2+len(ind)) + s := ind + strings.Repeat(" ", d+2+len(ind)) for i := range stack { name := "(nil)" @@ -941,7 +988,7 @@ func printcontextThread(t *Term, th *api.Thread) { } args := "" - if th.Breakpoint.Tracepoint && th.BreakpointInfo != nil { + if th.BreakpointInfo != nil && th.Breakpoint.LoadArgs != nil && *th.Breakpoint.LoadArgs == ShortLoadConfig { var arg []string for _, ar := range th.BreakpointInfo.Arguments { arg = append(arg, ar.SinglelineString()) @@ -977,18 +1024,29 @@ func printcontextThread(t *Term, th *api.Thread) { } if th.BreakpointInfo != nil { + bp := th.Breakpoint bpi := th.BreakpointInfo if bpi.Goroutine != nil { writeGoroutineLong(os.Stdout, bpi.Goroutine, "\t") } - if len(bpi.Variables) > 0 { - ss := make([]string, len(bpi.Variables)) - for i, v := range bpi.Variables { - ss[i] = fmt.Sprintf("%s: %s", v.Name, v.MultilineString("")) + for _, v := range bpi.Variables { + fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t")) + } + + for _, v := range bpi.Locals { + if *bp.LoadLocals == LongLoadConfig { + fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t")) + } else { + fmt.Printf("\t%s: %s\n", v.Name, v.SinglelineString()) + } + } + + if bp.LoadArgs != nil && *bp.LoadArgs == LongLoadConfig { + for _, v := range bpi.Arguments { + fmt.Printf("\t%s: %s\n", v.Name, v.MultilineString("\t")) } - fmt.Printf("\t%s\n", strings.Join(ss, ", ")) } if bpi.Stacktrace != nil { @@ -1153,10 +1211,3 @@ func formatBreakpointLocation(bp *api.Breakpoint) string { } return fmt.Sprintf("%#v for %s:%d", bp.Addr, p, bp.Line) } - -func describeNoVars(varType string, data []string) []string { - if len(data) == 0 { - return []string{fmt.Sprintf("(no %s)", varType)} - } - return data -} diff --git a/terminal/command_test.go b/terminal/command_test.go index 1ea01fef..386c6a2c 100644 --- a/terminal/command_test.go +++ b/terminal/command_test.go @@ -357,3 +357,44 @@ func TestNoVars(t *testing.T) { term.AssertExec("vars filterThatMatchesNothing", "(no vars)\n") }) } + +func TestOnPrefixLocals(t *testing.T) { + const prefix = "\ti: " + withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { + term.MustExec("b agobp main.agoroutine") + term.MustExec("on agobp args -v") + + seen := make([]bool, 10) + + for { + outstr, err := term.Exec("continue") + if err != nil { + if strings.Index(err.Error(), "exited") < 0 { + t.Fatalf("Unexpected error executing 'continue': %v", err) + } + break + } + out := strings.Split(outstr, "\n") + + for i := range out { + if !strings.HasPrefix(out[i], "\ti: ") { + continue + } + id, err := strconv.Atoi(out[i][len(prefix):]) + if err != nil { + continue + } + if seen[id] { + t.Fatalf("Goroutine %d seen twice\n", id) + } + seen[id] = true + } + } + + for i := range seen { + if !seen[i] { + t.Fatalf("Goroutine %d not seen\n", i) + } + } + }) +}