pkg/proc: use constants to describe variable value

This commit is contained in:
aarzilli 2017-09-08 12:31:20 +02:00 committed by Derek Parker
parent 85669434f6
commit 8b4392dc46
9 changed files with 241 additions and 9 deletions

36
_fixtures/consts.go Normal file

@ -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)
}

@ -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

@ -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
}

@ -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
}

@ -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

@ -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] }

@ -139,9 +139,12 @@ func ConvertVar(v *proc.Variable) *Variable {
case reflect.String, reflect.Func:
r.Value = constant.StringVal(v.Value)
default:
r.Value = v.ConstDescr()
if r.Value == "" {
r.Value = v.Value.String()
}
}
}
switch v.Kind {
case reflect.Complex64:

@ -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.

@ -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)
}
})
}