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:
aarzilli 2016-01-10 14:08:16 +01:00 committed by Derek Parker
parent 453bd0217f
commit 70cbbdc083
7 changed files with 145 additions and 70 deletions

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

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

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