提交 473b6638 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

proc: Improvements to Variable printing (#444)

* service/api: Removed unused fields of service/api.Function

* proc/eval: Set return variable name to input expression

* all: fine-grained control of loadValue for better variable printing

Makes proc.(*Variable).loadValue loading parameters configurable
through one extra argument of type LoadConfig.
This interface is also exposed through the API so clients can control
how much of a variable delve should read.
上级 60946a75
......@@ -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)
}
......@@ -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
......
......@@ -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
......
......@@ -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)
}
......
......@@ -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
......
......@@ -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 {
......
......@@ -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
......
......@@ -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,
}
}
......@@ -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 {
......
......@@ -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 {
......
......@@ -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
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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)
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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)
......
......@@ -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})
......
......@@ -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)
}
})
}
此差异已折叠。
......@@ -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] <filter>. Print function arguments, optionally filtered by a regexp."},
{aliases: []string{"locals"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: locals, helpMsg: "locals [-v] <filter>. Print function locals, optionally filtered by a regexp."},
{aliases: []string{"vars"}, cmdFn: vars, helpMsg: "vars [-v] <filter>. 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 <linespec>. 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
if !match {
fmt.Printf("(no %s)\n", varType)
}
return nil
}
func sources(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListSources(filter)
func printSortedStrings(v []string, err error) error {
if err != nil {
return err
}
sort.Strings(v)
for _, d := range v {
fmt.Println(d)
}
return nil
}
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 funcs(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListFunctions(filter)
func types(t *Term, ctx callContext, args string) error {
return printSortedStrings(t.client.ListTypes(args))
}
func types(t *Term, ctx callContext, filter string) ([]string, error) {
return t.client.ListTypes(filter)
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 args, ShortLoadConfig
}
func args(t *Term, ctx callContext, filter string) ([]string, error) {
vars, err := t.client.ListFunctionArgs(ctx.Scope)
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 nil, err
return err
}
return describeNoVars("args", filterVariables(vars, filter)), nil
return printFilteredVariables("args", vars, filter, cfg)
}
func locals(t *Term, ctx callContext, filter string) ([]string, error) {
locals, err := t.client.ListLocalVariables(ctx.Scope)
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 nil, err
return err
}
return describeNoVars("locals", filterVariables(locals, filter)), nil
return printFilteredVariables("locals", locals, filter, cfg)
}
func vars(t *Term, ctx callContext, filter string) ([]string, error) {
vars, err := t.client.ListPackageVariables(filter)
func vars(t *Term, ctx callContext, args string) error {
filter, cfg := parseVarArguments(args)
vars, err := t.client.ListPackageVariables(filter, cfg)
if err != nil {
return nil, err
return err
}
return describeNoVars("vars", filterVariables(vars, filter)), nil
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
}
......@@ -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)
}
}
})
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册