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
|
moduleData []moduleData
|
||||||
nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry
|
nameOfRuntimeType map[uintptr]nameOfRuntimeTypeEntry
|
||||||
|
|
||||||
|
// consts[off] lists all the constants with the type defined at offset off.
|
||||||
|
consts constantsMap
|
||||||
|
|
||||||
loadErrMu sync.Mutex
|
loadErrMu sync.Mutex
|
||||||
loadErr error
|
loadErr error
|
||||||
}
|
}
|
||||||
@ -83,13 +86,17 @@ type Function struct {
|
|||||||
// or the empty string if there is none.
|
// or the empty string if there is none.
|
||||||
// Borrowed from $GOROOT/debug/gosym/symtab.go
|
// Borrowed from $GOROOT/debug/gosym/symtab.go
|
||||||
func (fn *Function) PackageName() string {
|
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 {
|
if pathend < 0 {
|
||||||
pathend = 0
|
pathend = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if i := strings.Index(fn.Name[pathend:], "."); i != -1 {
|
if i := strings.Index(name[pathend:], "."); i != -1 {
|
||||||
return fn.Name[:pathend+i]
|
return name[:pathend+i]
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -119,6 +126,20 @@ func (fn *Function) BaseName() string {
|
|||||||
return fn.Name
|
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 {
|
type loclistReader struct {
|
||||||
data []byte
|
data []byte
|
||||||
cur int
|
cur int
|
||||||
|
@ -184,7 +184,7 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
|
|||||||
return scope.Gvar.clone(), nil
|
return scope.Gvar.clone(), nil
|
||||||
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
|
} else if maybePkg.Name == "runtime" && node.Sel.Name == "frameoff" {
|
||||||
return newConstant(constant.MakeInt64(scope.frameOffset), scope.Mem), nil
|
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
|
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
|
// 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)
|
_, _, fn := scope.BinInfo.PCToLine(scope.PC)
|
||||||
if fn != nil {
|
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
|
v.Name = node.Name
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
|
|||||||
bi.loadModuleDataOnce.Do(func() {
|
bi.loadModuleDataOnce.Do(func() {
|
||||||
scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0}
|
scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0}
|
||||||
var md *Variable
|
var md *Variable
|
||||||
md, err = scope.packageVarAddr("runtime.firstmoduledata")
|
md, err = scope.findGlobal("runtime.firstmoduledata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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) {
|
func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
|
||||||
scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0}
|
scope := &EvalScope{0, op.DwarfRegisters{}, mem, nil, bi, 0}
|
||||||
reflectOffs, err := scope.packageVarAddr("runtime.reflectOffs")
|
reflectOffs, err := scope.findGlobal("runtime.reflectOffs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou
|
|||||||
bi.packageVars = make(map[string]dwarf.Offset)
|
bi.packageVars = make(map[string]dwarf.Offset)
|
||||||
bi.Functions = []Function{}
|
bi.Functions = []Function{}
|
||||||
bi.compileUnits = []*compileUnit{}
|
bi.compileUnits = []*compileUnit{}
|
||||||
|
bi.consts = make(map[dwarf.Offset]*constantType)
|
||||||
reader := bi.DwarfReader()
|
reader := bi.DwarfReader()
|
||||||
var cu *compileUnit = nil
|
var cu *compileUnit = nil
|
||||||
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
|
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
|
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:
|
case dwarf.TagSubprogram:
|
||||||
ok1 := false
|
ok1 := false
|
||||||
var lowpc, highpc uint64
|
var lowpc, highpc uint64
|
||||||
|
@ -54,6 +54,8 @@ const (
|
|||||||
// VariableShadowed is set for local variables that are shadowed by a
|
// VariableShadowed is set for local variables that are shadowed by a
|
||||||
// variable with the same name in another scope
|
// variable with the same name in another scope
|
||||||
VariableShadowed
|
VariableShadowed
|
||||||
|
// VariableConstant means this variable is a constant value
|
||||||
|
VariableConstant
|
||||||
)
|
)
|
||||||
|
|
||||||
// Variable represents a variable. It contains the address, name,
|
// 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.Kind = reflect.String
|
||||||
v.Len = int64(len(constant.StringVal(val)))
|
v.Len = int64(len(constant.StringVal(val)))
|
||||||
}
|
}
|
||||||
|
v.Flags |= VariableConstant
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -642,7 +645,7 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
|
|||||||
return vars, nil
|
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 {
|
for n, off := range scope.BinInfo.packageVars {
|
||||||
if n == name || strings.HasSuffix(n, "/"+name) {
|
if n == name || strings.HasSuffix(n, "/"+name) {
|
||||||
reader := scope.DwarfReader()
|
reader := scope.DwarfReader()
|
||||||
@ -654,6 +657,28 @@ func (scope *EvalScope) packageVarAddr(name string) (*Variable, error) {
|
|||||||
return scope.extractVarInfoFromEntry(entry)
|
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)
|
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 {
|
type variablesByDepth struct {
|
||||||
vars []*Variable
|
vars []*Variable
|
||||||
depths []int
|
depths []int
|
||||||
@ -1833,3 +1952,9 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Varia
|
|||||||
|
|
||||||
return vars, nil
|
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:
|
case reflect.String, reflect.Func:
|
||||||
r.Value = constant.StringVal(v.Value)
|
r.Value = constant.StringVal(v.Value)
|
||||||
default:
|
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
|
// VariableShadowed is set for local variables that are shadowed by a
|
||||||
// variable with the same name in another scope
|
// variable with the same name in another scope
|
||||||
VariableShadowed = VariableFlags(proc.VariableShadowed)
|
VariableShadowed = VariableFlags(proc.VariableShadowed)
|
||||||
|
|
||||||
|
// VariableConstant means this variable is a constant value
|
||||||
|
VariableConstant
|
||||||
)
|
)
|
||||||
|
|
||||||
// Variable describes a variable.
|
// 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