service/locations: hooked expression evaluator to location specifiers
Location specifiers starting with '*' can be followed by any expression supported by the evaluator. The expression should evaluate to either an integer (which will be interpreted as an address) or to a function pointer (which will be dereferenced to get the function's entry point).
This commit is contained in:
parent
453bd0217f
commit
70cbbdc083
29
_fixtures/locationsprog2.go
Normal file
29
_fixtures/locationsprog2.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func afunction(s string) {
|
||||
fmt.Println(s)
|
||||
}
|
||||
|
||||
type someStruct struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (o *someStruct) structfunc(s2 string) {
|
||||
fmt.Println(o.s, s2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
fn1 := afunction
|
||||
var o someStruct
|
||||
fn2 := o.structfunc
|
||||
fn3 := func(s string) {
|
||||
fmt.Println("inline", s)
|
||||
}
|
||||
runtime.Breakpoint()
|
||||
fmt.Println(fn1, fn2, fn3, o)
|
||||
}
|
16
proc/eval.go
16
proc/eval.go
@ -282,7 +282,7 @@ func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
if arg.Unreadable != nil {
|
||||
return nil, arg.Unreadable
|
||||
}
|
||||
if arg.base == 0 {
|
||||
if arg.Base == 0 {
|
||||
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
||||
}
|
||||
return newConstant(arg.Children[1].Value, arg.mem), nil
|
||||
@ -315,7 +315,7 @@ func lenBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
|
||||
if arg.Unreadable != nil {
|
||||
return nil, arg.Unreadable
|
||||
}
|
||||
if arg.base == 0 {
|
||||
if arg.Base == 0 {
|
||||
return newConstant(constant.MakeInt64(0), arg.mem), nil
|
||||
}
|
||||
return newConstant(arg.Children[0].Value, arg.mem), nil
|
||||
@ -507,7 +507,7 @@ func (scope *EvalScope) evalIndex(node *ast.IndexExpr) (*Variable, error) {
|
||||
|
||||
switch xev.Kind {
|
||||
case reflect.Slice, reflect.Array, reflect.String:
|
||||
if xev.base == 0 {
|
||||
if xev.Base == 0 {
|
||||
return nil, fmt.Errorf("can not index \"%s\"", exprToString(node.X))
|
||||
}
|
||||
n, err := idxev.asInt()
|
||||
@ -567,7 +567,7 @@ func (scope *EvalScope) evalReslice(node *ast.SliceExpr) (*Variable, error) {
|
||||
|
||||
switch xev.Kind {
|
||||
case reflect.Slice, reflect.Array, reflect.String:
|
||||
if xev.base == 0 {
|
||||
if xev.Base == 0 {
|
||||
return nil, fmt.Errorf("can not slice \"%s\"", exprToString(node.X))
|
||||
}
|
||||
return xev.reslice(low, high)
|
||||
@ -917,7 +917,7 @@ func (v *Variable) isNil() bool {
|
||||
case reflect.Interface:
|
||||
return false
|
||||
case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
|
||||
return v.base == 0
|
||||
return v.Base == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1039,7 +1039,7 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) {
|
||||
if idx < 0 || int64(idx) >= v.Len {
|
||||
return nil, fmt.Errorf("index out of bounds")
|
||||
}
|
||||
return v.newVariable("", v.base+uintptr(int64(idx)*v.stride), v.fieldType), nil
|
||||
return v.newVariable("", v.Base+uintptr(int64(idx)*v.stride), v.fieldType), nil
|
||||
}
|
||||
|
||||
func (v *Variable) mapAccess(idx *Variable) (*Variable, error) {
|
||||
@ -1081,7 +1081,7 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||
return nil, fmt.Errorf("index out of bounds")
|
||||
}
|
||||
|
||||
base := v.base + uintptr(int64(low)*v.stride)
|
||||
base := v.Base + uintptr(int64(low)*v.stride)
|
||||
len := high - low
|
||||
|
||||
if high-low < 0 {
|
||||
@ -1104,7 +1104,7 @@ func (v *Variable) reslice(low int64, high int64) (*Variable, error) {
|
||||
r := v.newVariable("", 0, typ)
|
||||
r.Cap = len
|
||||
r.Len = len
|
||||
r.base = base
|
||||
r.Base = base
|
||||
r.stride = v.stride
|
||||
r.fieldType = v.fieldType
|
||||
|
||||
|
49
proc/proc.go
49
proc/proc.go
@ -179,26 +179,7 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
||||
}
|
||||
|
||||
if firstLine {
|
||||
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
|
||||
if filepath.Ext(filename) != ".go" {
|
||||
return origfn.Entry, nil
|
||||
}
|
||||
for {
|
||||
lineno++
|
||||
pc, fn, _ := dbp.goSymTable.LineToPC(filename, lineno)
|
||||
if fn != nil {
|
||||
if fn.Name != funcName {
|
||||
if strings.Contains(fn.Name, funcName) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if fn.Name == funcName {
|
||||
return pc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return origfn.Entry, nil
|
||||
return dbp.FunctionEntryToFirstLine(origfn.Entry)
|
||||
} else if lineOffset > 0 {
|
||||
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
|
||||
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
|
||||
@ -208,6 +189,34 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
||||
return origfn.Entry, nil
|
||||
}
|
||||
|
||||
func (dbp *Process) FunctionEntryToFirstLine(entry uint64) (uint64, error) {
|
||||
filename, lineno, startfn := dbp.goSymTable.PCToLine(entry)
|
||||
if filepath.Ext(filename) != ".go" {
|
||||
return entry, nil
|
||||
}
|
||||
if startfn == nil {
|
||||
return entry, nil
|
||||
}
|
||||
funcName := startfn.Name
|
||||
|
||||
for {
|
||||
lineno++
|
||||
pc, fn, _ := dbp.goSymTable.LineToPC(filename, lineno)
|
||||
if fn != nil {
|
||||
if fn.Name != funcName {
|
||||
if strings.Contains(fn.Name, funcName) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if fn.Name == funcName {
|
||||
return pc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// RequestManualStop sets the `halt` flag and
|
||||
// sends SIGSTOP to all threads.
|
||||
func (dbp *Process) RequestManualStop() error {
|
||||
|
@ -49,11 +49,11 @@ type Variable struct {
|
||||
Len int64
|
||||
Cap int64
|
||||
|
||||
// base address of arrays, base address of the backing array for slices (0 for nil slices)
|
||||
// base address of the backing byte array for strings
|
||||
// Base address of arrays, Base address of the backing array for slices (0 for nil slices)
|
||||
// Base address of the backing byte array for strings
|
||||
// address of the struct backing chan and map variables
|
||||
// address of the function entry point for function variables (0 for nil function pointers)
|
||||
base uintptr
|
||||
Base uintptr
|
||||
stride int64
|
||||
fieldType dwarf.Type
|
||||
|
||||
@ -168,7 +168,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process,
|
||||
v.stride = 1
|
||||
v.fieldType = &dwarf.UintType{BasicType: dwarf.BasicType{CommonType: dwarf.CommonType{ByteSize: 1, Name: "byte"}, BitSize: 8, BitOffset: 0}}
|
||||
if v.Addr != 0 {
|
||||
v.base, v.Len, v.Unreadable = readStringInfo(v.mem, v.dbp.arch, v.Addr)
|
||||
v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.dbp.arch, v.Addr)
|
||||
}
|
||||
case t.StructName == "runtime.iface" || t.StructName == "runtime.eface":
|
||||
v.Kind = reflect.Interface
|
||||
@ -182,7 +182,7 @@ func newVariable(name string, addr uintptr, dwarfType dwarf.Type, dbp *Process,
|
||||
}
|
||||
case *dwarf.ArrayType:
|
||||
v.Kind = reflect.Array
|
||||
v.base = v.Addr
|
||||
v.Base = v.Addr
|
||||
v.Len = t.Count
|
||||
v.Cap = -1
|
||||
v.fieldType = t.Type
|
||||
@ -254,7 +254,7 @@ func newConstant(val constant.Value, mem memoryReadWriter) *Variable {
|
||||
|
||||
var nilVariable = &Variable{
|
||||
Addr: 0,
|
||||
base: 0,
|
||||
Base: 0,
|
||||
Kind: reflect.Ptr,
|
||||
Children: []Variable{{Addr: 0, OnlyAddr: true}},
|
||||
}
|
||||
@ -771,7 +771,7 @@ func (v *Variable) loadValue() {
|
||||
}
|
||||
|
||||
func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||
if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.base == 0) {
|
||||
if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.Base == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -788,14 +788,14 @@ func (v *Variable) loadValueInternal(recurseLevel int) {
|
||||
sv.loadValueInternal(recurseLevel)
|
||||
v.Children = sv.Children
|
||||
v.Len = sv.Len
|
||||
v.base = sv.Addr
|
||||
v.Base = sv.Addr
|
||||
|
||||
case reflect.Map:
|
||||
v.loadMap(recurseLevel)
|
||||
|
||||
case reflect.String:
|
||||
var val string
|
||||
val, v.Unreadable = readStringValue(v.mem, v.base, v.Len)
|
||||
val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len)
|
||||
v.Value = constant.MakeString(val)
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
@ -943,7 +943,7 @@ func (v *Variable) loadSliceInfo(t *dwarf.StructType) {
|
||||
var base uint64
|
||||
base, err = readUintRaw(v.mem, uintptr(int64(v.Addr)+f.ByteOffset), f.Type.Size())
|
||||
if err == nil {
|
||||
v.base = uintptr(base)
|
||||
v.Base = uintptr(base)
|
||||
// Dereference array type to get value type
|
||||
ptrType, ok := f.Type.(*dwarf.PtrType)
|
||||
if !ok {
|
||||
@ -993,13 +993,13 @@ func (v *Variable) loadArrayValues(recurseLevel int) {
|
||||
}
|
||||
|
||||
if v.stride < maxArrayStridePrefetch {
|
||||
v.mem = cacheMemory(v.mem, v.base, int(v.stride*count))
|
||||
v.mem = cacheMemory(v.mem, v.Base, int(v.stride*count))
|
||||
}
|
||||
|
||||
errcount := 0
|
||||
|
||||
for i := int64(0); i < count; i++ {
|
||||
fieldvar := v.newVariable("", uintptr(int64(v.base)+(i*v.stride)), v.fieldType)
|
||||
fieldvar := v.newVariable("", uintptr(int64(v.Base)+(i*v.stride)), v.fieldType)
|
||||
fieldvar.loadValueInternal(recurseLevel + 1)
|
||||
|
||||
if fieldvar.Unreadable != nil {
|
||||
@ -1160,7 +1160,7 @@ func (v *Variable) readFunctionPtr() {
|
||||
// dereference pointer to find function pc
|
||||
fnaddr := uintptr(binary.LittleEndian.Uint64(val))
|
||||
if fnaddr == 0 {
|
||||
v.base = 0
|
||||
v.Base = 0
|
||||
v.Value = constant.MakeString("")
|
||||
return
|
||||
}
|
||||
@ -1171,10 +1171,10 @@ func (v *Variable) readFunctionPtr() {
|
||||
return
|
||||
}
|
||||
|
||||
v.base = uintptr(binary.LittleEndian.Uint64(val))
|
||||
fn := v.dbp.goSymTable.PCToFunc(uint64(v.base))
|
||||
v.Base = uintptr(binary.LittleEndian.Uint64(val))
|
||||
fn := v.dbp.goSymTable.PCToFunc(uint64(v.Base))
|
||||
if fn == nil {
|
||||
v.Unreadable = fmt.Errorf("could not find function for %#v", v.base)
|
||||
v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1238,7 +1238,7 @@ type mapIterator struct {
|
||||
// Code derived from go/src/runtime/hashmap.go
|
||||
func (v *Variable) mapIterator() *mapIterator {
|
||||
sv := v.maybeDereference()
|
||||
v.base = sv.Addr
|
||||
v.Base = sv.Addr
|
||||
|
||||
maptype, ok := sv.RealType.(*dwarf.StructType)
|
||||
if !ok {
|
||||
@ -1253,7 +1253,7 @@ func (v *Variable) mapIterator() *mapIterator {
|
||||
return it
|
||||
}
|
||||
|
||||
v.mem = cacheMemory(v.mem, v.base, int(v.RealType.Size()))
|
||||
v.mem = cacheMemory(v.mem, v.Base, int(v.RealType.Size()))
|
||||
|
||||
for _, f := range maptype.Field {
|
||||
var err error
|
||||
|
@ -541,12 +541,9 @@ func (d *Debugger) FindLocation(scope api.EvalScope, locStr string) ([]api.Locat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc, err := d.process.PC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, _ := d.process.ConvertEvalScope(scope.GoroutineID, scope.Frame)
|
||||
|
||||
locs, err := loc.Find(d, pc, locStr)
|
||||
locs, err := loc.Find(d, s, locStr)
|
||||
for i := range locs {
|
||||
file, line, fn := d.process.PCToLine(locs[i].PC)
|
||||
locs[i].File = file
|
||||
|
@ -3,17 +3,20 @@ package debugger
|
||||
import (
|
||||
"debug/gosym"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derekparker/delve/proc"
|
||||
"github.com/derekparker/delve/service/api"
|
||||
)
|
||||
|
||||
const maxFindLocationCandidates = 5
|
||||
|
||||
type LocationSpec interface {
|
||||
Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error)
|
||||
Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error)
|
||||
}
|
||||
|
||||
type NormalLocationSpec struct {
|
||||
@ -27,7 +30,7 @@ type RegexLocationSpec struct {
|
||||
}
|
||||
|
||||
type AddrLocationSpec struct {
|
||||
Addr uint64
|
||||
AddrExpr string
|
||||
}
|
||||
|
||||
type OffsetLocationSpec struct {
|
||||
@ -80,15 +83,7 @@ func parseLocationSpec(locStr string) (LocationSpec, error) {
|
||||
}
|
||||
|
||||
case '*':
|
||||
rest = rest[1:]
|
||||
addr, err := strconv.ParseInt(rest, 0, 64)
|
||||
if err != nil {
|
||||
return nil, malformed(err.Error())
|
||||
}
|
||||
if addr == 0 {
|
||||
return nil, malformed("can not set breakpoint at address 0x0")
|
||||
}
|
||||
return &AddrLocationSpec{uint64(addr)}, nil
|
||||
return &AddrLocationSpec{rest[1:]}, nil
|
||||
|
||||
default:
|
||||
return parseLocationSpecDefault(locStr, rest)
|
||||
@ -232,7 +227,7 @@ func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (loc *RegexLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
||||
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
funcs := d.process.Funcs()
|
||||
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
|
||||
if err != nil {
|
||||
@ -248,8 +243,35 @@ func (loc *RegexLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (loc *AddrLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
||||
return []api.Location{{PC: loc.Addr}}, nil
|
||||
func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
if scope == nil {
|
||||
addr, err := strconv.ParseInt(loc.AddrExpr, 0, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
||||
}
|
||||
return []api.Location{{PC: uint64(addr)}}, nil
|
||||
} else {
|
||||
v, err := scope.EvalExpression(loc.AddrExpr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v.Unreadable != nil {
|
||||
return nil, v.Unreadable
|
||||
}
|
||||
switch v.Kind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
addr, _ := constant.Uint64Val(v.Value)
|
||||
return []api.Location{{PC: addr}}, nil
|
||||
case reflect.Func:
|
||||
pc, err := d.process.FunctionEntryToFirstLine(uint64(v.Base))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []api.Location{{PC: uint64(pc)}}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("wrong expression kind: %v", v.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (loc *NormalLocationSpec) FileMatch(path string) bool {
|
||||
@ -283,7 +305,7 @@ func (ale AmbiguousLocationError) Error() string {
|
||||
return fmt.Sprintf("Location \"%s\" ambiguous: %s…", ale.Location, strings.Join(candidates, ", "))
|
||||
}
|
||||
|
||||
func (loc *NormalLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
||||
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
funcs := d.process.Funcs()
|
||||
files := d.process.Sources()
|
||||
|
||||
@ -339,8 +361,11 @@ func (loc *NormalLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]ap
|
||||
}
|
||||
}
|
||||
|
||||
func (loc *OffsetLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
||||
file, line, fn := d.process.PCToLine(pc)
|
||||
func (loc *OffsetLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
if scope == nil {
|
||||
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
||||
}
|
||||
file, line, fn := d.process.PCToLine(scope.PC)
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
@ -348,8 +373,11 @@ func (loc *OffsetLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]ap
|
||||
return []api.Location{{PC: addr}}, err
|
||||
}
|
||||
|
||||
func (loc *LineLocationSpec) Find(d *Debugger, pc uint64, locStr string) ([]api.Location, error) {
|
||||
file, _, fn := d.process.PCToLine(pc)
|
||||
func (loc *LineLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
|
||||
if scope == nil {
|
||||
return nil, fmt.Errorf("could not determine current location (scope is nil)")
|
||||
}
|
||||
file, _, fn := d.process.PCToLine(scope.PC)
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("could not determine current location")
|
||||
}
|
||||
|
@ -610,6 +610,18 @@ func TestClientServer_FindLocations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServer_FindLocationsAddr(t *testing.T) {
|
||||
withTestClient("locationsprog2", t, func(c service.Client) {
|
||||
<-c.Continue()
|
||||
|
||||
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
|
||||
anonfunc := findLocationHelper(t, c, "locationsprog2.go:25", false, 1, 0)[0]
|
||||
|
||||
findLocationHelper(t, c, "*fn1", false, 1, afunction)
|
||||
findLocationHelper(t, c, "*fn3", false, 1, anonfunc)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientServer_EvalVariable(t *testing.T) {
|
||||
withTestClient("testvariables", t, func(c service.Client) {
|
||||
state := <-c.Continue()
|
||||
|
Loading…
Reference in New Issue
Block a user