From 8b4392dc46405a78482a711efd3a6ff0e48da127 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Fri, 8 Sep 2017 12:31:20 +0200 Subject: [PATCH] pkg/proc: use constants to describe variable value --- _fixtures/consts.go | 36 ++++++++++ pkg/proc/bininfo.go | 27 ++++++- pkg/proc/eval.go | 4 +- pkg/proc/moduledata.go | 4 +- pkg/proc/types.go | 17 +++++ pkg/proc/variables.go | 127 ++++++++++++++++++++++++++++++++- service/api/conversions.go | 5 +- service/api/types.go | 3 + service/test/variables_test.go | 27 +++++++ 9 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 _fixtures/consts.go diff --git a/_fixtures/consts.go b/_fixtures/consts.go new file mode 100644 index 00000000..9163b6a7 --- /dev/null +++ b/_fixtures/consts.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "runtime" +) + +type ConstType uint8 + +const ( + constZero ConstType = iota + constOne + constTwo + constThree +) + +type BitFieldType uint8 + +const ( + bitZero BitFieldType = 1 << iota + bitOne + bitTwo + bitThree + bitFour +) + +func main() { + a := constTwo + b := constThree + c := bitZero | bitOne + d := BitFieldType(33) + e := ConstType(10) + f := BitFieldType(0) + runtime.Breakpoint() + fmt.Println(a, b, c, d, e, f) +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index b3bd0523..1a411187 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -53,6 +53,9 @@ type BinaryInfo struct { moduleData []moduleData nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry + // consts[off] lists all the constants with the type defined at offset off. + consts constantsMap + loadErrMu sync.Mutex loadErr error } @@ -83,13 +86,17 @@ type Function struct { // or the empty string if there is none. // Borrowed from $GOROOT/debug/gosym/symtab.go func (fn *Function) PackageName() string { - pathend := strings.LastIndex(fn.Name, "/") + return packageName(fn.Name) +} + +func packageName(name string) string { + pathend := strings.LastIndex(name, "/") if pathend < 0 { pathend = 0 } - if i := strings.Index(fn.Name[pathend:], "."); i != -1 { - return fn.Name[:pathend+i] + if i := strings.Index(name[pathend:], "."); i != -1 { + return name[:pathend+i] } return "" } @@ -119,6 +126,20 @@ func (fn *Function) BaseName() string { return fn.Name } +type constantsMap map[dwarf.Offset]*constantType + +type constantType struct { + initialized bool + values []constantValue +} + +type constantValue struct { + name string + fullName string + value int64 + singleBit bool +} + type loclistReader struct { data []byte cur int diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index e3147416..7e08e659 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -184,7 +184,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) { return scope.Gvar.clone(), nil } else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" { return newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem), nil - } else if v, err := scope.packageVarAddr(maybePkg.Name + "." + node.Sel.Name); err == nil { + } else if v, err := scope.findGlobal(maybePkg.Name + "." + node.Sel.Name); err == nil { return v, nil } } @@ -586,7 +586,7 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { // 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.findGlobal(fn.PackageName() + "." + node.Name); err == nil { v.Name = node.Name return v, nil } diff --git a/pkg/proc/moduledata.go b/pkg/proc/moduledata.go index 1dbb5556..ec56f5d2 100644 --- a/pkg/proc/moduledata.go +++ b/pkg/proc/moduledata.go @@ -17,7 +17,7 @@ func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) { bi.loadModuleDataOnce.Do(func() { scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0} var md *Variable - md, err = scope.packageVarAddr("runtime.firstmoduledata") + md, err = scope.findGlobal("runtime.firstmoduledata") if err != nil { return } @@ -122,7 +122,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) { scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0} - reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs") + reflectOffs, err := scope.findGlobal("runtime.reflectOffs") if err != nil { return nil, err } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 13858ec6..c6378f40 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -177,6 +177,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou bi.packageVars = make(map[string]dwarf.Offset) bi.Functions = []Function{} bi.compileUnits = []*compileUnit{} + bi.consts = make(map[dwarf.Offset]*constantType) reader := bi.DwarfReader() var cu *compileUnit = nil for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { @@ -224,6 +225,22 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou bi.packageVars[n] = entry.Offset } + case dwarf.TagConstant: + name, okName := entry.Val(dwarf.AttrName).(string) + typ, okType := entry.Val(dwarf.AttrType).(dwarf.Offset) + val, okVal := entry.Val(dwarf.AttrConstValue).(int64) + if okName && okType && okVal { + if !cu.isgo { + name = "C." + name + } + ct := bi.consts[typ] + if ct == nil { + ct = &constantType{} + bi.consts[typ] = ct + } + ct.values = append(ct.values, constantValue{name: name, fullName: name, value: val}) + } + case dwarf.TagSubprogram: ok1 := false var lowpc, highpc uint64 diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 8d516403..346ce2db 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -54,6 +54,8 @@ const ( // VariableShadowed is set for local variables that are shadowed by a // variable with the same name in another scope VariableShadowed + // VariableConstant means this variable is a constant value + VariableConstant ) // Variable represents a variable. It contains the address, name, @@ -291,6 +293,7 @@ func newConstant(val constant.Value, mem MemoryReadWriter) *Variable { v.Kind = reflect.String v.Len = int64(len(constant.StringVal(val))) } + v.Flags |= VariableConstant return v } @@ -642,7 +645,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) { return vars, nil } -func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) { +func (scope *EvalScope) findGlobal(name string) (*Variable, error) { for n, off := range scope.BinInfo.packageVars { if n == name || strings.HasSuffix(n, "/"+name) { reader := scope.DwarfReader() @@ -654,6 +657,28 @@ func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) { return scope.extractVarInfoFromEntry(entry) } } + for offset, ctyp := range scope.BinInfo.consts { + for _, cval := range ctyp.values { + if cval.fullName == name { + t, err := scope.Type(offset) + if err != nil { + return nil, err + } + v := scope.newVariable(name, 0x0, t, scope.Mem) + switch v.Kind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.Value = constant.MakeInt64(cval.value) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v.Value = constant.MakeUint64(uint64(cval.value)) + default: + return nil, fmt.Errorf("unsupported constant kind %v", v.Kind) + } + v.Flags |= VariableConstant + v.loaded = true + return v, nil + } + } + } return nil, fmt.Errorf("could not find symbol value for %s", name) } @@ -1724,6 +1749,100 @@ func (v *Variable) loadInterface(recurseLevel int, loadData bool, cfg LoadConfig } } +// ConstDescr describes the value of v using constants. +func (v *Variable) ConstDescr() string { + if v.bi == nil || (v.Flags&VariableConstant != 0) { + return "" + } + ctyp := v.bi.consts.Get(v.DwarfType) + if ctyp == nil { + return "" + } + if typename := v.DwarfType.Common().Name; strings.Index(typename, ".") < 0 || strings.HasPrefix(typename, "C.") { + // only attempt to use constants for user defined type, otherwise every + // int variable with value 1 will be described with os.SEEK_CUR and other + // similar problems. + return "" + } + + switch v.Kind { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + fallthrough + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, _ := constant.Int64Val(v.Value) + return ctyp.describe(n) + } + return "" +} + +// popcnt is the number of bits set to 1 in x. +// It's the same as math/bits.OnesCount64, copied here so that we can build +// on versions of go that don't have math/bits. +func popcnt(x uint64) int { + const m0 = 0x5555555555555555 // 01010101 ... + const m1 = 0x3333333333333333 // 00110011 ... + const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ... + const m = 1<<64 - 1 + x = x>>1&(m0&m) + x&(m0&m) + x = x>>2&(m1&m) + x&(m1&m) + x = (x>>4 + x) & (m2 & m) + x += x >> 8 + x += x >> 16 + x += x >> 32 + return int(x) & (1<<7 - 1) +} + +func (cm constantsMap) Get(typ godwarf.Type) *constantType { + ctyp := cm[typ.Common().Offset] + if ctyp == nil { + return nil + } + typepkg := packageName(typ.String()) + "." + if !ctyp.initialized { + ctyp.initialized = true + sort.Sort(constantValuesByValue(ctyp.values)) + for i := range ctyp.values { + if strings.HasPrefix(ctyp.values[i].name, typepkg) { + ctyp.values[i].name = ctyp.values[i].name[len(typepkg):] + } + if popcnt(uint64(ctyp.values[i].value)) == 1 { + ctyp.values[i].singleBit = true + } + } + } + return ctyp +} + +func (ctyp *constantType) describe(n int64) string { + for _, val := range ctyp.values { + if val.value == n { + return val.name + } + } + + if n == 0 { + return "" + } + + // If all the values for this constant only have one bit set we try to + // represent the value as a bitwise or of constants. + + fields := []string{} + for _, val := range ctyp.values { + if !val.singleBit { + continue + } + if n&val.value != 0 { + fields = append(fields, val.name) + n = n & ^val.value + } + } + if n == 0 { + return strings.Join(fields, "|") + } + return "" +} + type variablesByDepth struct { vars []*Variable depths []int @@ -1833,3 +1952,9 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Varia return vars, nil } + +type constantValuesByValue []constantValue + +func (v constantValuesByValue) Len() int { return len(v) } +func (v constantValuesByValue) Less(i int, j int) bool { return v[i].value < v[j].value } +func (v constantValuesByValue) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } diff --git a/service/api/conversions.go b/service/api/conversions.go index c63cd669..36c3123d 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -139,7 +139,10 @@ func ConvertVar(v *proc.Variable) *Variable { case reflect.String, reflect.Func: r.Value = constant.StringVal(v.Value) default: - r.Value = v.Value.String() + r.Value = v.ConstDescr() + if r.Value == "" { + r.Value = v.Value.String() + } } } diff --git a/service/api/types.go b/service/api/types.go index 8eb66dde..3bcba150 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -165,6 +165,9 @@ const ( // VariableShadowed is set for local variables that are shadowed by a // variable with the same name in another scope VariableShadowed = VariableFlags(proc.VariableShadowed) + + // VariableConstant means this variable is a constant value + VariableConstant ) // Variable describes a variable. diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 45e6a31c..ddb55d55 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -949,3 +949,30 @@ func TestPackageRenames(t *testing.T) { } }) } + +func TestConstants(t *testing.T) { + testcases := []varTest{ + {"a", true, "constTwo", "", "main.ConstType", nil}, + {"b", true, "constThree", "", "main.ConstType", nil}, + {"c", true, "bitZero|bitOne", "", "main.BitFieldType", nil}, + {"d", true, "33", "", "main.BitFieldType", nil}, + {"e", true, "10", "", "main.ConstType", nil}, + {"f", true, "0", "", "main.BitFieldType", nil}, + {"bitZero", true, "1", "", "main.BitFieldType", nil}, + {"bitOne", true, "2", "", "main.BitFieldType", nil}, + {"constTwo", true, "2", "", "main.ConstType", nil}, + } + ver, _ := goversion.Parse(runtime.Version()) + if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}) { + // Not supported on 1.9 or earlier + return + } + withTestProcess("consts", t, func(p proc.Process, fixture protest.Fixture) { + assertNoError(proc.Continue(p), t, "Continue") + for _, testcase := range testcases { + variable, err := evalVariable(p, testcase.name, pnormalLoadConfig) + assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) + assertVariable(t, variable, testcase) + } + }) +}