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:
parent
91939bc9e7
commit
50b5fc92e2
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
type FooBar struct {
|
type FooBar struct {
|
||||||
Baz int
|
Baz int
|
||||||
@ -19,6 +20,7 @@ type Nest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func barfoo() {
|
func barfoo() {
|
||||||
|
runtime.Breakpoint()
|
||||||
a1 := "bur"
|
a1 := "bur"
|
||||||
fmt.Println(a1)
|
fmt.Println(a1)
|
||||||
}
|
}
|
||||||
@ -56,6 +58,7 @@ func foobar(baz string, bar FooBar) {
|
|||||||
ba = make([]int, 200, 200) // Test array size capping
|
ba = make([]int, 200, 200) // Test array size capping
|
||||||
)
|
)
|
||||||
|
|
||||||
|
runtime.Breakpoint()
|
||||||
barfoo()
|
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)
|
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
|
i2 := 2
|
||||||
p1 := &i1
|
p1 := &i1
|
||||||
runtime.Breakpoint()
|
runtime.Breakpoint()
|
||||||
fmt.Printf("%d %d %v\n", i1, i2, p1)
|
fmt.Println(i1, i2, p1)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type A struct {
|
type A struct {
|
||||||
val int
|
val int
|
||||||
}
|
}
|
||||||
|
|
||||||
type C struct {
|
type C struct {
|
||||||
@ -14,20 +14,20 @@ type C struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type B struct {
|
type B struct {
|
||||||
A
|
A
|
||||||
*C
|
*C
|
||||||
a A
|
a A
|
||||||
ptr *A
|
ptr *A
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}}
|
b := B{A: A{-314}, C: &C{"hello"}, a: A{42}, ptr: &A{1337}}
|
||||||
runtime.Breakpoint()
|
runtime.Breakpoint()
|
||||||
fmt.Println(b)
|
fmt.Println(b)
|
||||||
fmt.Println(b.val)
|
fmt.Println(b.val)
|
||||||
fmt.Println(b.A.val)
|
fmt.Println(b.A.val)
|
||||||
fmt.Println(b.a.val)
|
fmt.Println(b.a.val)
|
||||||
fmt.Println(b.ptr.val)
|
fmt.Println(b.ptr.val)
|
||||||
fmt.Println(b.C.s)
|
fmt.Println(b.C.s)
|
||||||
fmt.Println(b.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 {
|
if state.BreakpointInfo != nil {
|
||||||
for _, arg := range state.BreakpointInfo.Arguments {
|
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)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ver, ok := parseVersionString(vv.Value)
|
ver, ok := parseVersionString(vv.Value.(string))
|
||||||
if !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -332,7 +334,7 @@ func TestNextConcurrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
v, err := evalVariable(p, "n")
|
v, err := evalVariable(p, "n")
|
||||||
assertNoError(err, t, "EvalVariable")
|
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")
|
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()"))
|
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/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@ -29,16 +30,24 @@ const (
|
|||||||
type Variable struct {
|
type Variable struct {
|
||||||
Addr uintptr
|
Addr uintptr
|
||||||
Name string
|
Name string
|
||||||
Value string
|
DwarfType dwarf.Type
|
||||||
Type string
|
RealType dwarf.Type
|
||||||
dwarfType dwarf.Type
|
Kind reflect.Kind
|
||||||
thread *Thread
|
thread *Thread
|
||||||
|
|
||||||
Len int64
|
Value interface{}
|
||||||
Cap int64
|
|
||||||
|
Len int64
|
||||||
|
Cap int64
|
||||||
|
|
||||||
base uintptr
|
base uintptr
|
||||||
stride int64
|
stride int64
|
||||||
fieldType dwarf.Type
|
fieldType dwarf.Type
|
||||||
|
|
||||||
|
Children []Variable
|
||||||
|
|
||||||
|
loaded bool
|
||||||
|
Unreadable error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a runtime M (OS thread) structure.
|
// Represents a runtime M (OS thread) structure.
|
||||||
@ -91,24 +100,40 @@ type EvalScope struct {
|
|||||||
CFA int64
|
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{
|
v := &Variable{
|
||||||
Name: name,
|
Name: name,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
dwarfType: dwarfType,
|
DwarfType: dwarfType,
|
||||||
thread: thread,
|
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:
|
case *dwarf.StructType:
|
||||||
if strings.HasPrefix(t.StructName, "[]") {
|
switch {
|
||||||
err := v.loadSliceInfo(t)
|
case t.StructName == "string":
|
||||||
if err != nil {
|
v.Kind = reflect.String
|
||||||
return nil, err
|
case strings.HasPrefix(t.StructName, "[]"):
|
||||||
|
v.Kind = reflect.Slice
|
||||||
|
if v.Addr != 0 {
|
||||||
|
v.loadSliceInfo(t)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
v.Kind = reflect.Struct
|
||||||
}
|
}
|
||||||
case *dwarf.ArrayType:
|
case *dwarf.ArrayType:
|
||||||
|
v.Kind = reflect.Array
|
||||||
v.base = v.Addr
|
v.base = v.Addr
|
||||||
v.Len = t.Count
|
v.Len = t.Count
|
||||||
v.Cap = -1
|
v.Cap = -1
|
||||||
@ -118,12 +143,48 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, thread *Thread
|
|||||||
if t.Count > 0 {
|
if t.Count > 0 {
|
||||||
v.stride = t.ByteSize / t.Count
|
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) {
|
func (v *Variable) toField(field *dwarf.StructField) (*Variable, error) {
|
||||||
|
if v.Unreadable != nil {
|
||||||
|
return v.clone(), nil
|
||||||
|
}
|
||||||
if v.Addr == 0 {
|
if v.Addr == 0 {
|
||||||
return nil, fmt.Errorf("%s is nil", v.Name)
|
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)
|
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 {
|
func (scope *EvalScope) DwarfReader() *reader.Reader {
|
||||||
@ -271,7 +332,7 @@ func parseG(thread *Thread, gaddr uint64, deref bool) (*G, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
waitreason, err := thread.readString(uintptr(waitReasonAddr))
|
waitreason, _, err := thread.readString(uintptr(waitReasonAddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -355,11 +416,14 @@ func (scope *EvalScope) ExtractVariableInfo(name string) (*Variable, error) {
|
|||||||
return nil, origErr
|
return nil, origErr
|
||||||
}
|
}
|
||||||
v.Name = name
|
v.Name = name
|
||||||
|
return v, nil
|
||||||
} else {
|
} else {
|
||||||
for _, memberName := range memberNames {
|
if len(memberNames) > 0 {
|
||||||
v, err = v.structMember(memberName)
|
for i := range memberNames {
|
||||||
if err != nil {
|
v, err = v.structMember(memberNames[i])
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,8 +436,8 @@ func (scope *EvalScope) EvalVariable(name string) (*Variable, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = v.loadValue(true)
|
v.loadValue()
|
||||||
return v, err
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the value of the named variable
|
// Sets the value of the named variable
|
||||||
@ -382,6 +446,9 @@ func (scope *EvalScope) SetVariable(name, value string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if v.Unreadable != nil {
|
||||||
|
return fmt.Errorf("Variable \"%s\" is unreadable: %v\n", name, v.Unreadable)
|
||||||
|
}
|
||||||
return v.setValue(value)
|
return v.setValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,8 +458,8 @@ func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = v.loadValue(true)
|
v.loadValue()
|
||||||
return v, err
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
|
func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
|
||||||
@ -459,8 +526,8 @@ func (dbp *Process) EvalPackageVariable(name string) (*Variable, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = v.loadValue(true)
|
v.loadValue()
|
||||||
return v, err
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) {
|
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) {
|
func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||||
structVar, err := v.maybeDereference()
|
if v.Unreadable != nil {
|
||||||
structVar.Name = v.Name
|
return v.clone(), nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
structVar = structVar.resolveTypedefs()
|
structVar := v.maybeDereference()
|
||||||
switch t := structVar.dwarfType.(type) {
|
structVar.Name = v.Name
|
||||||
|
if structVar.Unreadable != nil {
|
||||||
|
return structVar, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := structVar.RealType.(type) {
|
||||||
case *dwarf.StructType:
|
case *dwarf.StructType:
|
||||||
for _, field := range t.Field {
|
for _, field := range t.Field {
|
||||||
if field.Name != memberName {
|
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)
|
return nil, fmt.Errorf("%s has no member %s", v.Name, memberName)
|
||||||
default:
|
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 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.
|
// If v is a pointer a new variable is returned containing the value pointed by v.
|
||||||
func (v *Variable) maybeDereference() (*Variable, error) {
|
func (v *Variable) maybeDereference() *Variable {
|
||||||
v = v.resolveTypedefs()
|
if v.Unreadable != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
switch t := v.dwarfType.(type) {
|
switch t := v.RealType.(type) {
|
||||||
case *dwarf.PtrType:
|
case *dwarf.PtrType:
|
||||||
ptrval, err := v.thread.readUintRaw(uintptr(v.Addr), int64(v.thread.dbp.arch.PtrSize()))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
r.Unreadable = err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newVariable("", uintptr(ptrval), t.Type, v.thread)
|
return r
|
||||||
default:
|
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.
|
// Extracts the value of the variable at the given address.
|
||||||
func (v *Variable) loadValue(printStructName bool) (err error) {
|
func (v *Variable) loadValue() {
|
||||||
v.Value, err = v.loadValueInternal(printStructName, 0)
|
v.loadValueInternal(0)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) loadValueInternal(printStructName bool, recurseLevel int) (string, error) {
|
func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||||
v = v.resolveTypedefs()
|
if v.Unreadable != nil || v.loaded || v.Addr == 0 {
|
||||||
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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 {
|
func (v *Variable) setValue(value string) error {
|
||||||
v = v.resolveTypedefs()
|
switch t := v.RealType.(type) {
|
||||||
|
|
||||||
switch t := v.dwarfType.(type) {
|
|
||||||
case *dwarf.PtrType:
|
case *dwarf.PtrType:
|
||||||
return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize()))
|
return v.writeUint(false, value, int64(v.thread.dbp.arch.PtrSize()))
|
||||||
case *dwarf.ComplexType:
|
case *dwarf.ComplexType:
|
||||||
@ -719,22 +739,22 @@ func (v *Variable) setValue(value string) error {
|
|||||||
case *dwarf.BoolType:
|
case *dwarf.BoolType:
|
||||||
return v.writeBool(value)
|
return v.writeBool(value)
|
||||||
default:
|
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
|
// string data structure is always two ptrs in size. Addr, followed by len
|
||||||
// http://research.swtch.com/godata
|
// http://research.swtch.com/godata
|
||||||
|
|
||||||
// read len
|
// read len
|
||||||
val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize())
|
val, err := thread.readMemory(addr+uintptr(thread.dbp.arch.PtrSize()), thread.dbp.arch.PtrSize())
|
||||||
if err != nil {
|
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 {
|
if strlen < 0 {
|
||||||
return "", fmt.Errorf("invalid length: %d", strlen)
|
return "", 0, fmt.Errorf("invalid length: %d", strlen)
|
||||||
}
|
}
|
||||||
|
|
||||||
count := strlen
|
count := strlen
|
||||||
@ -745,28 +765,24 @@ func (thread *Thread) readString(addr uintptr) (string, error) {
|
|||||||
// read addr
|
// read addr
|
||||||
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
val, err = thread.readMemory(addr, thread.dbp.arch.PtrSize())
|
||||||
if err != nil {
|
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))
|
addr = uintptr(binary.LittleEndian.Uint64(val))
|
||||||
if addr == 0 {
|
if addr == 0 {
|
||||||
return "", nil
|
return "", 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err = thread.readMemory(addr, count)
|
val, err = thread.readMemory(addr, int(count))
|
||||||
if err != nil {
|
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))
|
retstr := *(*string)(unsafe.Pointer(&val))
|
||||||
|
|
||||||
if count != strlen {
|
return retstr, strlen, nil
|
||||||
retstr = retstr + fmt.Sprintf("...+%d more", strlen-count)
|
|
||||||
}
|
|
||||||
|
|
||||||
return retstr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) loadSliceInfo(t *dwarf.StructType) error {
|
func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
|
||||||
var err error
|
var err error
|
||||||
for _, f := range t.Field {
|
for _, f := range t.Field {
|
||||||
switch f.Name {
|
switch f.Name {
|
||||||
@ -778,31 +794,30 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) error {
|
|||||||
// Dereference array type to get value type
|
// Dereference array type to get value type
|
||||||
ptrType, ok := f.Type.(*dwarf.PtrType)
|
ptrType, ok := f.Type.(*dwarf.PtrType)
|
||||||
if !ok {
|
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
|
v.fieldType = ptrType.Type
|
||||||
}
|
}
|
||||||
case "len":
|
case "len":
|
||||||
lstrAddr, err := v.toField(f)
|
lstrAddr, _ := v.toField(f)
|
||||||
|
lstrAddr.loadValue()
|
||||||
|
err = lstrAddr.Unreadable
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = lstrAddr.loadValue(true)
|
v.Len = lstrAddr.Value.(int64)
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
v.Len, err = strconv.ParseInt(lstrAddr.Value, 10, 64)
|
|
||||||
}
|
}
|
||||||
case "cap":
|
case "cap":
|
||||||
cstrAddr, err := v.toField(f)
|
cstrAddr, _ := v.toField(f)
|
||||||
|
cstrAddr.loadValue()
|
||||||
|
err = cstrAddr.Unreadable
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = cstrAddr.loadValue(true)
|
v.Cap = cstrAddr.Value.(int64)
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
v.Cap, err = strconv.ParseInt(cstrAddr.Value, 10, 64)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if err != nil {
|
||||||
|
v.Unreadable = err
|
||||||
if err != nil {
|
return
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v.stride = v.fieldType.Size()
|
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())
|
v.stride = int64(v.thread.dbp.arch.PtrSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) loadArrayValues(recurseLevel int) (string, error) {
|
func (v *Variable) loadArrayValues(recurseLevel int) {
|
||||||
vals := make([]string, 0)
|
if v.Unreadable != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
errcount := 0
|
errcount := 0
|
||||||
|
|
||||||
for i := int64(0); i < v.Len; i++ {
|
for i := int64(0); i < v.Len; i++ {
|
||||||
// Cap number of elements
|
// Cap number of elements
|
||||||
if i >= maxArrayValues {
|
if i >= maxArrayValues {
|
||||||
vals = append(vals, fmt.Sprintf("...+%d more", v.Len-maxArrayValues))
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
var val string
|
fieldvar := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
|
||||||
fieldvar, err := newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType, v.thread)
|
fieldvar.loadValueInternal(recurseLevel + 1)
|
||||||
if err == nil {
|
|
||||||
val, err = fieldvar.loadValueInternal(false, recurseLevel+1)
|
if fieldvar.Unreadable != nil {
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
errcount++
|
errcount++
|
||||||
val = fmt.Sprintf("<unreadable: %s>", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vals = append(vals, val)
|
v.Children = append(v.Children, *fieldvar)
|
||||||
if errcount > maxErrCount {
|
if errcount > maxErrCount {
|
||||||
vals = append(vals, fmt.Sprintf("...+%d more", v.Len-i))
|
|
||||||
break
|
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
|
var fs int64
|
||||||
switch size {
|
switch size {
|
||||||
case 8:
|
case 8:
|
||||||
@ -856,19 +863,18 @@ func (v *Variable) readComplex(size int64) (string, error) {
|
|||||||
case 16:
|
case 16:
|
||||||
fs = 8
|
fs = 8
|
||||||
default:
|
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 {
|
ftyp := &dwarf.FloatType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: fs, Name: fmt.Sprintf("float%d", fs)}, BitSize: fs * 8, BitOffset: 0}}
|
||||||
return "", err
|
|
||||||
}
|
realvar := newVariable("real", v.Addr, ftyp, v.thread)
|
||||||
imagvar := *v
|
imagvar := newVariable("imaginary", v.Addr+uintptr(fs), ftyp, v.thread)
|
||||||
imagvar.Addr += uintptr(fs)
|
realvar.loadValue()
|
||||||
i, err := imagvar.readFloat(fs)
|
imagvar.loadValue()
|
||||||
if err != nil {
|
v.Len = 2
|
||||||
return "", err
|
v.Children = []Variable{*realvar, *imagvar}
|
||||||
}
|
|
||||||
return fmt.Sprintf("(%s + %si)", r, i), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) writeComplex(value string, size int64) error {
|
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))
|
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) {
|
func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) {
|
||||||
var n int64
|
var n int64
|
||||||
|
|
||||||
@ -976,14 +974,6 @@ func (thread *Thread) readIntRaw(addr uintptr, size int64) (int64, error) {
|
|||||||
return n, nil
|
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 {
|
func (v *Variable) writeUint(signed bool, value string, size int64) error {
|
||||||
var (
|
var (
|
||||||
n uint64
|
n uint64
|
||||||
@ -1039,10 +1029,10 @@ func (thread *Thread) readUintRaw(addr uintptr, size int64) (uint64, error) {
|
|||||||
return n, nil
|
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))
|
val, err := v.thread.readMemory(v.Addr, int(size))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return 0.0, err
|
||||||
}
|
}
|
||||||
buf := bytes.NewBuffer(val)
|
buf := bytes.NewBuffer(val)
|
||||||
|
|
||||||
@ -1050,14 +1040,14 @@ func (v *Variable) readFloat(size int64) (string, error) {
|
|||||||
case 4:
|
case 4:
|
||||||
n := float32(0)
|
n := float32(0)
|
||||||
binary.Read(buf, binary.LittleEndian, &n)
|
binary.Read(buf, binary.LittleEndian, &n)
|
||||||
return strconv.FormatFloat(float64(n), 'f', -1, int(size)*8), nil
|
return float64(n), nil
|
||||||
case 8:
|
case 8:
|
||||||
n := float64(0)
|
n := float64(0)
|
||||||
binary.Read(buf, binary.LittleEndian, &n)
|
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 {
|
func (v *Variable) writeFloat(value string, size int64) error {
|
||||||
@ -1084,19 +1074,6 @@ func (v *Variable) writeFloatRaw(f float64, size int64) error {
|
|||||||
return err
|
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 {
|
func (v *Variable) writeBool(value string) error {
|
||||||
b, err := strconv.ParseBool(value)
|
b, err := strconv.ParseBool(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1110,30 +1087,34 @@ func (v *Variable) writeBool(value string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) readFunctionPtr() (string, error) {
|
func (v *Variable) readFunctionPtr() {
|
||||||
val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize())
|
val, err := v.thread.readMemory(v.Addr, v.thread.dbp.arch.PtrSize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
v.Unreadable = err
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// dereference pointer to find function pc
|
// dereference pointer to find function pc
|
||||||
fnaddr := uintptr(binary.LittleEndian.Uint64(val))
|
fnaddr := uintptr(binary.LittleEndian.Uint64(val))
|
||||||
if fnaddr == 0 {
|
if fnaddr == 0 {
|
||||||
return "nil", nil
|
v.Unreadable = err
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err = v.thread.readMemory(fnaddr, v.thread.dbp.arch.PtrSize())
|
val, err = v.thread.readMemory(fnaddr, v.thread.dbp.arch.PtrSize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
v.Unreadable = err
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
funcAddr := binary.LittleEndian.Uint64(val)
|
funcAddr := binary.LittleEndian.Uint64(val)
|
||||||
fn := v.thread.dbp.goSymTable.PCToFunc(uint64(funcAddr))
|
fn := v.thread.dbp.goSymTable.PCToFunc(uint64(funcAddr))
|
||||||
if fn == nil {
|
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
|
// 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
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"debug/dwarf"
|
||||||
"debug/gosym"
|
"debug/gosym"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/derekparker/delve/proc"
|
"github.com/derekparker/delve/proc"
|
||||||
@ -57,12 +59,43 @@ func ConvertThread(th *proc.Thread) *Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// convertVar converts an internal variable to an API Variable.
|
// convertVar converts an internal variable to an API Variable.
|
||||||
func ConvertVar(v *proc.Variable) Variable {
|
func ConvertVar(v *proc.Variable) *Variable {
|
||||||
return Variable{
|
r := Variable{
|
||||||
Name: v.Name,
|
Addr: v.Addr,
|
||||||
Value: v.Value,
|
Name: v.Name,
|
||||||
Type: v.Type,
|
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 {
|
func ConvertFunction(fn *gosym.Func) *Function {
|
||||||
|
262
service/api/prettyprint.go
Normal file
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
|
package api
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
// DebuggerState represents the current context of the debugger.
|
// DebuggerState represents the current context of the debugger.
|
||||||
type DebuggerState struct {
|
type DebuggerState struct {
|
||||||
// Breakpoint is the current breakpoint at which the debugged process is
|
// Breakpoint is the current breakpoint at which the debugged process is
|
||||||
@ -104,9 +106,35 @@ type Function struct {
|
|||||||
|
|
||||||
// Variable describes a variable.
|
// Variable describes a variable.
|
||||||
type Variable struct {
|
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"`
|
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
|
// 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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err == nil {
|
||||||
bpi.Arguments = vars
|
bpi.Arguments = convertVars(args)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -376,7 +376,7 @@ func (d *Debugger) PackageVariables(threadID int, filter string) ([]api.Variable
|
|||||||
}
|
}
|
||||||
for _, v := range pv {
|
for _, v := range pv {
|
||||||
if regex.Match([]byte(v.Name)) {
|
if regex.Match([]byte(v.Name)) {
|
||||||
vars = append(vars, api.ConvertVar(v))
|
vars = append(vars, *api.ConvertVar(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vars, err
|
return vars, err
|
||||||
@ -397,7 +397,7 @@ func (d *Debugger) Registers(threadID int) (string, error) {
|
|||||||
func convertVars(pv []*proc.Variable) []api.Variable {
|
func convertVars(pv []*proc.Variable) []api.Variable {
|
||||||
vars := make([]api.Variable, 0, len(pv))
|
vars := make([]api.Variable, 0, len(pv))
|
||||||
for _, v := range pv {
|
for _, v := range pv {
|
||||||
vars = append(vars, api.ConvertVar(v))
|
vars = append(vars, *api.ConvertVar(v))
|
||||||
}
|
}
|
||||||
return vars
|
return vars
|
||||||
}
|
}
|
||||||
@ -419,19 +419,11 @@ func (d *Debugger) FunctionArguments(scope api.EvalScope) ([]api.Variable, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return functionArguments(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func functionArguments(s *proc.EvalScope) ([]api.Variable, error) {
|
|
||||||
pv, err := s.FunctionArguments()
|
pv, err := s.FunctionArguments()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
vars := make([]api.Variable, 0, len(pv))
|
return convertVars(pv), nil
|
||||||
for _, v := range pv {
|
|
||||||
vars = append(vars, api.ConvertVar(v))
|
|
||||||
}
|
|
||||||
return vars, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) EvalVariableInScope(scope api.EvalScope, symbol string) (*api.Variable, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
converted := api.ConvertVar(v)
|
return api.ConvertVar(v), err
|
||||||
return &converted, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) error {
|
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 {
|
for i := range rawlocs {
|
||||||
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
|
frame := api.Stackframe{Location: api.ConvertLocation(rawlocs[i].Call)}
|
||||||
if full {
|
if full {
|
||||||
|
var err error
|
||||||
scope := rawlocs[i].Scope(d.process.CurrentThread)
|
scope := rawlocs[i].Scope(d.process.CurrentThread)
|
||||||
lv, err := scope.LocalVariables()
|
locals, err := scope.LocalVariables()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
av, err := scope.FunctionArguments()
|
arguments, err := scope.FunctionArguments()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
frame.Locals = convertVars(lv)
|
|
||||||
frame.Arguments = convertVars(av)
|
frame.Locals = convertVars(locals)
|
||||||
|
frame.Arguments = convertVars(arguments)
|
||||||
}
|
}
|
||||||
locations = append(locations, frame)
|
locations = append(locations, frame)
|
||||||
}
|
}
|
||||||
|
@ -456,8 +456,12 @@ func TestClientServer_traceContinue(t *testing.T) {
|
|||||||
t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name)
|
t.Fatalf("Wrong variable returned %s", bpi.Variables[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bpi.Variables[0].Value != strconv.Itoa(count-1) {
|
t.Logf("Variable i is %v", bpi.Variables[0])
|
||||||
t.Fatalf("Wrong variable value %s (%d)", bpi.Variables[0].Value, count)
|
|
||||||
|
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 {
|
if state.Exited {
|
||||||
@ -604,10 +608,6 @@ func TestClientServer_FindLocations(t *testing.T) {
|
|||||||
|
|
||||||
func TestClientServer_EvalVariable(t *testing.T) {
|
func TestClientServer_EvalVariable(t *testing.T) {
|
||||||
withTestClient("testvariables", t, func(c service.Client) {
|
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()
|
state := <-c.Continue()
|
||||||
|
|
||||||
if state.Err != nil {
|
if state.Err != nil {
|
||||||
@ -617,20 +617,16 @@ func TestClientServer_EvalVariable(t *testing.T) {
|
|||||||
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
|
var1, err := c.EvalVariable(api.EvalScope{-1, 0}, "a1")
|
||||||
assertNoError(err, t, "EvalVariable")
|
assertNoError(err, t, "EvalVariable")
|
||||||
|
|
||||||
t.Logf("var1: <%s>", var1.Value)
|
t.Logf("var1: %s", var1.SinglelineString())
|
||||||
|
|
||||||
if var1.Value != "foofoofoofoofoofoo" {
|
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) {
|
func TestClientServer_SetVariable(t *testing.T) {
|
||||||
withTestClient("testvariables", t, func(c service.Client) {
|
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()
|
state := <-c.Continue()
|
||||||
|
|
||||||
if state.Err != nil {
|
if state.Err != nil {
|
||||||
@ -641,10 +637,12 @@ func TestClientServer_SetVariable(t *testing.T) {
|
|||||||
|
|
||||||
a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2")
|
a2, err := c.EvalVariable(api.EvalScope{-1, 0}, "a2")
|
||||||
|
|
||||||
t.Logf("a2: <%s>", a2.Value)
|
t.Logf("a2: %v", a2)
|
||||||
|
|
||||||
if a2.Value != "8" {
|
n, err := strconv.Atoi(a2.Value)
|
||||||
t.Fatalf("Wrong variable value: %v", 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" {
|
if arg.Name != "i" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
n, err := strconv.Atoi(arg.Value)
|
t.Logf("frame %d, variable i is %v\n", arg)
|
||||||
assertNoError(err, t, fmt.Sprintf("Wrong value for i in goroutine %d (%s)", g.ID, arg.Value))
|
argn, err := strconv.Atoi(arg.Value)
|
||||||
found[n] = true
|
if err == nil {
|
||||||
|
found[argn] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -707,10 +707,9 @@ func TestClientServer_FullStacktrace(t *testing.T) {
|
|||||||
if v == nil {
|
if v == nil {
|
||||||
t.Fatalf("Could not find value of variable n in frame %d", i)
|
t.Fatalf("Could not find value of variable n in frame %d", i)
|
||||||
}
|
}
|
||||||
n, err := strconv.Atoi(v.Value)
|
vn, err := strconv.Atoi(v.Value)
|
||||||
assertNoError(err, t, fmt.Sprintf("Wrong value for n: %s", v.Value))
|
if err != nil || vn != cur {
|
||||||
if n != cur {
|
t.Fatalf("Expected value %d got %d (error: %v)", cur, vn, err)
|
||||||
t.Fatalf("Expected value %d got %d", cur, n)
|
|
||||||
}
|
}
|
||||||
cur--
|
cur--
|
||||||
if cur < 0 {
|
if cur < 0 {
|
||||||
|
346
service/test/variables_test.go
Normal file
346
service/test/variables_test.go
Normal file
@ -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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(val.Value)
|
|
||||||
|
fmt.Println(val.MultilineString(""))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,7 +651,7 @@ func filterVariables(vars []api.Variable, filter string) []string {
|
|||||||
data := make([]string, 0, len(vars))
|
data := make([]string, 0, len(vars))
|
||||||
for _, v := range vars {
|
for _, v := range vars {
|
||||||
if reg == nil || reg.Match([]byte(v.Name)) {
|
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
|
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)
|
fmt.Printf("%sat %s:%d\n", s, shortenFilePath(stack[i].File), stack[i].Line)
|
||||||
|
|
||||||
for j := range stack[i].Arguments {
|
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 {
|
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 {
|
if state.Breakpoint.Tracepoint {
|
||||||
var arg []string
|
var arg []string
|
||||||
for _, ar := range state.CurrentThread.Function.Args {
|
for _, ar := range state.CurrentThread.Function.Args {
|
||||||
arg = append(arg, ar.Value)
|
arg = append(arg, ar.SinglelineString())
|
||||||
}
|
}
|
||||||
args = strings.Join(arg, ", ")
|
args = strings.Join(arg, ", ")
|
||||||
}
|
}
|
||||||
@ -864,7 +865,7 @@ func printcontext(t *Term, state *api.DebuggerState) error {
|
|||||||
|
|
||||||
ss := make([]string, len(bpi.Variables))
|
ss := make([]string, len(bpi.Variables))
|
||||||
for i, v := range 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, ", "))
|
fmt.Printf("\t%s\n", strings.Join(ss, ", "))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user