pkg/proc: use constants to describe variable value
This commit is contained in:
parent
85669434f6
commit
8b4392dc46
36
_fixtures/consts.go
Normal file
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,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user