提交 2ad9ce6f 编写于 作者: A aarzilli 提交者: Derek Parker

proc: lexical block support

Fixes #106
上级 1128c26b
......@@ -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.
package main
import (
"fmt"
"runtime"
)
func main() {
a := 2
{
a := 3
p := &a
runtime.Breakpoint()
fmt.Println(a, p)
}
fmt.Println(a)
}
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()
}
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() {
......
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")
}
})
}
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
vars = append(vars, val)
depth := varReader.Depth()
depths = append(depths, depth)
if depth > 1 {
hasScopes = true
}
}
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)
}
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
......
......@@ -169,6 +169,8 @@ If regex is specified only function arguments with a name matching it will be re
{aliases: []string{"locals"}, allowedPrefixes: scopePrefix | onPrefix, cmdFn: locals, helpMsg: `Print local variables.
[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.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册