parent
1128c26b87
commit
2ad9ce6fe3
@ -3,3 +3,4 @@
|
||||
- When a function defines two (or more) variables with the same name delve is unable to distinguish between them: `locals` will print both variables, `print` will randomly pick one. See [Issue #106](https://github.com/derekparker/delve/issues/106).
|
||||
- Delve does not currently support 32bit systems. This will usually manifest as a compiler error in `proc/disasm.go`. See [Issue #20](https://github.com/derekparker/delve/issues/20).
|
||||
- When Delve is compiled with versions of go prior to 1.7.0 it is not possible to set a breakpoint on a function in a remote package using the `Receiver.MethodName` syntax. See [Issue #528](https://github.com/derekparker/delve/issues/528).
|
||||
- When running Delve on binaries compiled with a version of go prior to 1.9.0 `locals` will print all local variables, including ones that are out of scope. If there are multiple variables defined with the same name in the current function `print` will not be able to select the correct one for the current line.
|
||||
|
17
_fixtures/scopeescapevareval.go
Normal file
17
_fixtures/scopeescapevareval.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := 2
|
||||
{
|
||||
a := 3
|
||||
p := &a
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(a, p)
|
||||
}
|
||||
fmt.Println(a)
|
||||
}
|
165
_fixtures/scopetest.go
Normal file
165
_fixtures/scopetest.go
Normal file
@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
func f1(x int) {}
|
||||
func f2(x int) {}
|
||||
func f3(x int) {}
|
||||
func f4(x int) {}
|
||||
func f5(x int) {}
|
||||
func f6(x int) {}
|
||||
func gret1(x int) int {
|
||||
return x - 1
|
||||
}
|
||||
|
||||
var boolvar = true
|
||||
|
||||
func gretbool() bool {
|
||||
x := boolvar
|
||||
boolvar = !boolvar
|
||||
return x
|
||||
}
|
||||
func gret3() (int, int, int) { return 0, 1, 2 }
|
||||
|
||||
var v = []int{0, 1, 2}
|
||||
var ch = make(chan int, 1)
|
||||
var floatch = make(chan float64, 1)
|
||||
var iface interface{} = 13
|
||||
|
||||
func TestNestedFor() {
|
||||
a := 0
|
||||
f1(a) // a int = 0
|
||||
for i := 0; i < 5; i++ {
|
||||
f2(i) // i int
|
||||
for i := 1; i < 5; i++ {
|
||||
f3(i) // a int = 0, i int = 0, i int = 1
|
||||
i++
|
||||
f3(i)
|
||||
}
|
||||
f4(i) // a int = 0, i int = 0
|
||||
}
|
||||
f5(a)
|
||||
}
|
||||
func TestOas2() {
|
||||
if a, b, c := gret3(); a != 1 {
|
||||
f1(a) // a int = 0, b int = 1, c int = 2
|
||||
f1(b) // a int = 0, b int = 1, c int = 2
|
||||
f1(c) // a int = 0, b int = 1, c int = 2
|
||||
}
|
||||
for i, x := range v {
|
||||
f1(i) // i int = 0, x int = 0
|
||||
f1(x) // i int = 0, x int = 0
|
||||
}
|
||||
if a, ok := <-ch; ok {
|
||||
f1(a) // a int = 12, ok bool = true
|
||||
}
|
||||
if a, ok := iface.(int); ok {
|
||||
f1(a) // a int = 13, ok bool = true
|
||||
}
|
||||
}
|
||||
func TestIfElse(x int) {
|
||||
if x := gret1(x); x != 0 {
|
||||
a := 0
|
||||
f1(a) // x int = 2, x int = 1, a int = 0
|
||||
f1(x)
|
||||
} else {
|
||||
b := 1
|
||||
f1(b) // x int = 1, x int = 0, b int = 1
|
||||
f1(x + 1)
|
||||
}
|
||||
}
|
||||
func TestSwitch(in int) {
|
||||
switch x := gret1(in); x {
|
||||
case 0:
|
||||
i := x + 5
|
||||
f1(x) // in int = 1, x int = 0, i int = 5
|
||||
f1(i)
|
||||
case 1:
|
||||
j := x + 10
|
||||
f1(x)
|
||||
f1(j) // in int = 2, x int = 1, j int = 11
|
||||
case 2:
|
||||
k := x + 2
|
||||
f1(x)
|
||||
f1(k) // in int = 3, x int = 2, k int = 4
|
||||
}
|
||||
}
|
||||
func TestTypeSwitch(iface interface{}) {
|
||||
switch x := iface.(type) {
|
||||
case int:
|
||||
f1(x) // iface interface{}, x int = 1
|
||||
case uint8:
|
||||
f1(int(x)) // iface interface{}, x uint8 = 2
|
||||
case float64:
|
||||
f1(int(x) + 1) // iface interface{}, x float64 = 3.0
|
||||
}
|
||||
}
|
||||
func TestSelectScope() {
|
||||
select {
|
||||
case i := <-ch:
|
||||
f1(i) // i int = 13
|
||||
case f := <-floatch:
|
||||
f1(int(f)) // f float64 = 14.0
|
||||
}
|
||||
}
|
||||
func TestBlock() {
|
||||
a := 1
|
||||
f1(a) // a int = 1
|
||||
{
|
||||
b := 2
|
||||
a := 3
|
||||
f1(b) // a int = 1, a int = 3, b int = 2
|
||||
f1(a) // a int = 1, a int = 3, b int = 2
|
||||
}
|
||||
}
|
||||
func TestDiscontiguousRanges() {
|
||||
a := 0
|
||||
f1(a) // a int = 0
|
||||
{
|
||||
b := 0
|
||||
f2(b) // a int = 0, b int = 0
|
||||
if gretbool() {
|
||||
c := 0
|
||||
f3(c) // a int = 0, b int = 0, c int = 0
|
||||
} else {
|
||||
c := 1.1
|
||||
f4(int(c)) // a int = 0, b int = 0, c float64 = 1.1
|
||||
}
|
||||
f5(b) // a int = 0, b int = 0
|
||||
}
|
||||
f6(a) // a int = 0
|
||||
}
|
||||
func TestClosureScope() {
|
||||
a := 1
|
||||
b := 1
|
||||
f := func(c int) {
|
||||
d := 3
|
||||
f1(a) // a int = 1, c int = 3, d int = 3
|
||||
f1(c)
|
||||
f1(d)
|
||||
if e := 3; e != 0 {
|
||||
f1(e) // a int = 1, c int = 3, d int = 3, e int = 3
|
||||
}
|
||||
}
|
||||
f(3)
|
||||
f1(b)
|
||||
}
|
||||
func main() {
|
||||
ch <- 12
|
||||
TestNestedFor()
|
||||
TestOas2()
|
||||
TestIfElse(2)
|
||||
TestIfElse(1)
|
||||
TestSwitch(3)
|
||||
TestSwitch(2)
|
||||
TestSwitch(1)
|
||||
TestTypeSwitch(1)
|
||||
TestTypeSwitch(uint8(2))
|
||||
TestTypeSwitch(float64(3.0))
|
||||
ch <- 13
|
||||
TestSelectScope()
|
||||
floatch <- 14.0
|
||||
TestSelectScope()
|
||||
TestBlock()
|
||||
TestDiscontiguousRanges()
|
||||
TestDiscontiguousRanges()
|
||||
TestClosureScope()
|
||||
}
|
16
_fixtures/testshadow.go
Normal file
16
_fixtures/testshadow.go
Normal file
@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := 0
|
||||
{
|
||||
a := 1
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(a)
|
||||
}
|
||||
fmt.Println(a)
|
||||
}
|
@ -257,30 +257,6 @@ func (reader *Reader) InstructionsForEntry(entry *dwarf.Entry) ([]byte, error) {
|
||||
return append([]byte{}, instructions...), nil
|
||||
}
|
||||
|
||||
// NextScopeVariable moves the reader to the next debug entry that describes a local variable and returns the entry.
|
||||
func (reader *Reader) NextScopeVariable() (*dwarf.Entry, error) {
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// All scope variables will be at the same depth
|
||||
reader.SkipChildren()
|
||||
|
||||
// End of the current depth
|
||||
if entry.Tag == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if entry.Tag == dwarf.TagVariable || entry.Tag == dwarf.TagFormalParameter {
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
|
||||
// No more items
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NextMememberVariable moves the reader to the next debug entry that describes a member variable and returns the entry.
|
||||
func (reader *Reader) NextMemberVariable() (*dwarf.Entry, error) {
|
||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
||||
|
103
pkg/dwarf/reader/variables.go
Normal file
103
pkg/dwarf/reader/variables.go
Normal file
@ -0,0 +1,103 @@
|
||||
package reader
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"debug/dwarf"
|
||||
)
|
||||
|
||||
// VariableReader provides a way of reading the local variables and formal
|
||||
// parameters of a function that are visible at the specified PC address.
|
||||
type VariableReader struct {
|
||||
dwarf *dwarf.Data
|
||||
reader *dwarf.Reader
|
||||
entry *dwarf.Entry
|
||||
depth int
|
||||
onlyVisible bool
|
||||
pc uint64
|
||||
err error
|
||||
}
|
||||
|
||||
// Variables returns a VariableReader for the function or lexical block at off.
|
||||
// If onlyVisible is true only variables visible at pc will be returned by
|
||||
// the VariableReader.
|
||||
func Variables(dwarf *dwarf.Data, off dwarf.Offset, pc uint64, onlyVisible bool) *VariableReader {
|
||||
reader := dwarf.Reader()
|
||||
reader.Seek(off)
|
||||
return &VariableReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, onlyVisible: onlyVisible, pc: pc, err: nil}
|
||||
}
|
||||
|
||||
// Next reads the next variable entry, returns false if there aren't any.
|
||||
func (vrdr *VariableReader) Next() bool {
|
||||
if vrdr.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for {
|
||||
vrdr.entry, vrdr.err = vrdr.reader.Next()
|
||||
if vrdr.entry == nil || vrdr.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch vrdr.entry.Tag {
|
||||
case 0:
|
||||
vrdr.depth--
|
||||
if vrdr.depth == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram:
|
||||
recur := true
|
||||
if vrdr.onlyVisible {
|
||||
recur, vrdr.err = vrdr.entryRangesContains()
|
||||
if vrdr.err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if recur {
|
||||
vrdr.depth++
|
||||
} else {
|
||||
if vrdr.depth == 0 {
|
||||
return false
|
||||
}
|
||||
vrdr.reader.SkipChildren()
|
||||
}
|
||||
|
||||
default:
|
||||
if vrdr.depth == 0 {
|
||||
vrdr.err = errors.New("offset was not lexical block or subprogram")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (vrdr *VariableReader) entryRangesContains() (bool, error) {
|
||||
rngs, err := vrdr.dwarf.Ranges(vrdr.entry)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, rng := range rngs {
|
||||
if vrdr.pc >= rng[0] && vrdr.pc < rng[1] {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Entry returns the current variable entry.
|
||||
func (vrdr *VariableReader) Entry() *dwarf.Entry {
|
||||
return vrdr.entry
|
||||
}
|
||||
|
||||
// Depth returns the depth of the current scope
|
||||
func (vrdr *VariableReader) Depth() int {
|
||||
return vrdr.depth
|
||||
}
|
||||
|
||||
// Err returns the error if there was one.
|
||||
func (vrdr *VariableReader) Err() error {
|
||||
return vrdr.err
|
||||
}
|
@ -2,6 +2,7 @@ package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/dwarf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -445,28 +446,34 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
|
||||
return nilVariable, nil
|
||||
}
|
||||
|
||||
// try to interpret this as a local variable
|
||||
v, err := scope.extractVarInfo(node.Name)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
vars, err := scope.variablesByTag(dwarf.TagVariable, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
origErr := err
|
||||
// workaround: sometimes go inserts an entry for '&varname' instead of varname
|
||||
v, err = scope.extractVarInfo("&" + node.Name)
|
||||
if err == nil {
|
||||
v = v.maybeDereference()
|
||||
v.Name = node.Name
|
||||
return v, nil
|
||||
for i := range vars {
|
||||
if vars[i].Name == node.Name && vars[i].Flags&VariableShadowed == 0 {
|
||||
return vars[i], nil
|
||||
}
|
||||
}
|
||||
args, err := scope.variablesByTag(dwarf.TagFormalParameter, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range args {
|
||||
if args[i].Name == node.Name {
|
||||
return args[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not a local variable then it could be a package variable w/o explicit package name
|
||||
_, _, fn := scope.BinInfo.PCToLine(scope.PC)
|
||||
if fn != nil {
|
||||
if v, err = scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil {
|
||||
if v, err := scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil {
|
||||
v.Name = node.Name
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, origErr
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", node.Name)
|
||||
}
|
||||
|
||||
// Evaluates expressions <subexpr>.<field name> where subexpr is not a package name
|
||||
|
@ -3032,3 +3032,43 @@ func TestIssue871(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestShadowedFlag(t *testing.T) {
|
||||
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
|
||||
return
|
||||
}
|
||||
withTestProcess("testshadow", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
assertNoError(proc.Continue(p), t, "Continue")
|
||||
scope, err := proc.GoroutineScope(p.CurrentThread())
|
||||
assertNoError(err, t, "GoroutineScope")
|
||||
locals, err := scope.LocalVariables(normalLoadConfig)
|
||||
assertNoError(err, t, "LocalVariables")
|
||||
foundShadowed := false
|
||||
foundNonShadowed := false
|
||||
for _, v := range locals {
|
||||
if v.Flags&proc.VariableShadowed != 0 {
|
||||
if v.Name != "a" {
|
||||
t.Errorf("wrong shadowed variable %s", v.Name)
|
||||
}
|
||||
foundShadowed = true
|
||||
if n, _ := constant.Int64Val(v.Value); n != 0 {
|
||||
t.Errorf("wrong value for shadowed variable a: %d", n)
|
||||
}
|
||||
} else {
|
||||
if v.Name != "a" {
|
||||
t.Errorf("wrong non-shadowed variable %s", v.Name)
|
||||
}
|
||||
foundNonShadowed = true
|
||||
if n, _ := constant.Int64Val(v.Value); n != 1 {
|
||||
t.Errorf("wrong value for non-shadowed variable a: %d", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundShadowed {
|
||||
t.Error("could not find any shadowed variable")
|
||||
}
|
||||
if !foundNonShadowed {
|
||||
t.Error("could not find any non-shadowed variable")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
312
pkg/proc/scope_test.go
Normal file
312
pkg/proc/scope_test.go
Normal file
@ -0,0 +1,312 @@
|
||||
package proc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"math"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/derekparker/delve/pkg/goversion"
|
||||
"github.com/derekparker/delve/pkg/proc"
|
||||
protest "github.com/derekparker/delve/pkg/proc/test"
|
||||
)
|
||||
|
||||
func TestScopeWithEscapedVariable(t *testing.T) {
|
||||
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 3, 0, ""}) {
|
||||
return
|
||||
}
|
||||
|
||||
withTestProcess("scopeescapevareval", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
assertNoError(proc.Continue(p), t, "Continue")
|
||||
|
||||
// On the breakpoint there are two 'a' variables in scope, the one that
|
||||
// isn't shadowed is a variable that escapes to the heap and figures in
|
||||
// debug_info as '&a'. Evaluating 'a' should yield the escaped variable.
|
||||
|
||||
avar, err := evalVariable(p, "a")
|
||||
assertNoError(err, t, "EvalVariable(a)")
|
||||
if aval, _ := constant.Int64Val(avar.Value); aval != 3 {
|
||||
t.Errorf("wrong value for variable a: %d", aval)
|
||||
}
|
||||
|
||||
if avar.Flags&proc.VariableEscaped == 0 {
|
||||
t.Errorf("variale a isn't escaped to the heap")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestScope will:
|
||||
// - run _fixtures/scopetest.go
|
||||
// - set a breakpoint on all lines containing a comment
|
||||
// - continue until the program ends
|
||||
// - every time a breakpoint is hit it will check that
|
||||
// scope.FunctionArguments+scope.LocalVariables and scope.EvalExpression
|
||||
// return what the corresponding comment describes they should return and
|
||||
// removes the breakpoint.
|
||||
//
|
||||
// Each comment is a comma separated list of variable declarations, with
|
||||
// each variable declaration having the following format:
|
||||
//
|
||||
// name type = initialvalue
|
||||
//
|
||||
// the = and the initial value are optional and can only be specified if the
|
||||
// type is an integer type, float32, float64 or bool.
|
||||
//
|
||||
// If multiple variables with the same name are specified
|
||||
// LocalVariables+FunctionArguments should return them in the same order and
|
||||
// EvalExpression should return the last one.
|
||||
func TestScope(t *testing.T) {
|
||||
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
|
||||
return
|
||||
}
|
||||
|
||||
fixturesDir := protest.FindFixturesDir()
|
||||
scopetestPath := filepath.Join(fixturesDir, "scopetest.go")
|
||||
|
||||
scopeChecks := getScopeChecks(scopetestPath, t)
|
||||
|
||||
withTestProcess("scopetest", t, func(p proc.Process, fixture protest.Fixture) {
|
||||
for i := range scopeChecks {
|
||||
setFileBreakpoint(p, t, fixture, scopeChecks[i].line)
|
||||
}
|
||||
|
||||
t.Logf("%d breakpoints set", len(scopeChecks))
|
||||
|
||||
for {
|
||||
if err := proc.Continue(p); err != nil {
|
||||
if _, exited := err.(proc.ProcessExitedError); exited {
|
||||
break
|
||||
}
|
||||
assertNoError(err, t, "Continue()")
|
||||
}
|
||||
bp, _, _ := p.CurrentThread().Breakpoint()
|
||||
|
||||
scopeCheck := findScopeCheck(scopeChecks, bp.Line)
|
||||
if scopeCheck == nil {
|
||||
t.Errorf("unknown stop position %s:%d %#x", bp.File, bp.Line, bp.Addr)
|
||||
}
|
||||
|
||||
scope, err := proc.GoroutineScope(p.CurrentThread())
|
||||
assertNoError(err, t, "GoroutineScope()")
|
||||
|
||||
args, err := scope.FunctionArguments(normalLoadConfig)
|
||||
assertNoError(err, t, "FunctionArguments()")
|
||||
locals, err := scope.LocalVariables(normalLoadConfig)
|
||||
assertNoError(err, t, "LocalVariables()")
|
||||
|
||||
for _, arg := range args {
|
||||
scopeCheck.checkVar(arg, t)
|
||||
}
|
||||
|
||||
for _, local := range locals {
|
||||
scopeCheck.checkVar(local, t)
|
||||
}
|
||||
|
||||
for i := range scopeCheck.varChecks {
|
||||
if !scopeCheck.varChecks[i].ok {
|
||||
t.Errorf("%d: variable %s not found", scopeCheck.line, scopeCheck.varChecks[i].name)
|
||||
}
|
||||
}
|
||||
|
||||
var prev *varCheck
|
||||
for i := range scopeCheck.varChecks {
|
||||
vc := &scopeCheck.varChecks[i]
|
||||
if prev != nil && prev.name != vc.name {
|
||||
prev.checkInScope(scopeCheck.line, scope, t)
|
||||
}
|
||||
prev = vc
|
||||
}
|
||||
if prev != nil {
|
||||
prev.checkInScope(scopeCheck.line, scope, t)
|
||||
}
|
||||
|
||||
scopeCheck.ok = true
|
||||
_, err = p.ClearBreakpoint(bp.Addr)
|
||||
assertNoError(err, t, "ClearBreakpoint")
|
||||
}
|
||||
})
|
||||
|
||||
for i := range scopeChecks {
|
||||
if !scopeChecks[i].ok {
|
||||
t.Errorf("breakpoint at line %d not hit", scopeChecks[i].line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type scopeCheck struct {
|
||||
line int
|
||||
varChecks []varCheck
|
||||
ok bool // this scope check was passed
|
||||
}
|
||||
|
||||
type varCheck struct {
|
||||
name string
|
||||
typ string
|
||||
kind reflect.Kind
|
||||
hasVal bool
|
||||
intVal int64
|
||||
uintVal uint64
|
||||
floatVal float64
|
||||
boolVal bool
|
||||
|
||||
ok bool // this variable check was passed
|
||||
}
|
||||
|
||||
func getScopeChecks(path string, t *testing.T) []scopeCheck {
|
||||
var fset token.FileSet
|
||||
root, err := parser.ParseFile(&fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse %s: %v", path, err)
|
||||
}
|
||||
|
||||
scopeChecks := []scopeCheck{}
|
||||
|
||||
for _, cmtg := range root.Comments {
|
||||
for _, cmt := range cmtg.List {
|
||||
pos := fset.Position(cmt.Slash)
|
||||
|
||||
scopeChecks = append(scopeChecks, scopeCheck{line: pos.Line})
|
||||
scopeChecks[len(scopeChecks)-1].Parse(cmt.Text[2:], t)
|
||||
}
|
||||
}
|
||||
|
||||
return scopeChecks
|
||||
}
|
||||
|
||||
func findScopeCheck(scopeChecks []scopeCheck, line int) *scopeCheck {
|
||||
for i := range scopeChecks {
|
||||
if scopeChecks[i].line == line {
|
||||
return &scopeChecks[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (check *scopeCheck) Parse(descr string, t *testing.T) {
|
||||
decls := strings.Split(descr, ",")
|
||||
check.varChecks = make([]varCheck, len(decls))
|
||||
for i, decl := range decls {
|
||||
varcheck := &check.varChecks[i]
|
||||
value := ""
|
||||
if equal := strings.Index(decl, "="); equal >= 0 {
|
||||
value = strings.TrimSpace(decl[equal+1:])
|
||||
decl = strings.TrimSpace(decl[:equal])
|
||||
varcheck.hasVal = true
|
||||
} else {
|
||||
decl = strings.TrimSpace(decl)
|
||||
}
|
||||
|
||||
space := strings.Index(decl, " ")
|
||||
if space < 0 {
|
||||
t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
|
||||
}
|
||||
varcheck.name = strings.TrimSpace(decl[:space])
|
||||
varcheck.typ = strings.TrimSpace(decl[space+1:])
|
||||
if strings.Index(varcheck.typ, " ") >= 0 {
|
||||
t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
|
||||
}
|
||||
|
||||
if !varcheck.hasVal {
|
||||
continue
|
||||
}
|
||||
|
||||
switch varcheck.typ {
|
||||
case "int", "int8", "int16", "int32", "int64":
|
||||
var err error
|
||||
varcheck.kind = reflect.Int
|
||||
varcheck.intVal, err = strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse scope comment %q: %v", descr, err)
|
||||
}
|
||||
|
||||
case "uint", "uint8", "uint16", "uint32", "uint64", "uintptr":
|
||||
var err error
|
||||
varcheck.kind = reflect.Uint
|
||||
varcheck.uintVal, err = strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse scope comment %q: %v", descr, err)
|
||||
}
|
||||
|
||||
case "float32", "float64":
|
||||
var err error
|
||||
varcheck.kind = reflect.Float64
|
||||
varcheck.floatVal, err = strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse scope comment %q: %v", descr, err)
|
||||
}
|
||||
|
||||
case "bool":
|
||||
var err error
|
||||
varcheck.kind = reflect.Bool
|
||||
varcheck.boolVal, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse scope comment %q: %v", descr, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (check *scopeCheck) checkVar(v *proc.Variable, t *testing.T) {
|
||||
var varCheck *varCheck
|
||||
for i := range check.varChecks {
|
||||
varCheck = &check.varChecks[i]
|
||||
if !varCheck.ok && (varCheck.name == v.Name) {
|
||||
varCheck = &check.varChecks[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if varCheck == nil {
|
||||
t.Errorf("%d: unexpected variable %s", check.line, v.Name)
|
||||
}
|
||||
|
||||
varCheck.check(check.line, v, t, "FunctionArguments+LocalVariables")
|
||||
varCheck.ok = true
|
||||
}
|
||||
|
||||
func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) {
|
||||
v, err := scope.EvalVariable(varCheck.name, normalLoadConfig)
|
||||
assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name))
|
||||
varCheck.check(line, v, t, "EvalExpression")
|
||||
|
||||
}
|
||||
|
||||
func (varCheck *varCheck) check(line int, v *proc.Variable, t *testing.T, ctxt string) {
|
||||
typ := v.DwarfType.String()
|
||||
typ = strings.Replace(typ, " ", "", -1)
|
||||
if typ != varCheck.typ {
|
||||
t.Errorf("%d: wrong type for %s (%s), got %s, expected %s", line, v.Name, ctxt, typ, varCheck.typ)
|
||||
}
|
||||
|
||||
if !varCheck.hasVal {
|
||||
return
|
||||
}
|
||||
|
||||
switch varCheck.kind {
|
||||
case reflect.Int:
|
||||
if vv, _ := constant.Int64Val(v.Value); vv != varCheck.intVal {
|
||||
t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.intVal)
|
||||
}
|
||||
case reflect.Uint:
|
||||
if vv, _ := constant.Uint64Val(v.Value); vv != varCheck.uintVal {
|
||||
t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.uintVal)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if vv, _ := constant.Float64Val(v.Value); math.Abs(vv-varCheck.floatVal) > 0.001 {
|
||||
t.Errorf("%d: wrong value for %s (%s), got %g expected %g", line, v.Name, ctxt, vv, varCheck.floatVal)
|
||||
}
|
||||
case reflect.Bool:
|
||||
if vv := constant.BoolVal(v.Value); vv != varCheck.boolVal {
|
||||
t.Errorf("%d: wrong value for %s (%s), got %v expected %v", line, v.Name, ctxt, vv, varCheck.boolVal)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"go/token"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
@ -45,6 +46,9 @@ type VariableFlags uint16
|
||||
const (
|
||||
// VariableEscaped is set for local variables that escaped to the heap
|
||||
VariableEscaped VariableFlags = (1 << iota)
|
||||
// VariableShadowed is set for local variables that are shadowed by a
|
||||
// variable with the same name in another scope
|
||||
VariableShadowed
|
||||
)
|
||||
|
||||
// Variable represents a variable. It contains the address, name,
|
||||
@ -579,52 +583,22 @@ func (scope *EvalScope) SetVariable(name, value string) error {
|
||||
return xv.setValue(yv)
|
||||
}
|
||||
|
||||
func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry, cfg LoadConfig) (*Variable, error) {
|
||||
rdr := scope.DwarfReader()
|
||||
v, err := scope.extractVarInfoFromEntry(entry, rdr)
|
||||
func (scope *EvalScope) extractVariableFromEntry(entry *dwarf.Entry) (*Variable, error) {
|
||||
v, err := scope.extractVarInfoFromEntry(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (scope *EvalScope) extractVarInfo(varName string) (*Variable, error) {
|
||||
reader := scope.DwarfReader()
|
||||
off, err := scope.BinInfo.findFunctionDebugInfo(scope.PC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader.Seek(off)
|
||||
reader.Next()
|
||||
|
||||
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Tag == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
n, ok := entry.Val(dwarf.AttrName).(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if n == varName {
|
||||
return scope.extractVarInfoFromEntry(entry, reader)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", varName)
|
||||
}
|
||||
|
||||
// LocalVariables returns all local variables from the current function scope.
|
||||
func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
|
||||
return scope.variablesByTag(dwarf.TagVariable, cfg)
|
||||
return scope.variablesByTag(dwarf.TagVariable, &cfg)
|
||||
}
|
||||
|
||||
// FunctionArguments returns the name, value, and type of all current function arguments.
|
||||
func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
|
||||
return scope.variablesByTag(dwarf.TagFormalParameter, cfg)
|
||||
return scope.variablesByTag(dwarf.TagFormalParameter, &cfg)
|
||||
}
|
||||
|
||||
// PackageVariables returns the name, value, and type of all package variables in the application.
|
||||
@ -648,7 +622,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
|
||||
}
|
||||
|
||||
// Ignore errors trying to extract values
|
||||
val, err := scope.extractVariableFromEntry(entry, cfg)
|
||||
val, err := scope.extractVariableFromEntry(entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -668,7 +642,7 @@ func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return scope.extractVarInfoFromEntry(entry, reader)
|
||||
return scope.extractVarInfoFromEntry(entry)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find symbol value for %s", name)
|
||||
@ -735,7 +709,7 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
|
||||
|
||||
// Extracts the name and type of a variable from a dwarf entry
|
||||
// then executes the instructions given in the DW_AT_location attribute to grab the variable's address
|
||||
func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry, rdr *reader.Reader) (*Variable, error) {
|
||||
func (scope *EvalScope) extractVarInfoFromEntry(entry *dwarf.Entry) (*Variable, error) {
|
||||
if entry == nil {
|
||||
return nil, fmt.Errorf("invalid entry")
|
||||
}
|
||||
@ -1665,39 +1639,61 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig
|
||||
}
|
||||
}
|
||||
|
||||
type variablesByDepth struct {
|
||||
vars []*Variable
|
||||
depths []int
|
||||
}
|
||||
|
||||
func (v *variablesByDepth) Len() int { return len(v.vars) }
|
||||
|
||||
func (v *variablesByDepth) Less(i int, j int) bool { return v.depths[i] < v.depths[j] }
|
||||
|
||||
func (v *variablesByDepth) Swap(i int, j int) {
|
||||
v.depths[i], v.depths[j] = v.depths[j], v.depths[i]
|
||||
v.vars[i], v.vars[j] = v.vars[j], v.vars[i]
|
||||
}
|
||||
|
||||
// Fetches all variables of a specific type in the current function scope
|
||||
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variable, error) {
|
||||
reader := scope.DwarfReader()
|
||||
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Variable, error) {
|
||||
off, err := scope.BinInfo.findFunctionDebugInfo(scope.PC)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader.Seek(off)
|
||||
reader.Next()
|
||||
|
||||
var vars []*Variable
|
||||
for entry, err := reader.NextScopeVariable(); entry != nil; entry, err = reader.NextScopeVariable() {
|
||||
var depths []int
|
||||
varReader := reader.Variables(scope.BinInfo.dwarf, off, scope.PC, tag == dwarf.TagVariable)
|
||||
hasScopes := false
|
||||
for varReader.Next() {
|
||||
entry := varReader.Entry()
|
||||
if entry.Tag != tag {
|
||||
continue
|
||||
}
|
||||
val, err := scope.extractVariableFromEntry(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// skip variables that we can't parse yet
|
||||
continue
|
||||
}
|
||||
if entry.Tag == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if entry.Tag == tag {
|
||||
val, err := scope.extractVariableFromEntry(entry, cfg)
|
||||
if err != nil {
|
||||
// skip variables that we can't parse yet
|
||||
continue
|
||||
}
|
||||
|
||||
vars = append(vars, val)
|
||||
vars = append(vars, val)
|
||||
depth := varReader.Depth()
|
||||
depths = append(depths, depth)
|
||||
if depth > 1 {
|
||||
hasScopes = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := varReader.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(vars) <= 0 {
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
if hasScopes {
|
||||
sort.Stable(&variablesByDepth{vars, depths})
|
||||
}
|
||||
|
||||
// prefetch the whole chunk of memory relative to these variables
|
||||
|
||||
minaddr := vars[0].Addr
|
||||
@ -1728,6 +1724,8 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variab
|
||||
}
|
||||
}
|
||||
|
||||
lvn := map[string]*Variable{} // lvn[n] is the last variable we saw named n
|
||||
|
||||
for i, v := range vars {
|
||||
if name := v.Name; len(name) > 1 && name[0] == '&' {
|
||||
v = v.maybeDereference()
|
||||
@ -1735,7 +1733,15 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg LoadConfig) ([]*Variab
|
||||
v.Flags |= VariableEscaped
|
||||
vars[i] = v
|
||||
}
|
||||
v.loadValue(cfg)
|
||||
if hasScopes {
|
||||
if otherv := lvn[v.Name]; otherv != nil {
|
||||
otherv.Flags |= VariableShadowed
|
||||
}
|
||||
lvn[v.Name] = v
|
||||
}
|
||||
if cfg != nil {
|
||||
v.loadValue(*cfg)
|
||||
}
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
|
@ -170,6 +170,8 @@ If regex is specified only function arguments with a name matching it will be re
|
||||
|
||||
[goroutine <n>] [frame <m>] locals [-v] [<regex>]
|
||||
|
||||
The name of variables that are shadowed in the current scope will be shown in parenthesis.
|
||||
|
||||
If regex is specified only local variables with a name matching it will be returned. If -v is specified more information about each local variable will be shown.`},
|
||||
{aliases: []string{"vars"}, cmdFn: vars, helpMsg: `Print package variables.
|
||||
|
||||
@ -935,10 +937,14 @@ func printFilteredVariables(varType string, vars []api.Variable, filter string,
|
||||
for _, v := range vars {
|
||||
if reg == nil || reg.Match([]byte(v.Name)) {
|
||||
match = true
|
||||
name := v.Name
|
||||
if v.Flags&api.VariableShadowed != 0 {
|
||||
name = "(" + name + ")"
|
||||
}
|
||||
if cfg == ShortLoadConfig {
|
||||
fmt.Printf("%s = %s\n", v.Name, v.SinglelineString())
|
||||
fmt.Printf("%s = %s\n", name, v.SinglelineString())
|
||||
} else {
|
||||
fmt.Printf("%s = %s\n", v.Name, v.MultilineString(""))
|
||||
fmt.Printf("%s = %s\n", name, v.MultilineString(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,10 +147,16 @@ type Function struct {
|
||||
GoType uint64 `json:"goType"`
|
||||
}
|
||||
|
||||
// VariableFlags is the type of the Flags field of Variable.
|
||||
type VariableFlags uint16
|
||||
|
||||
const (
|
||||
// VariableEscaped is set for local variables that escaped to the heap
|
||||
VariableEscaped = VariableFlags(proc.VariableEscaped)
|
||||
|
||||
// VariableShadowed is set for local variables that are shadowed by a
|
||||
// variable with the same name in another scope
|
||||
VariableShadowed = VariableFlags(proc.VariableShadowed)
|
||||
)
|
||||
|
||||
// Variable describes a variable.
|
||||
|
Loading…
Reference in New Issue
Block a user