Changed api.Variable to have a machine readable value

The new contents of api.Variable are documented in
proc/variables.go.

Implements #243
This commit is contained in:
aarzilli 2015-10-18 19:37:13 +02:00 committed by Derek Parker
parent 91939bc9e7
commit 50b5fc92e2
15 changed files with 1192 additions and 745 deletions

@ -1,6 +1,7 @@
package main
import "fmt"
import "runtime"
type FooBar struct {
Baz int
@ -19,6 +20,7 @@ type Nest struct {
}
func barfoo() {
runtime.Breakpoint()
a1 := "bur"
fmt.Println(a1)
}
@ -56,6 +58,7 @@ func foobar(baz string, bar FooBar) {
ba = make([]int, 200, 200) // Test array size capping
)
runtime.Breakpoint()
barfoo()
fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, b1, b2, baz, neg, i8, u8, u16, u32, u64, up, f32, c64, c128, i32, bar, f, ms, ba, p1)
}

@ -10,5 +10,5 @@ func main() {
i2 := 2
p1 := &i1
runtime.Breakpoint()
fmt.Printf("%d %d %v\n", i1, i2, p1)
fmt.Println(i1, i2, p1)
}

@ -1,12 +1,12 @@
package main
import (
"fmt"
"runtime"
"fmt"
"runtime"
)
type A struct {
val int
val int
}
type C struct {
@ -14,20 +14,20 @@ type C struct {
}
type B struct {
A
A
*C
a A
ptr *A
a A
ptr *A
}
func main() {
b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}}
runtime.Breakpoint()
fmt.Println(b)
fmt.Println(b.val)
fmt.Println(b.A.val)
fmt.Println(b.a.val)
fmt.Println(b.ptr.val)
fmt.Println(b.C.s)
fmt.Println(b.s)
}
b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}}
runtime.Breakpoint()
fmt.Println(b)
fmt.Println(b.val)
fmt.Println(b.A.val)
fmt.Println(b.a.val)
fmt.Println(b.ptr.val)
fmt.Println(b.C.s)
fmt.Println(b.s)
}

@ -195,7 +195,7 @@ starts and attaches to it, and enables you to immediately begin debugging your p
}
if state.BreakpointInfo != nil {
for _, arg := range state.BreakpointInfo.Arguments {
args = append(args, arg.Value)
args = append(args, arg.SinglelineString())
}
}
fmt.Printf("%s(%s) %s:%d\n", fname, strings.Join(args, ", "), state.CurrentThread.File, state.CurrentThread.Line)

@ -707,9 +707,9 @@ func (dbp *Process) getGoInformation() (ver GoVersion, isextld bool, err error)
return
}
ver, ok := parseVersionString(vv.Value)
ver, ok := parseVersionString(vv.Value.(string))
if !ok {
err = fmt.Errorf("Could not parse version number: %s\n", vv.Value)
err = fmt.Errorf("Could not parse version number: %v\n", vv.Value)
return
}

@ -7,7 +7,9 @@ import (
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
@ -332,7 +334,7 @@ func TestNextConcurrent(t *testing.T) {
}
v, err := evalVariable(p, "n")
assertNoError(err, t, "EvalVariable")
if v.Value != initV.Value {
if v.Value.(int64) != initV.Value.(int64) {
t.Fatal("Did not end up on same goroutine")
}
}
@ -809,3 +811,221 @@ func TestIssue239(t *testing.T) {
assertNoError(p.Continue(), t, fmt.Sprintf("Continue()"))
})
}
func evalVariable(p *Process, symbol string) (*Variable, error) {
scope, err := p.CurrentThread.Scope()
if err != nil {
return nil, err
}
return scope.EvalVariable(symbol)
}
func setVariable(p *Process, symbol, value string) error {
scope, err := p.CurrentThread.Scope()
if err != nil {
return err
}
return scope.SetVariable(symbol, value)
}
func TestVariableEvaluation(t *testing.T) {
testcases := []struct {
name string
st reflect.Kind
value interface{}
length, cap int64
childrenlen int
}{
{"a1", reflect.String, "foofoofoofoofoofoo", 18, 0, 0},
{"a11", reflect.Array, nil, 3, -1, 3},
{"a12", reflect.Slice, nil, 2, 2, 2},
{"a13", reflect.Slice, nil, 3, 3, 3},
{"a2", reflect.Int, int64(6), 0, 0, 0},
{"a3", reflect.Float64, float64(7.23), 0, 0, 0},
{"a4", reflect.Array, nil, 2, -1, 2},
{"a5", reflect.Slice, nil, 5, 5, 5},
{"a6", reflect.Struct, nil, 2, 0, 2},
{"a7", reflect.Ptr, nil, 1, 0, 1},
{"a8", reflect.Struct, nil, 2, 0, 2},
{"a9", reflect.Ptr, nil, 1, 0, 1},
{"baz", reflect.String, "bazburzum", 9, 0, 0},
{"neg", reflect.Int, int64(-1), 0, 0, 0},
{"f32", reflect.Float32, float64(float32(1.2)), 0, 0, 0},
{"c64", reflect.Complex64, nil, 2, 0, 2},
{"c128", reflect.Complex128, nil, 2, 0, 2},
{"a6.Baz", reflect.Int, int64(8), 0, 0, 0},
{"a7.Baz", reflect.Int, int64(5), 0, 0, 0},
{"a8.Baz", reflect.String, "feh", 3, 0, 0},
{"a8", reflect.Struct, nil, 2, 0, 2},
{"i32", reflect.Array, nil, 2, -1, 2},
{"b1", reflect.Bool, true, 0, 0, 0},
{"b2", reflect.Bool, false, 0, 0, 0},
{"f", reflect.Func, "main.barfoo", 0, 0, 0},
{"ba", reflect.Slice, nil, 200, 200, 64},
}
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
for _, tc := range testcases {
v, err := evalVariable(p, tc.name)
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", tc.name))
if v.Kind != tc.st {
t.Fatalf("%s simple type: expected: %s got: %s", tc.name, tc.st, v.Kind.String())
}
if v.Value == nil && tc.value != nil {
t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value)
} else {
switch x := v.Value.(type) {
case int64:
if y, ok := tc.value.(int64); !ok || x != y {
t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value)
}
case float64:
if y, ok := tc.value.(float64); !ok || x != y {
t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value)
}
case string:
if y, ok := tc.value.(string); !ok || x != y {
t.Fatalf("%s value: expected: %v got: %v", tc.name, tc.value, v.Value)
}
}
}
if v.Len != tc.length {
t.Fatalf("%s len: expected: %d got: %d", tc.name, tc.length, v.Len)
}
if v.Cap != tc.cap {
t.Fatalf("%s cap: expected: %d got: %d", tc.name, tc.cap, v.Cap)
}
if len(v.Children) != tc.childrenlen {
t.Fatalf("%s children len: expected %d got: %d", tc.name, tc.childrenlen, len(v.Children))
}
}
})
}
func TestFrameEvaluation(t *testing.T) {
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "setFunctionBreakpoint")
assertNoError(p.Continue(), t, "Continue()")
/**** Testing evaluation on goroutines ****/
gs, err := p.GoroutinesInfo()
assertNoError(err, t, "GoroutinesInfo")
found := make([]bool, 10)
for _, g := range gs {
frame := -1
frames, err := p.GoroutineStacktrace(g, 10)
assertNoError(err, t, "GoroutineStacktrace()")
for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
frame = i
break
}
}
if frame < 0 {
t.Logf("Goroutine %d: could not find correct frame", g.Id)
continue
}
scope, err := p.ConvertEvalScope(g.Id, frame)
assertNoError(err, t, "ConvertEvalScope()")
t.Logf("scope = %v", scope)
v, err := scope.EvalVariable("i")
t.Logf("v = %v", v)
if err != nil {
t.Logf("Goroutine %d: %v\n", g.Id, err)
continue
}
found[v.Value.(int64)] = true
}
for i := range found {
if !found[i] {
t.Fatalf("Goroutine %d not found\n", i)
}
}
/**** Testing evaluation on frames ****/
assertNoError(p.Continue(), t, "Continue() 2")
g, err := p.CurrentThread.GetG()
assertNoError(err, t, "GetG()")
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")
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
n := v.Value.(int64)
t.Logf("frame %d n %d\n", i+1, n)
if n != int64(3-i) {
t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i)
}
}
})
}
func TestPointerSetting(t *testing.T) {
withTestProcess("testvariables3", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
pval := func(n int64) {
variable, err := evalVariable(p, "p1")
assertNoError(err, t, "EvalVariable()")
if variable.Children[0].Value.(int64) != n {
t.Fatalf("Wrong value of p1, *%d expected *%d", variable.Children[0].Value.(int64), n)
}
}
pval(1)
// change p1 to point to i2
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "Scope()")
i2addr, err := scope.ExtractVariableInfo("i2")
assertNoError(err, t, "EvalVariableAddr()")
assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()")
pval(2)
// change the value of i2 check that p1 also changes
assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()")
pval(5)
})
}
func TestVariableFunctionScoping(t *testing.T) {
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
err := p.Continue()
assertNoError(err, t, "Continue() returned an error")
_, err = evalVariable(p, "a1")
assertNoError(err, t, "Unable to find variable a1")
_, err = evalVariable(p, "a2")
assertNoError(err, t, "Unable to find variable a1")
// Move scopes, a1 exists here by a2 does not
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
_, err = evalVariable(p, "a1")
assertNoError(err, t, "Unable to find variable a1")
_, err = evalVariable(p, "a2")
if err == nil {
t.Fatalf("Can eval out of scope variable a2")
}
})
}
func TestRecursiveStructure(t *testing.T) {
withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
v, err := evalVariable(p, "aas")
assertNoError(err, t, "EvalVariable()")
t.Logf("v: %v\n", v)
})
}

@ -8,6 +8,7 @@ import (
"go/ast"
"go/parser"
"go/token"
"reflect"
"strconv"
"strings"
"unsafe"
@ -29,16 +30,24 @@ const (
type Variable struct {
Addr uintptr
Name string
Value string
Type string
dwarfType dwarf.Type
DwarfType dwarf.Type
RealType dwarf.Type
Kind reflect.Kind
thread *Thread
Len int64
Cap int64
Value interface{}
Len int64
Cap int64
base uintptr
stride int64
fieldType dwarf.Type
Children []Variable
loaded bool
Unreadable error
}
// Represents a runtime M (OS thread) structure.
@ -91,24 +100,40 @@ type EvalScope struct {
CFA int64
}
func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) (*Variable, error) {
func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread) *Variable {
v := &Variable{
Name: name,
Addr: addr,
dwarfType: dwarfType,
DwarfType: dwarfType,
thread: thread,
Type: dwarfType.String(),
}
switch t := dwarfType.(type) {
v.RealType = v.DwarfType
for {
if tt, ok := v.RealType.(*dwarf.TypedefType); ok {
v.RealType = tt.Type
} else {
break
}
}
switch t := v.RealType.(type) {
case *dwarf.PtrType:
v.Kind = reflect.Ptr
case *dwarf.StructType:
if strings.HasPrefix(t.StructName, "[]") {
err := v.loadSliceInfo(t)
if err != nil {
return nil, err
switch {
case t.StructName == "string":
v.Kind = reflect.String
case strings.HasPrefix(t.StructName, "[]"):
v.Kind = reflect.Slice
if v.Addr != 0 {
v.loadSliceInfo(t)
}
default:
v.Kind = reflect.Struct
}
case *dwarf.ArrayType:
v.Kind = reflect.Array
v.base = v.Addr
v.Len = t.Count
v.Cap = -1
@ -118,12 +143,48 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
if t.Count > 0 {
v.stride = t.ByteSize / t.Count
}
case *dwarf.ComplexType:
switch t.ByteSize {
case 8:
v.Kind = reflect.Complex64
case 16:
v.Kind = reflect.Complex128
}
case *dwarf.IntType:
v.Kind = reflect.Int
case *dwarf.UintType:
v.Kind = reflect.Uint
case *dwarf.FloatType:
switch t.ByteSize {
case 4:
v.Kind = reflect.Float32
case 8:
v.Kind = reflect.Float64
}
case *dwarf.BoolType:
v.Kind = reflect.Bool
case *dwarf.FuncType:
v.Kind = reflect.Func
case *dwarf.VoidType:
v.Kind = reflect.Invalid
case *dwarf.UnspecifiedType:
v.Kind = reflect.Invalid
default:
v.Unreadable = fmt.Errorf("Unknown type: %T", t)
}
return v, nil
return v
}
func (v *Variable) clone() *Variable {
r := *v
return &r
}
func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
if v.Unreadable != nil {
return v.clone(), nil
}
if v.Addr == 0 {
return nil, fmt.Errorf("%s is nil", v.Name)
}
@ -137,7 +198,7 @@ func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
name = fmt.Sprintf("%s.%s", v.Name, field.Name)
}
}
return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread)
return newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.thread), nil
}
func (scope *EvalScope) DwarfReader() *reader.Reader {
@ -271,7 +332,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
if err != nil {
return nil, err
}
waitreason, err := thread.readString(uintptr(waitReasonAddr))
waitreason, _, err := thread.readString(uintptr(waitReasonAddr))
if err != nil {
return nil, err
}
@ -355,11 +416,14 @@ func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) {
return nil, origErr
}
v.Name = name
return v, nil
} else {
for _, memberName := range memberNames {
v, err = v.structMember(memberName)
if err != nil {
return nil, err
if len(memberNames) > 0 {
for i := range memberNames {
v, err = v.structMember(memberNames[i])
if err != nil {
return nil, err
}
}
}
}
@ -372,8 +436,8 @@ func (scope *EvalScope) EvalVariable(name string) (*Variable, error) {
if err != nil {
return nil, err
}
err = v.loadValue(true)
return v, err
v.loadValue()
return v, nil
}
// Sets the value of the named variable
@ -382,6 +446,9 @@ func (scope *EvalScope) SetVariable(name, value string) error {
if err != nil {
return err
}
if v.Unreadable != nil {
return fmt.Errorf("Variable \"%s\" is unreadable: %v\n", name, v.Unreadable)
}
return v.setValue(value)
}
@ -391,8 +458,8 @@ func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable,
if err != nil {
return nil, err
}
err = v.loadValue(true)
return v, err
v.loadValue()
return v, nil
}
func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
@ -459,8 +526,8 @@ func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) {
if err != nil {
return nil, err
}
err = v.loadValue(true)
return v, err
v.loadValue()
return v, nil
}
func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) {
@ -483,13 +550,16 @@ func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) {
}
func (v *Variable) structMember(memberName string) (*Variable, error) {
structVar, err := v.maybeDereference()
structVar.Name = v.Name
if err != nil {
return nil, err
if v.Unreadable != nil {
return v.clone(), nil
}
structVar = structVar.resolveTypedefs()
switch t := structVar.dwarfType.(type) {
structVar := v.maybeDereference()
structVar.Name = v.Name
if structVar.Unreadable != nil {
return structVar, nil
}
switch t := structVar.RealType.(type) {
case *dwarf.StructType:
for _, field := range t.Field {
if field.Name != memberName {
@ -530,7 +600,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
}
return nil, fmt.Errorf("%s has no member %s", v.Name, memberName)
default:
return nil, fmt.Errorf("%s type %s is not a struct", v.Name, structVar.dwarfType)
return nil, fmt.Errorf("%s type %s is not a struct", v.Name, structVar.DwarfType)
}
}
@ -570,142 +640,92 @@ func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry, rdr *reader.
return nil, err
}
return newVariable(n, uintptr(addr), t, scope.Thread)
return newVariable(n, uintptr(addr), t, scope.Thread), nil
}
// If v is a pointer a new variable is returned containing the value pointed by v.
func (v *Variable) maybeDereference() (*Variable, error) {
v = v.resolveTypedefs()
func (v *Variable) maybeDereference() *Variable {
if v.Unreadable != nil {
return v
}
switch t := v.dwarfType.(type) {
switch t := v.RealType.(type) {
case *dwarf.PtrType:
ptrval, err := v.thread.readUintRaw(uintptr(v.Addr), int64(v.thread.dbp.arch.PtrSize()))
r := newVariable("", uintptr(ptrval), t.Type, v.thread)
if err != nil {
return nil, err
r.Unreadable = err
}
return newVariable("", uintptr(ptrval), t.Type, v.thread)
return r
default:
return v, nil
return v
}
}
// Returns a Variable with the same address but a concrete dwarfType.
func (v *Variable) resolveTypedefs() *Variable {
typ := v.dwarfType
for {
if tt, ok := typ.(*dwarf.TypedefType); ok {
typ = tt.Type
} else {
break
}
}
r := *v
r.dwarfType = typ
return &r
}
// Extracts the value of the variable at the given address.
func (v *Variable) loadValue(printStructName bool) (err error) {
v.Value, err = v.loadValueInternal(printStructName, 0)
return
func (v *Variable) loadValue() {
v.loadValueInternal(0)
}
func (v *Variable) loadValueInternal(printStructName bool, recurseLevel int) (string, error) {
v = v.resolveTypedefs()
switch t := v.dwarfType.(type) {
case *dwarf.PtrType:
ptrv, err := v.maybeDereference()
if err != nil {
return "", err
}
if ptrv.Addr == 0 {
return fmt.Sprintf("%s nil", t.String()), nil
}
// Don't increase the recursion level when dereferencing pointers
val, err := ptrv.loadValueInternal(printStructName, recurseLevel)
if err != nil {
return "", err
}
return fmt.Sprintf("*%s", val), nil
case *dwarf.StructType:
switch {
case t.StructName == "string":
return v.thread.readString(uintptr(v.Addr))
case strings.HasPrefix(t.StructName, "[]"):
return v.loadArrayValues(recurseLevel)
default:
// Recursively call extractValue to grab
// the value of all the members of the struct.
if recurseLevel <= maxVariableRecurse {
errcount := 0
fields := make([]string, 0, len(t.Field))
for i, field := range t.Field {
var (
err error
val string
fieldvar *Variable
)
fieldvar, err = v.toField(field)
if err == nil {
val, err = fieldvar.loadValueInternal(printStructName, recurseLevel+1)
}
if err != nil {
errcount++
val = fmt.Sprintf("<unreadable: %s>", err.Error())
}
fields = append(fields, fmt.Sprintf("%s: %s", field.Name, val))
if errcount > maxErrCount {
fields = append(fields, fmt.Sprintf("...+%d more", len(t.Field)-i))
}
}
if printStructName {
return fmt.Sprintf("%s {%s}", t.StructName, strings.Join(fields, ", ")), nil
}
return fmt.Sprintf("{%s}", strings.Join(fields, ", ")), nil
}
// no fields
if printStructName {
return fmt.Sprintf("%s {...}", t.StructName), nil
}
return "{...}", nil
}
case *dwarf.ArrayType:
return v.loadArrayValues(recurseLevel)
case *dwarf.ComplexType:
return v.readComplex(t.ByteSize)
case *dwarf.IntType:
return v.readInt(t.ByteSize)
case *dwarf.UintType:
return v.readUint(t.ByteSize)
case *dwarf.FloatType:
return v.readFloat(t.ByteSize)
case *dwarf.BoolType:
return v.readBool()
case *dwarf.FuncType:
return v.readFunctionPtr()
case *dwarf.VoidType:
return "(void)", nil
case *dwarf.UnspecifiedType:
return "(unknown)", nil
default:
fmt.Printf("Unknown type: %T\n", t)
func (v *Variable) loadValueInternal(recurseLevel int) {
if v.Unreadable != nil || v.loaded || v.Addr == 0 {
return
}
v.loaded = true
switch v.Kind {
case reflect.Ptr:
v.Len = 1
v.Children = []Variable{*v.maybeDereference()}
// Don't increase the recursion level when dereferencing pointers
v.Children[0].loadValueInternal(recurseLevel)
return "", fmt.Errorf("could not find value for type %s", v.dwarfType)
case reflect.String:
v.Value, v.Len, v.Unreadable = v.thread.readString(uintptr(v.Addr))
case reflect.Slice, reflect.Array:
v.loadArrayValues(recurseLevel)
case reflect.Struct:
t := v.RealType.(*dwarf.StructType)
v.Len = int64(len(t.Field))
// Recursively call extractValue to grab
// the value of all the members of the struct.
if recurseLevel <= maxVariableRecurse {
v.Children = make([]Variable, 0, len(t.Field))
for i, field := range t.Field {
f, _ := v.toField(field)
v.Children = append(v.Children, *f)
v.Children[i].Name = field.Name
v.Children[i].loadValueInternal(recurseLevel + 1)
}
}
case reflect.Complex64, reflect.Complex128:
v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v.Value, v.Unreadable = v.thread.readIntRaw(v.Addr, v.RealType.(*dwarf.IntType).ByteSize)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
v.Value, v.Unreadable = v.thread.readUintRaw(v.Addr, v.RealType.(*dwarf.UintType).ByteSize)
case reflect.Bool:
val, err := v.thread.readMemory(v.Addr, 1)
v.Unreadable = err
if err == nil {
v.Value = val[0] != 0
}
case reflect.Float32, reflect.Float64:
v.Value, v.Unreadable = v.readFloatRaw(v.RealType.(*dwarf.FloatType).ByteSize)
case reflect.Func:
v.readFunctionPtr()
case reflect.Map:
fallthrough
default:
v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String())
}
}
func (v *Variable) setValue(value string) error {
v = v.resolveTypedefs()
switch t := v.dwarfType.(type) {
switch t := v.RealType.(type) {
case *dwarf.PtrType:
return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize()))
case *dwarf.ComplexType:
@ -719,22 +739,22 @@ func (v *Variable) setValue(value string) error {
case *dwarf.BoolType:
return v.writeBool(value)
default:
return fmt.Errorf("Can not set value of variables of type: %T\n", t)
return fmt.Errorf("Can not set value of variables of kind: %s\n", v.RealType.String())
}
}
func (thread *Thread) readString(addr uintptr) (string, error) {
func (thread *Thread) readString(addr uintptr) (string, int64, error) {
// string data structure is always two ptrs in size. Addr, followed by len
// http://research.swtch.com/godata
// read len
val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize())
if err != nil {
return "", fmt.Errorf("could not read string len %s", err)
return "", 0, fmt.Errorf("could not read string len %s", err)
}
strlen := int(binary.LittleEndian.Uint64(val))
strlen := int64(binary.LittleEndian.Uint64(val))
if strlen < 0 {
return "", fmt.Errorf("invalid length: %d", strlen)
return "", 0, fmt.Errorf("invalid length: %d", strlen)
}
count := strlen
@ -745,28 +765,24 @@ func (thread *Thread) readString(addr uintptr) (string, error) {
// read addr
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
if err != nil {
return "", fmt.Errorf("could not read string pointer %s", err)
return "", 0, fmt.Errorf("could not read string pointer %s", err)
}
addr = uintptr(binary.LittleEndian.Uint64(val))
if addr == 0 {
return "", nil
return "", 0, nil
}
val, err = thread.readMemory(addr, count)
val, err = thread.readMemory(addr, int(count))
if err != nil {
return "", fmt.Errorf("could not read string at %#v due to %s", addr, err)
return "", 0, fmt.Errorf("could not read string at %#v due to %s", addr, err)
}
retstr := *(*string)(unsafe.Pointer(&val))
if count != strlen {
retstr = retstr + fmt.Sprintf("...+%d more", strlen-count)
}
return retstr, nil
return retstr, strlen, nil
}
func (v *Variable) loadSliceInfo(t *dwarf.StructType) error {
func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
var err error
for _, f := range t.Field {
switch f.Name {
@ -778,31 +794,30 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) error {
// Dereference array type to get value type
ptrType, ok := f.Type.(*dwarf.PtrType)
if !ok {
return fmt.Errorf("Invalid type %s in slice array", f.Type)
v.Unreadable = fmt.Errorf("Invalid type %s in slice array", f.Type)
return
}
v.fieldType = ptrType.Type
}
case "len":
lstrAddr, err := v.toField(f)
lstrAddr, _ := v.toField(f)
lstrAddr.loadValue()
err = lstrAddr.Unreadable
if err == nil {
err = lstrAddr.loadValue(true)
}
if err == nil {
v.Len, err = strconv.ParseInt(lstrAddr.Value, 10, 64)
v.Len = lstrAddr.Value.(int64)
}
case "cap":
cstrAddr, err := v.toField(f)
cstrAddr, _ := v.toField(f)
cstrAddr.loadValue()
err = cstrAddr.Unreadable
if err == nil {
err = cstrAddr.loadValue(true)
}
if err == nil {
v.Cap, err = strconv.ParseInt(cstrAddr.Value, 10, 64)
v.Cap = cstrAddr.Value.(int64)
}
}
}
if err != nil {
return nil
if err != nil {
v.Unreadable = err
return
}
}
v.stride = v.fieldType.Size()
@ -810,45 +825,37 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) error {
v.stride = int64(v.thread.dbp.arch.PtrSize())
}
return nil
return
}
func (v *Variable) loadArrayValues(recurseLevel int) (string, error) {
vals := make([]string, 0)
func (v *Variable) loadArrayValues(recurseLevel int) {
if v.Unreadable != nil {
return
}
errcount := 0
for i := int64(0); i < v.Len; i++ {
// Cap number of elements
if i >= maxArrayValues {
vals = append(vals, fmt.Sprintf("...+%d more", v.Len-maxArrayValues))
break
}
var val string
fieldvar, err := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
if err == nil {
val, err = fieldvar.loadValueInternal(false, recurseLevel+1)
}
if err != nil {
fieldvar := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
fieldvar.loadValueInternal(recurseLevel + 1)
if fieldvar.Unreadable != nil {
errcount++
val = fmt.Sprintf("<unreadable: %s>", err.Error())
}
vals = append(vals, val)
v.Children = append(v.Children, *fieldvar)
if errcount > maxErrCount {
vals = append(vals, fmt.Sprintf("...+%d more", v.Len-i))
break
}
}
if v.Cap < 0 {
return fmt.Sprintf("%s [%s]", v.dwarfType, strings.Join(vals, ",")), nil
} else {
return fmt.Sprintf("[]%s len: %d, cap: %d, [%s]", v.fieldType, v.Len, v.Cap, strings.Join(vals, ",")), nil
}
}
func (v *Variable) readComplex(size int64) (string, error) {
func (v *Variable) readComplex(size int64) {
var fs int64
switch size {
case 8:
@ -856,19 +863,18 @@ func (v *Variable) readComplex(size int64) (string, error) {
case 16:
fs = 8
default:
return "", fmt.Errorf("invalid size (%d) for complex type", size)
v.Unreadable = fmt.Errorf("invalid size (%d) for complex type", size)
return
}
r, err := v.readFloat(fs)
if err != nil {
return "", err
}
imagvar := *v
imagvar.Addr += uintptr(fs)
i, err := imagvar.readFloat(fs)
if err != nil {
return "", err
}
return fmt.Sprintf("(%s + %si)", r, i), nil
ftyp := &dwarf.FloatType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: fs, Name: fmt.Sprintf("float%d", fs)}, BitSize: fs * 8, BitOffset: 0}}
realvar := newVariable("real", v.Addr, ftyp, v.thread)
imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread)
realvar.loadValue()
imagvar.loadValue()
v.Len = 2
v.Children = []Variable{*realvar, *imagvar}
}
func (v *Variable) writeComplex(value string, size int64) error {
@ -946,14 +952,6 @@ func (v *Variable) writeComplex(value string, size int64) error {
return imagaddr.writeFloatRaw(imag, int64(size/2))
}
func (v *Variable) readInt(size int64) (string, error) {
n, err := v.thread.readIntRaw(v.Addr, size)
if err != nil {
return "", err
}
return strconv.FormatInt(n, 10), nil
}
func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) {
var n int64
@ -976,14 +974,6 @@ func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) {
return n, nil
}
func (v *Variable) readUint(size int64) (string, error) {
n, err := v.thread.readUintRaw(v.Addr, size)
if err != nil {
return "", err
}
return strconv.FormatUint(n, 10), nil
}
func (v *Variable) writeUint(signed bool, value string, size int64) error {
var (
n uint64
@ -1039,10 +1029,10 @@ func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) {
return n, nil
}
func (v *Variable) readFloat(size int64) (string, error) {
func (v *Variable) readFloatRaw(size int64) (float64, error) {
val, err := v.thread.readMemory(v.Addr, int(size))
if err != nil {
return "", err
return 0.0, err
}
buf := bytes.NewBuffer(val)
@ -1050,14 +1040,14 @@ func (v *Variable) readFloat(size int64) (string, error) {
case 4:
n := float32(0)
binary.Read(buf, binary.LittleEndian, &n)
return strconv.FormatFloat(float64(n), 'f', -1, int(size)*8), nil
return float64(n), nil
case 8:
n := float64(0)
binary.Read(buf, binary.LittleEndian, &n)
return strconv.FormatFloat(n, 'f', -1, int(size)*8), nil
return n, nil
}
return "", fmt.Errorf("could not read float")
return 0.0, fmt.Errorf("could not read float")
}
func (v *Variable) writeFloat(value string, size int64) error {
@ -1084,19 +1074,6 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error {
return err
}
func (v *Variable) readBool() (string, error) {
val, err := v.thread.readMemory(v.Addr, 1)
if err != nil {
return "", err
}
if val[0] == 0 {
return "false", nil
}
return "true", nil
}
func (v *Variable) writeBool(value string) error {
b, err := strconv.ParseBool(value)
if err != nil {
@ -1110,30 +1087,34 @@ func (v *Variable) writeBool(value string) error {
return err
}
func (v *Variable) readFunctionPtr() (string, error) {
func (v *Variable) readFunctionPtr() {
val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize())
if err != nil {
return "", err
v.Unreadable = err
return
}
// dereference pointer to find function pc
fnaddr := uintptr(binary.LittleEndian.Uint64(val))
if fnaddr == 0 {
return "nil", nil
v.Unreadable = err
return
}
val, err = v.thread.readMemory(fnaddr, v.thread.dbp.arch.PtrSize())
if err != nil {
return "", err
v.Unreadable = err
return
}
funcAddr := binary.LittleEndian.Uint64(val)
fn := v.thread.dbp.goSymTable.PCToFunc(uint64(funcAddr))
if fn == nil {
return "", fmt.Errorf("could not find function for %#v", funcAddr)
v.Unreadable = fmt.Errorf("could not find function for %#v", funcAddr)
return
}
return fn.Name, nil
v.Value = fn.Name
}
// Fetches all variables of a specific type in the current function scope

@ -1,419 +0,0 @@
package proc
import (
"fmt"
"sort"
"strconv"
"testing"
protest "github.com/derekparker/delve/proc/test"
)
type varTest struct {
name string
value string
setTo string
varType string
err error
}
func assertVariable(t *testing.T, variable *Variable, expected varTest) {
if variable.Name != expected.name {
t.Fatalf("Expected %s got %s\n", expected.name, variable.Name)
}
if variable.Type != expected.varType {
t.Fatalf("Expected %s got %s (for variable %s)\n", expected.varType, variable.Type, expected.name)
}
if variable.Value != expected.value {
t.Fatalf("Expected %#v got %#v (for variable %s)\n", expected.value, variable.Value, expected.name)
}
}
func evalVariable(p *Process, symbol string) (*Variable, error) {
scope, err := p.CurrentThread.Scope()
if err != nil {
return nil, err
}
return scope.EvalVariable(symbol)
}
func (tc *varTest) settable() bool {
return tc.setTo != ""
}
func (tc *varTest) afterSet() varTest {
r := *tc
r.value = r.setTo
return r
}
func setVariable(p *Process, symbol, value string) error {
scope, err := p.CurrentThread.Scope()
if err != nil {
return err
}
return scope.SetVariable(symbol, value)
}
const varTestBreakpointLineNumber = 59
func TestVariableEvaluation(t *testing.T) {
testcases := []varTest{
{"a1", "foofoofoofoofoofoo", "", "struct string", nil},
{"a10", "ofo", "", "struct string", nil},
{"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil},
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil},
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil},
{"a2", "6", "10", "int", nil},
{"a3", "7.23", "3.1", "float64", nil},
{"a4", "[2]int [1,2]", "", "[2]int", nil},
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "", "*main.FooBar", nil},
{"baz", "bazburzum", "", "struct string", nil},
{"neg", "-1", "-20", "int", nil},
{"f32", "1.2", "1.1", "float32", nil},
{"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil},
{"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil},
{"a6.Baz", "8", "20", "int", nil},
{"a7.Baz", "5", "25", "int", nil},
{"a8.Baz", "feh", "", "struct string", nil},
{"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")},
{"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil}, // reread variable after member
{"i32", "[2]int32 [1,2]", "", "[2]int32", nil},
{"b1", "true", "false", "bool", nil},
{"b2", "false", "true", "bool", nil},
{"i8", "1", "2", "int8", nil},
{"u16", "65535", "0", "uint16", nil},
{"u32", "4294967295", "1", "uint32", nil},
{"u64", "18446744073709551615", "2", "uint64", nil},
{"u8", "255", "3", "uint8", nil},
{"up", "5", "4", "uintptr", nil},
{"f", "main.barfoo", "", "func()", nil},
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil},
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil},
{"ms.Nest.Nest", "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *main.Nest {...}}}", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest", "*main.Nest nil", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest.Nest", "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")},
{"main.p1", "10", "12", "int", nil},
{"p1", "10", "13", "int", nil},
{"NonExistent", "", "", "", fmt.Errorf("could not find symbol value for NonExistent")},
}
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
_, err := p.SetBreakpoint(pc)
assertNoError(err, t, "SetBreakpoint() returned an error")
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
if tc.err == nil {
assertNoError(err, t, "EvalVariable() returned an error")
assertVariable(t, variable, tc)
} else {
if tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
if tc.settable() {
assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()")
variable, err = evalVariable(p, tc.name)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc.afterSet())
assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()")
variable, err := evalVariable(p, tc.name)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc)
}
}
})
}
func TestVariableFunctionScoping(t *testing.T) {
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
_, err := p.SetBreakpoint(pc)
assertNoError(err, t, "SetBreakpoint() returned an error")
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
p.ClearBreakpoint(pc)
_, err = evalVariable(p, "a1")
assertNoError(err, t, "Unable to find variable a1")
_, err = evalVariable(p, "a2")
assertNoError(err, t, "Unable to find variable a1")
// Move scopes, a1 exists here by a2 does not
pc, _, _ = p.goSymTable.LineToPC(fixture.Source, 23)
_, err = p.SetBreakpoint(pc)
assertNoError(err, t, "SetBreakpoint() returned an error")
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
_, err = evalVariable(p, "a1")
assertNoError(err, t, "Unable to find variable a1")
_, err = evalVariable(p, "a2")
if err == nil {
t.Fatalf("Can eval out of scope variable a2")
}
})
}
type varArray []*Variable
// Len is part of sort.Interface.
func (s varArray) Len() int {
return len(s)
}
// Swap is part of sort.Interface.
func (s varArray) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s varArray) Less(i, j int) bool {
return s[i].Name < s[j].Name
}
func TestLocalVariables(t *testing.T) {
testcases := []struct {
fn func(*EvalScope) ([]*Variable, error)
output []varTest
}{
{(*EvalScope).LocalVariables,
[]varTest{
{"a1", "foofoofoofoofoofoo", "", "struct string", nil},
{"a10", "ofo", "", "struct string", nil},
{"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "", "[3]main.FooBar", nil},
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "", "struct []main.FooBar", nil},
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "", "struct []*main.FooBar", nil},
{"a2", "6", "", "int", nil},
{"a3", "7.23", "", "float64", nil},
{"a4", "[2]int [1,2]", "", "[2]int", nil},
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "", "*main.FooBar", nil},
{"b1", "true", "", "bool", nil},
{"b2", "false", "", "bool", nil},
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil},
{"c128", "(2 + 3i)", "", "complex128", nil},
{"c64", "(1 + 2i)", "", "complex64", nil},
{"f", "main.barfoo", "", "func()", nil},
{"f32", "1.2", "", "float32", nil},
{"i32", "[2]int32 [1,2]", "", "[2]int32", nil},
{"i8", "1", "", "int8", nil},
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "", "main.Nest", nil},
{"neg", "-1", "", "int", nil},
{"u16", "65535", "", "uint16", nil},
{"u32", "4294967295", "", "uint32", nil},
{"u64", "18446744073709551615", "", "uint64", nil},
{"u8", "255", "", "uint8", nil},
{"up", "5", "", "uintptr", nil}}},
{(*EvalScope).FunctionArguments,
[]varTest{
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "", "main.FooBar", nil},
{"baz", "bazburzum", "", "struct string", nil}}},
}
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
_, err := p.SetBreakpoint(pc)
assertNoError(err, t, "SetBreakpoint() returned an error")
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "AsScope()")
vars, err := tc.fn(scope)
assertNoError(err, t, "LocalVariables() returned an error")
sort.Sort(varArray(vars))
if len(tc.output) != len(vars) {
t.Fatalf("Invalid variable count. Expected %d got %d.", len(tc.output), len(vars))
}
for i, variable := range vars {
assertVariable(t, variable, tc.output[i])
}
}
})
}
func TestRecursiveStructure(t *testing.T) {
withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
v, err := evalVariable(p, "aas")
assertNoError(err, t, "EvalVariable()")
t.Logf("v: %v\n", v)
})
}
func TestFrameEvaluation(t *testing.T) {
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
_, err := setFunctionBreakpoint(p, "main.stacktraceme")
assertNoError(err, t, "setFunctionBreakpoint")
assertNoError(p.Continue(), t, "Continue()")
/**** Testing evaluation on goroutines ****/
gs, err := p.GoroutinesInfo()
assertNoError(err, t, "GoroutinesInfo")
found := make([]bool, 10)
for _, g := range gs {
frame := -1
frames, err := p.GoroutineStacktrace(g, 10)
assertNoError(err, t, "GoroutineStacktrace()")
for i := range frames {
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
frame = i
break
}
}
if frame < 0 {
t.Logf("Goroutine %d: could not find correct frame", g.Id)
continue
}
scope, err := p.ConvertEvalScope(g.Id, frame)
assertNoError(err, t, "ConvertEvalScope()")
t.Logf("scope = %v", scope)
v, err := scope.EvalVariable("i")
t.Logf("v = %v", v)
if err != nil {
t.Logf("Goroutine %d: %v\n", g.Id, err)
continue
}
i, err := strconv.Atoi(v.Value)
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s)", v.Value))
found[i] = true
}
for i := range found {
if !found[i] {
t.Fatalf("Goroutine %d not found\n", i)
}
}
/**** Testing evaluation on frames ****/
assertNoError(p.Continue(), t, "Continue() 2")
g, err := p.CurrentThread.GetG()
assertNoError(err, t, "GetG()")
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")
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
n, err := strconv.Atoi(v.Value)
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s) on frame %d", v.Value, i+1))
t.Logf("frame %d n %d\n", i+1, n)
if n != 3-i {
t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i)
}
}
})
}
func TestComplexSetting(t *testing.T) {
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
_, err := p.SetBreakpoint(pc)
assertNoError(err, t, "SetBreakpoint() returned an error")
err = p.Continue()
assertNoError(err, t, "Continue() returned an error")
h := func(setExpr, value string) {
assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()")
variable, err := evalVariable(p, "c128")
assertNoError(err, t, "EvalVariable()")
if variable.Value != value {
t.Fatalf("Wrong value of c128: \"%s\", expected \"%s\" after setting it to \"%s\"", variable.Value, value, setExpr)
}
}
h("3.2i", "(0 + 3.2i)")
h("1.1", "(1.1 + 0i)")
h("1 + 3.3i", "(1 + 3.3i)")
h("complex128(1.2, 3.4)", "(1.2 + 3.4i)")
})
}
func TestPointerSetting(t *testing.T) {
withTestProcess("testvariables3", t, func(p *Process, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue() returned an error")
pval := func(value string) {
variable, err := evalVariable(p, "p1")
assertNoError(err, t, "EvalVariable()")
if variable.Value != value {
t.Fatalf("Wrong value of p1, \"%s\" expected \"%s\"", variable.Value, value)
}
}
pval("*1")
// change p1 to point to i2
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "Scope()")
i2addr, err := scope.ExtractVariableInfo("i2")
assertNoError(err, t, "EvalVariableAddr()")
assertNoError(setVariable(p, "p1", strconv.Itoa(int(i2addr.Addr))), t, "SetVariable()")
pval("*2")
// change the value of i2 check that p1 also changes
assertNoError(setVariable(p, "i2", "5"), t, "SetVariable()")
pval("*5")
})
}
func TestEmbeddedStruct(t *testing.T) {
withTestProcess("testvariables4", t, func(p *Process, fixture protest.Fixture) {
testcases := []varTest{
{"b.val", "-314", "", "int", nil},
{"b.A.val", "-314", "", "int", nil},
{"b.a.val", "42", "", "int", nil},
{"b.ptr.val", "1337", "", "int", nil},
{"b.C.s", "hello", "", "struct string", nil},
{"b.s", "hello", "", "struct string", nil},
}
assertNoError(p.Continue(), t, "Continue()")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
if tc.err == nil {
assertNoError(err, t, "EvalVariable() returned an error")
assertVariable(t, variable, tc)
} else {
if tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
}
})
}

@ -1,7 +1,9 @@
package api
import (
"debug/dwarf"
"debug/gosym"
"fmt"
"strconv"
"github.com/derekparker/delve/proc"
@ -57,12 +59,43 @@ func ConvertThread(th *proc.Thread) *Thread {
}
// convertVar converts an internal variable to an API Variable.
func ConvertVar(v *proc.Variable) Variable {
return Variable{
Name: v.Name,
Value: v.Value,
Type: v.Type,
func ConvertVar(v *proc.Variable) *Variable {
r := Variable{
Addr: v.Addr,
Name: v.Name,
Kind: v.Kind,
Len: v.Len,
Cap: v.Cap,
}
if v.DwarfType != nil {
r.Type = v.DwarfType.String()
}
if v.RealType != nil {
r.RealType = v.RealType.String()
}
if v.Unreadable != nil {
r.Unreadable = v.Unreadable.Error()
}
switch typ := v.RealType.(type) {
case *dwarf.FloatType:
r.Value = strconv.FormatFloat(v.Value.(float64), 'f', -1, int(typ.Size()*8))
default:
if v.Value != nil {
r.Value = fmt.Sprintf("%v", v.Value)
}
}
r.Children = make([]Variable, len(v.Children))
for i := range v.Children {
r.Children[i] = *ConvertVar(&v.Children[i])
}
return &r
}
func ConvertFunction(fn *gosym.Func) *Function {

262
service/api/prettyprint.go Normal file

@ -0,0 +1,262 @@
package api
import (
"bytes"
"fmt"
"reflect"
)
const (
// strings longer than this will cause slices, arrays and structs to be printed on multiple lines when newlines is enabled
maxShortStringLen = 7
// string used for one indentation level (when printing on multiple lines)
indentString = "\t"
)
// Returns a representation of v on a single line
func (v *Variable) SinglelineString() string {
var buf bytes.Buffer
v.writeTo(&buf, false, true, "")
return buf.String()
}
// 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, indent)
return buf.String()
}
func (v *Variable) writeTo(buf *bytes.Buffer, newlines, includeType bool, indent string) {
if v.Unreadable != "" {
fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
return
}
if v.Addr == 0 {
fmt.Fprintf(buf, "%s nil", v.Type)
return
}
switch v.Kind {
case reflect.Slice:
v.writeSliceTo(buf, newlines, includeType, indent)
case reflect.Array:
v.writeArrayTo(buf, newlines, includeType, indent)
case reflect.Ptr:
fmt.Fprintf(buf, "*")
v.Children[0].writeTo(buf, newlines, includeType, indent)
case reflect.String:
v.writeStringTo(buf)
case reflect.Struct:
v.writeStructTo(buf, newlines, includeType, indent)
case reflect.Map:
v.writeMapTo(buf, newlines, includeType, indent)
case reflect.Func:
fmt.Fprintf(buf, "%s", v.Value)
case reflect.Complex64, reflect.Complex128:
switch v.RealType {
case "complex64":
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
case "complex128":
fmt.Fprintf(buf, "(%s + %si)", v.Children[0].Value, v.Children[1].Value)
}
default:
if v.Value != "" {
buf.Write([]byte(v.Value))
} else {
fmt.Fprintf(buf, "(unknown %s)", v.Kind)
}
}
}
func (v *Variable) writeStringTo(buf *bytes.Buffer) {
s := v.Value
if len(s) != int(v.Len) {
s = fmt.Sprintf("%s...+%d more", s, int(v.Len)-len(s))
}
fmt.Fprintf(buf, "%q", s)
}
func (v *Variable) writeSliceTo(buf *bytes.Buffer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type[len("struct "):], v.Len, v.Cap)
}
v.writeSliceOrArrayTo(buf, newlines, indent)
}
func (v *Variable) writeArrayTo(buf *bytes.Buffer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
v.writeSliceOrArrayTo(buf, newlines, indent)
}
func (v *Variable) writeStructTo(buf *bytes.Buffer, newlines, includeType bool, indent string) {
if int(v.Len) != len(v.Children) {
fmt.Fprintf(buf, "(*%s)(0x%x)", v.Type, v.Addr)
return
}
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
nl := v.shouldNewlineStruct(newlines)
fmt.Fprintf(buf, "{")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
fmt.Fprintf(buf, "%s: ", v.Children[i].Name)
v.Children[i].writeTo(buf, nl, true, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ",")
if !nl {
fmt.Fprintf(buf, " ")
}
}
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprintf(buf, "}")
}
func (v *Variable) writeMapTo(buf *bytes.Buffer, newlines, includeType bool, indent string) {
if includeType {
fmt.Fprintf(buf, "%s ", v.Type)
}
nl := newlines && (len(v.Children) > 0)
fmt.Fprintf(buf, "[")
for i := 0; i < len(v.Children); i += 2 {
key := &v.Children[i]
value := &v.Children[i+1]
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
key.writeTo(buf, false, false, indent+indentString)
fmt.Fprintf(buf, ": ")
value.writeTo(buf, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ", ")
}
}
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))
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprintf(buf, "]")
}
func (v *Variable) shouldNewlineArray(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
kind, hasptr := (&v.Children[0]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map:
return true
case reflect.String:
if hasptr {
return true
}
for i := range v.Children {
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
return false
default:
return false
}
}
func (v *Variable) recursiveKind() (reflect.Kind, bool) {
hasptr := false
var kind reflect.Kind
for {
kind = v.Kind
if kind == reflect.Ptr {
hasptr = true
v = &(v.Children[0])
} else {
break
}
}
return kind, hasptr
}
func (v *Variable) shouldNewlineStruct(newlines bool) bool {
if !newlines || len(v.Children) == 0 {
return false
}
for i := range v.Children {
kind, hasptr := (&v.Children[i]).recursiveKind()
switch kind {
case reflect.Slice, reflect.Array, reflect.Struct, reflect.Map:
return true
case reflect.String:
if hasptr {
return true
}
if len(v.Children[i].Value) > maxShortStringLen {
return true
}
}
}
return false
}
func (v *Variable) writeSliceOrArrayTo(buf *bytes.Buffer, newlines bool, indent string) {
nl := v.shouldNewlineArray(newlines)
fmt.Fprintf(buf, "[")
for i := range v.Children {
if nl {
fmt.Fprintf(buf, "\n%s%s", indent, indentString)
}
v.Children[i].writeTo(buf, nl, false, indent+indentString)
if i != len(v.Children)-1 || nl {
fmt.Fprintf(buf, ",")
}
}
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))
}
if nl {
fmt.Fprintf(buf, "\n%s", indent)
}
fmt.Fprintf(buf, "]")
}

@ -1,5 +1,7 @@
package api
import "reflect"
// DebuggerState represents the current context of the debugger.
type DebuggerState struct {
// Breakpoint is the current breakpoint at which the debugged process is
@ -104,9 +106,35 @@ type Function struct {
// Variable describes a variable.
type Variable struct {
Name string `json:"name"`
// Name of the variable or struct member
Name string `json:"name"`
// Address of the variable or struct member
Addr uintptr `json:"addr"`
// Go type of the variable
Type string `json:"type"`
// Type of the variable after resolving any typedefs
RealType string `json:"realType"`
Kind reflect.Kind `json:"kind"`
//Strings have their length capped at proc.maxArrayValues, use Len for the real length of a string
//Function variables will store the name of the function in this field
Value string `json:"value"`
Type string `json:"type"`
// Number of elements in an array or a slice, number of keys for a map, number of struct members for a struct, length of strings
Len int64 `json:"len"`
// Cap value for slices
Cap int64 `json:"cap"`
// Array and slice elements, member fields of structs, key/value pairs of maps, value of complex numbers
// The Name field in this slice will always be the empty string except for structs (when it will be the field name) and for complex numbers (when it will be "real" and "imaginary")
// For maps each map entry will have to items in this slice, even numbered items will represent map keys and odd numbered items will represent their values
// This field's length is capped at proc.maxArrayValues for slices and arrays and 2*proc.maxArrayValues for maps, in the circumnstances where the cap takes effect len(Children) != Len
// The other length cap applied to this field is related to maximum recursion depth, when the maximum recursion depth is reached this field is left empty, contrary to the previous one this cap also applies to structs (otherwise structs will always have all thier member fields returned)
Children []Variable `json:"children"`
// Unreadable addresses will have this field set
Unreadable string `json:"unreadable"`
}
// Goroutine represents the information relevant to Delve from the runtime's

@ -312,11 +312,11 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
if err != nil {
return err
}
bpi.Variables[i] = api.ConvertVar(v)
bpi.Variables[i] = *api.ConvertVar(v)
}
vars, err := functionArguments(s)
args, err := s.FunctionArguments()
if err == nil {
bpi.Arguments = vars
bpi.Arguments = convertVars(args)
}
return nil
}
@ -376,7 +376,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
}
for _, v := range pv {
if regex.Match([]byte(v.Name)) {
vars = append(vars, api.ConvertVar(v))
vars = append(vars, *api.ConvertVar(v))
}
}
return vars, err
@ -397,7 +397,7 @@ func (d *Debugger) Registers(threadID int) (string, error) {
func convertVars(pv []*proc.Variable) []api.Variable {
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, api.ConvertVar(v))
vars = append(vars, *api.ConvertVar(v))
}
return vars
}
@ -419,19 +419,11 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error
if err != nil {
return nil, err
}
return functionArguments(s)
}
func functionArguments(s *proc.EvalScope) ([]api.Variable, error) {
pv, err := s.FunctionArguments()
if err != nil {
return nil, err
}
vars := make([]api.Variable, 0, len(pv))
for _, v := range pv {
vars = append(vars, api.ConvertVar(v))
}
return vars, nil
return convertVars(pv), nil
}
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
@ -443,8 +435,7 @@ func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api
if err != nil {
return nil, err
}
converted := api.ConvertVar(v)
return &converted, err
return api.ConvertVar(v), err
}
func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error {
@ -492,17 +483,19 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, full bool) ([]ap
for i := range rawlocs {
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
if full {
var err error
scope := rawlocs[i].Scope(d.process.CurrentThread)
lv, err := scope.LocalVariables()
locals, err := scope.LocalVariables()
if err != nil {
return nil, err
}
av, err := scope.FunctionArguments()
arguments, err := scope.FunctionArguments()
if err != nil {
return nil, err
}
frame.Locals = convertVars(lv)
frame.Arguments = convertVars(av)
frame.Locals = convertVars(locals)
frame.Arguments = convertVars(arguments)
}
locations = append(locations, frame)
}

@ -456,8 +456,12 @@ func TestClientServer_traceContinue(t *testing.T) {
t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name)
}
if bpi.Variables[0].Value != strconv.Itoa(count-1) {
t.Fatalf("Wrong variable value %s (%d)", bpi.Variables[0].Value, count)
t.Logf("Variable i is %v", bpi.Variables[0])
n, err := strconv.Atoi(bpi.Variables[0].Value)
if err != nil || n != count-1 {
t.Fatalf("Wrong variable value %q (%v %d)", bpi.Variables[0].Value, err, count)
}
}
if state.Exited {
@ -604,10 +608,6 @@ func TestClientServer_FindLocations(t *testing.T) {
func TestClientServer_EvalVariable(t *testing.T) {
withTestClient("testvariables", t, func(c service.Client) {
fp := testProgPath(t, "testvariables")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59})
assertNoError(err, t, "CreateBreakpoint()")
state := <-c.Continue()
if state.Err != nil {
@ -617,20 +617,16 @@ func TestClientServer_EvalVariable(t *testing.T) {
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
assertNoError(err, t, "EvalVariable")
t.Logf("var1: <%s>", var1.Value)
t.Logf("var1: %s", var1.SinglelineString())
if var1.Value != "foofoofoofoofoofoo" {
t.Fatalf("Wrong variable value: %v", var1.Value)
t.Fatalf("Wrong variable value: %s", var1.Value)
}
})
}
func TestClientServer_SetVariable(t *testing.T) {
withTestClient("testvariables", t, func(c service.Client) {
fp := testProgPath(t, "testvariables")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 59})
assertNoError(err, t, "CreateBreakpoint()")
state := <-c.Continue()
if state.Err != nil {
@ -641,10 +637,12 @@ func TestClientServer_SetVariable(t *testing.T) {
a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2")
t.Logf("a2: <%s>", a2.Value)
t.Logf("a2: %v", a2)
if a2.Value != "8" {
t.Fatalf("Wrong variable value: %v", a2.Value)
n, err := strconv.Atoi(a2.Value)
if err != nil && n != 8 {
t.Fatalf("Wrong variable value: %v", a2)
}
})
}
@ -676,9 +674,11 @@ func TestClientServer_FullStacktrace(t *testing.T) {
if arg.Name != "i" {
continue
}
n, err := strconv.Atoi(arg.Value)
assertNoError(err, t, fmt.Sprintf("Wrong value for i in goroutine %d (%s)", g.ID, arg.Value))
found[n] = true
t.Logf("frame %d, variable i is %v\n", arg)
argn, err := strconv.Atoi(arg.Value)
if err == nil {
found[argn] = true
}
}
}
}
@ -707,10 +707,9 @@ func TestClientServer_FullStacktrace(t *testing.T) {
if v == nil {
t.Fatalf("Could not find value of variable n in frame %d", i)
}
n, err := strconv.Atoi(v.Value)
assertNoError(err, t, fmt.Sprintf("Wrong value for n: %s", v.Value))
if n != cur {
t.Fatalf("Expected value %d got %d", cur, n)
vn, err := strconv.Atoi(v.Value)
if err != nil || vn != cur {
t.Fatalf("Expected value %d got %d (error: %v)", cur, vn, err)
}
cur--
if cur < 0 {

@ -0,0 +1,346 @@
package servicetest
import (
"fmt"
"sort"
"strings"
"testing"
"github.com/derekparker/delve/proc"
"github.com/derekparker/delve/service/api"
protest "github.com/derekparker/delve/proc/test"
)
type varTest struct {
name string
value string
setTo string
varType string
err error
}
func matchStringOrPrefix(output, target string) bool {
if strings.HasSuffix(target, "…") {
prefix := target[:len(target)-len("…")]
b := strings.HasPrefix(output, prefix)
return b
} else {
return output == target
}
}
func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) {
if variable.Name != expected.name {
t.Fatalf("Expected %s got %s\n", expected.name, variable.Name)
}
cv := api.ConvertVar(variable)
if cv.Type != expected.varType {
t.Fatalf("Expected %s got %s (for variable %s)\n", expected.varType, cv.Type, expected.name)
}
if ss := cv.SinglelineString(); !matchStringOrPrefix(ss, expected.value) {
t.Fatalf("Expected %#v got %#v (for variable %s)\n", expected.value, ss, expected.name)
}
}
func evalVariable(p *proc.Process, symbol string) (*proc.Variable, error) {
scope, err := p.CurrentThread.Scope()
if err != nil {
return nil, err
}
return scope.EvalVariable(symbol)
}
func (tc *varTest) settable() bool {
return tc.setTo != ""
}
func (tc *varTest) afterSet() varTest {
r := *tc
r.value = r.setTo
return r
}
func setVariable(p *proc.Process, symbol, value string) error {
scope, err := p.CurrentThread.Scope()
if err != nil {
return err
}
return scope.SetVariable(symbol, value)
}
const varTestBreakpointLineNumber = 59
func withTestProcess(name string, t *testing.T, fn func(p *proc.Process, fixture protest.Fixture)) {
fixture := protest.BuildFixture(name)
p, err := proc.Launch([]string{fixture.Path})
if err != nil {
t.Fatal("Launch():", err)
}
defer func() {
p.Halt()
p.Kill()
}()
fn(p, fixture)
}
func TestVariableEvaluation(t *testing.T) {
testcases := []varTest{
{"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil},
{"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil},
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil},
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil},
{"a2", "6", "10", "int", nil},
{"a3", "7.23", "3.1", "float64", nil},
{"a4", "[2]int [1,2]", "", "[2]int", nil},
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "", "*main.FooBar", nil},
{"baz", "\"bazburzum\"", "", "struct string", nil},
{"neg", "-1", "-20", "int", nil},
{"f32", "1.2", "1.1", "float32", nil},
{"c64", "(1 + 2i)", "(4 + 5i)", "complex64", nil},
{"c128", "(2 + 3i)", "(6.3 + 7i)", "complex128", nil},
{"a6.Baz", "8", "20", "int", nil},
{"a7.Baz", "5", "25", "int", nil},
{"a8.Baz", "\"feh\"", "", "struct string", nil},
{"a9.Baz", "nil", "", "int", fmt.Errorf("a9 is nil")},
{"a9.NonExistent", "nil", "", "int", fmt.Errorf("a9 has no member NonExistent")},
{"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member
{"i32", "[2]int32 [1,2]", "", "[2]int32", nil},
{"b1", "true", "false", "bool", nil},
{"b2", "false", "true", "bool", nil},
{"i8", "1", "2", "int8", nil},
{"u16", "65535", "0", "uint16", nil},
{"u32", "4294967295", "1", "uint32", nil},
{"u64", "18446744073709551615", "2", "uint64", nil},
{"u8", "255", "3", "uint8", nil},
{"up", "5", "4", "uintptr", nil},
{"f", "main.barfoo", "", "func()", nil},
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil},
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)(…", "", "main.Nest", nil},
{"ms.Nest.Nest", "*main.Nest {Level: 2, Nest: *main.Nest {Level: 3, Nest: *(*main.Nest)(…", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest", "*main.Nest nil", "", "*main.Nest", nil},
{"ms.Nest.Nest.Nest.Nest.Nest.Nest", "", "", "*main.Nest", fmt.Errorf("ms.Nest.Nest.Nest.Nest.Nest is nil")},
{"main.p1", "10", "12", "int", nil},
{"p1", "10", "13", "int", nil},
{"NonExistent", "", "", "", 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)
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())
}
}
if tc.settable() {
assertNoError(setVariable(p, tc.name, tc.setTo), t, "SetVariable()")
variable, err = evalVariable(p, tc.name)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc.afterSet())
assertNoError(setVariable(p, tc.name, tc.value), t, "SetVariable()")
variable, err := evalVariable(p, tc.name)
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, tc)
}
}
})
}
func TestMultilineVariableEvaluation(t *testing.T) {
testcases := []varTest{
{"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil},
{"a11", `[3]main.FooBar [
{Baz: 1, Bur: "a"},
{Baz: 2, Bur: "b"},
{Baz: 3, Bur: "c"},
]`, "", "[3]main.FooBar", nil},
{"a12", `[]main.FooBar len: 2, cap: 2, [
{Baz: 4, Bur: "d"},
{Baz: 5, Bur: "e"},
]`, "", "struct []main.FooBar", nil},
{"a13", `[]*main.FooBar len: 3, cap: 3, [
*{Baz: 6, Bur: "f"},
*{Baz: 7, Bur: "g"},
*{Baz: 8, Bur: "h"},
]`, "", "struct []*main.FooBar", nil},
{"a2", "6", "10", "int", nil},
{"a4", "[2]int [1,2]", "", "[2]int", nil},
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil}, // reread variable after member
{"i32", "[2]int32 [1,2]", "", "[2]int32", nil},
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil},
{"ms", `main.Nest {
Level: 0,
Nest: *main.Nest {
Level: 1,
Nest: *(*main.Nest)(`, "", "main.Nest", nil},
}
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)
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)
}
}
})
}
type varArray []*proc.Variable
// Len is part of sort.Interface.
func (s varArray) Len() int {
return len(s)
}
// Swap is part of sort.Interface.
func (s varArray) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s varArray) Less(i, j int) bool {
return s[i].Name < s[j].Name
}
func TestLocalVariables(t *testing.T) {
testcases := []struct {
fn func(*proc.EvalScope) ([]*proc.Variable, error)
output []varTest
}{
{(*proc.EvalScope).LocalVariables,
[]varTest{
{"a1", "\"foofoofoofoofoofoo\"", "", "struct string", nil},
{"a10", "\"ofo\"", "", "struct string", nil},
{"a11", "[3]main.FooBar [{Baz: 1, Bur: \"a\"},{Baz: 2, Bur: \"b\"},{Baz: 3, Bur: \"c\"}]", "", "[3]main.FooBar", nil},
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: \"d\"},{Baz: 5, Bur: \"e\"}]", "", "struct []main.FooBar", nil},
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: \"f\"},*{Baz: 7, Bur: \"g\"},*{Baz: 8, Bur: \"h\"}]", "", "struct []*main.FooBar", nil},
{"a2", "6", "", "int", nil},
{"a3", "7.23", "", "float64", nil},
{"a4", "[2]int [1,2]", "", "[2]int", nil},
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: \"word\"}", "", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: \"strum\"}", "", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: \"feh\"}", "", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "", "*main.FooBar", nil},
{"b1", "true", "", "bool", nil},
{"b2", "false", "", "bool", nil},
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "", "struct []int", nil},
{"c128", "(2 + 3i)", "", "complex128", nil},
{"c64", "(1 + 2i)", "", "complex64", nil},
{"f", "main.barfoo", "", "func()", nil},
{"f32", "1.2", "", "float32", nil},
{"i32", "[2]int32 [1,2]", "", "[2]int32", nil},
{"i8", "1", "", "int8", nil},
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *(*main.Nest)…", "", "main.Nest", nil},
{"neg", "-1", "", "int", nil},
{"u16", "65535", "", "uint16", nil},
{"u32", "4294967295", "", "uint32", nil},
{"u64", "18446744073709551615", "", "uint64", nil},
{"u8", "255", "", "uint8", nil},
{"up", "5", "", "uintptr", nil}}},
{(*proc.EvalScope).FunctionArguments,
[]varTest{
{"bar", "main.FooBar {Baz: 10, Bur: \"lorem\"}", "", "main.FooBar", nil},
{"baz", "\"bazburzum\"", "", "struct string", nil}}},
}
withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) {
err := p.Continue()
assertNoError(err, t, "Continue() returned an error")
for _, tc := range testcases {
scope, err := p.CurrentThread.Scope()
assertNoError(err, t, "AsScope()")
vars, err := tc.fn(scope)
assertNoError(err, t, "LocalVariables() returned an error")
sort.Sort(varArray(vars))
if len(tc.output) != len(vars) {
t.Fatalf("Invalid variable count. Expected %d got %d.", len(tc.output), len(vars))
}
for i, variable := range vars {
assertVariable(t, variable, tc.output[i])
}
}
})
}
func TestEmbeddedStruct(t *testing.T) {
withTestProcess("testvariables4", t, func(p *proc.Process, fixture protest.Fixture) {
testcases := []varTest{
{"b.val", "-314", "", "int", nil},
{"b.A.val", "-314", "", "int", nil},
{"b.a.val", "42", "", "int", nil},
{"b.ptr.val", "1337", "", "int", nil},
{"b.C.s", "\"hello\"", "", "struct string", nil},
{"b.s", "\"hello\"", "", "struct string", nil},
}
assertNoError(p.Continue(), t, "Continue()")
for _, tc := range testcases {
variable, err := evalVariable(p, tc.name)
if tc.err == nil {
assertNoError(err, t, "EvalVariable() returned an error")
assertVariable(t, variable, tc)
} else {
if tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
}
})
}
func TestComplexSetting(t *testing.T) {
withTestProcess("testvariables", t, func(p *proc.Process, fixture protest.Fixture) {
err := p.Continue()
assertNoError(err, t, "Continue() returned an error")
h := func(setExpr, value string) {
assertNoError(setVariable(p, "c128", setExpr), t, "SetVariable()")
variable, err := evalVariable(p, "c128")
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)
}
}
h("3.2i", "(0 + 3.2i)")
h("1.1", "(1.1 + 0i)")
h("1 + 3.3i", "(1 + 3.3i)")
h("complex128(1.2, 3.4)", "(1.2 + 3.4i)")
})
}

@ -629,7 +629,8 @@ func printVar(t *Term, scope api.EvalScope, args ...string) error {
if err != nil {
return err
}
fmt.Println(val.Value)
fmt.Println(val.MultilineString(""))
return nil
}
@ -650,7 +651,7 @@ func filterVariables(vars []api.Variable, filter string) []string {
data := make([]string, 0, len(vars))
for _, v := range vars {
if reg == nil || reg.Match([]byte(v.Name)) {
data = append(data, fmt.Sprintf("%s = %s", v.Name, v.Value))
data = append(data, fmt.Sprintf("%s = %s", v.Name, v.SinglelineString()))
}
}
return data
@ -801,10 +802,10 @@ func printStack(stack []api.Stackframe, ind string) {
fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
for j := range stack[i].Arguments {
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].Value)
fmt.Printf("%s %s = %s\n", s, stack[i].Arguments[j].Name, stack[i].Arguments[j].SinglelineString())
}
for j := range stack[i].Locals {
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].Value)
fmt.Printf("%s %s = %s\n", s, stack[i].Locals[j].Name, stack[i].Locals[j].SinglelineString())
}
}
}
@ -829,7 +830,7 @@ func printcontext(t *Term, state *api.DebuggerState) error {
if state.Breakpoint.Tracepoint {
var arg []string
for _, ar := range state.CurrentThread.Function.Args {
arg = append(arg, ar.Value)
arg = append(arg, ar.SinglelineString())
}
args = strings.Join(arg, ", ")
}
@ -864,7 +865,7 @@ func printcontext(t *Term, state *api.DebuggerState) error {
ss := make([]string, len(bpi.Variables))
for i, v := range bpi.Variables {
ss[i] = fmt.Sprintf("%s: <%v>", v.Name, v.Value)
ss[i] = fmt.Sprintf("%s: %v", v.Name, v.MultilineString(""))
}
fmt.Printf("\t%s\n", strings.Join(ss, ", "))