terminal,service: add API and commands for watchpoints (#2488)
Adds API calls and terminal commands to set watchpoints.
This commit is contained in:
parent
370ce5c01c
commit
4f11320e4c
@ -34,6 +34,7 @@ Command | Description
|
||||
[on](#on) | Executes a command when a breakpoint is hit.
|
||||
[toggle](#toggle) | Toggles on or off a breakpoint.
|
||||
[trace](#trace) | Set tracepoint.
|
||||
[watch](#watch) | Set watchpoint.
|
||||
|
||||
|
||||
## Viewing program variables and memory
|
||||
@ -581,6 +582,24 @@ Print package variables.
|
||||
If regex is specified only package variables with a name matching it will be returned. If -v is specified more information about each package variable will be shown.
|
||||
|
||||
|
||||
## watch
|
||||
Set watchpoint.
|
||||
|
||||
watch [-r|-w|-rw] <expr>
|
||||
|
||||
-r stops when the memory location is read
|
||||
-w stops when the memory location is written
|
||||
-rw stops when the memory location is read or written
|
||||
|
||||
The memory location is specified with the same expression language used by 'print', for example:
|
||||
|
||||
watch v
|
||||
|
||||
will watch the address of variable 'v'.
|
||||
|
||||
See also: "help print".
|
||||
|
||||
|
||||
## whatis
|
||||
Prints type of an expression.
|
||||
|
||||
|
@ -26,6 +26,7 @@ clear_breakpoint(Id, Name) | Equivalent to API call [ClearBreakpoint](https://go
|
||||
clear_checkpoint(ID) | Equivalent to API call [ClearCheckpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearCheckpoint)
|
||||
raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, UnsafeCall) | Equivalent to API call [Command](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Command)
|
||||
create_breakpoint(Breakpoint) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint)
|
||||
create_watchpoint(Scope, Expr, Type) | Equivalent to API call [CreateWatchpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateWatchpoint)
|
||||
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)
|
||||
disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call [Disassemble](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Disassemble)
|
||||
dump_cancel() | Equivalent to API call [DumpCancel](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DumpCancel)
|
||||
|
@ -35,6 +35,7 @@ type Breakpoint struct {
|
||||
Name string // User defined name of the breakpoint
|
||||
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
||||
|
||||
WatchExpr string
|
||||
WatchType WatchType
|
||||
HWBreakIndex uint8 // hardware breakpoint index
|
||||
|
||||
@ -316,7 +317,11 @@ func (t *Target) SetWatchpoint(scope *EvalScope, expr string, wtype WatchType, c
|
||||
return nil, errors.New("can not watch stack allocated variable")
|
||||
}
|
||||
|
||||
return t.setBreakpointInternal(xv.Addr, UserBreakpoint, wtype.withSize(uint8(sz)), cond)
|
||||
bp, err := t.setBreakpointInternal(xv.Addr, UserBreakpoint, wtype.withSize(uint8(sz)), cond)
|
||||
if bp != nil {
|
||||
bp.WatchExpr = expr
|
||||
}
|
||||
return bp, err
|
||||
}
|
||||
|
||||
func (t *Target) setBreakpointInternal(addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
|
||||
|
@ -130,6 +130,21 @@ See also: "help on", "help cond" and "help clear"`},
|
||||
A tracepoint is a breakpoint that does not stop the execution of the program, instead when the tracepoint is hit a notification is displayed. See $GOPATH/src/github.com/go-delve/delve/Documentation/cli/locspec.md for the syntax of linespec.
|
||||
|
||||
See also: "help on", "help cond" and "help clear"`},
|
||||
{aliases: []string{"watch"}, group: breakCmds, cmdFn: watchpoint, helpMsg: `Set watchpoint.
|
||||
|
||||
watch [-r|-w|-rw] <expr>
|
||||
|
||||
-r stops when the memory location is read
|
||||
-w stops when the memory location is written
|
||||
-rw stops when the memory location is read or written
|
||||
|
||||
The memory location is specified with the same expression language used by 'print', for example:
|
||||
|
||||
watch v
|
||||
|
||||
will watch the address of variable 'v'.
|
||||
|
||||
See also: "help print".`},
|
||||
{aliases: []string{"restart", "r"}, group: runCmds, cmdFn: restart, helpMsg: `Restart process.
|
||||
|
||||
For recorded targets the command takes the following forms:
|
||||
@ -1664,6 +1679,30 @@ func edit(t *Term, ctx callContext, args string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func watchpoint(t *Term, ctx callContext, args string) error {
|
||||
v := strings.SplitN(args, " ", 2)
|
||||
if len(v) != 2 {
|
||||
return errors.New("wrong number of arguments: watch [-r|-w|-rw] <expr>")
|
||||
}
|
||||
var wtype api.WatchType
|
||||
switch v[0] {
|
||||
case "-r":
|
||||
wtype = api.WatchRead
|
||||
case "-w":
|
||||
wtype = api.WatchWrite
|
||||
case "-rw":
|
||||
wtype = api.WatchRead | api.WatchWrite
|
||||
default:
|
||||
return fmt.Errorf("wrong argument %q to watch", v[0])
|
||||
}
|
||||
bp, err := t.client.CreateWatchpoint(ctx.Scope, v[1], wtype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s set at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp))
|
||||
return nil
|
||||
}
|
||||
|
||||
func examineMemoryCmd(t *Term, ctx callContext, argstr string) error {
|
||||
var (
|
||||
address uint64
|
||||
@ -2432,7 +2471,9 @@ func printcontextThread(t *Term, th *api.Thread) {
|
||||
}
|
||||
|
||||
bpname := ""
|
||||
if th.Breakpoint.Name != "" {
|
||||
if th.Breakpoint.WatchExpr != "" {
|
||||
bpname = fmt.Sprintf("watchpoint on [%s] ", th.Breakpoint.WatchExpr)
|
||||
} else if th.Breakpoint.Name != "" {
|
||||
bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
|
||||
}
|
||||
|
||||
@ -2794,6 +2835,9 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
||||
if bp.Tracepoint {
|
||||
thing = "tracepoint"
|
||||
}
|
||||
if bp.WatchExpr != "" {
|
||||
thing = "watchpoint"
|
||||
}
|
||||
if upcase {
|
||||
thing = strings.Title(thing)
|
||||
}
|
||||
@ -2801,6 +2845,9 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
||||
if id == "" {
|
||||
id = strconv.Itoa(bp.ID)
|
||||
}
|
||||
if bp.WatchExpr != "" && bp.WatchExpr != bp.Name {
|
||||
return fmt.Sprintf("%s %s on [%s]", thing, id, bp.WatchExpr)
|
||||
}
|
||||
state := "(enabled)"
|
||||
if bp.Disabled {
|
||||
state = "(disabled)"
|
||||
@ -2822,11 +2869,13 @@ func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string {
|
||||
// In case we are connecting to an older version of delve that does not return the Addrs field.
|
||||
fmt.Fprintf(&out, "%#x", bp.Addr)
|
||||
}
|
||||
fmt.Fprintf(&out, " for ")
|
||||
p := t.formatPath(bp.File)
|
||||
if bp.FunctionName != "" {
|
||||
fmt.Fprintf(&out, "%s() ", bp.FunctionName)
|
||||
if bp.WatchExpr == "" {
|
||||
fmt.Fprintf(&out, " for ")
|
||||
p := t.formatPath(bp.File)
|
||||
if bp.FunctionName != "" {
|
||||
fmt.Fprintf(&out, "%s() ", bp.FunctionName)
|
||||
}
|
||||
fmt.Fprintf(&out, "%s:%d", p, bp.Line)
|
||||
}
|
||||
fmt.Fprintf(&out, "%s:%d", p, bp.Line)
|
||||
return out.String()
|
||||
}
|
||||
|
@ -313,6 +313,54 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["create_watchpoint"] = starlark.NewBuiltin("create_watchpoint", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
var rpcArgs rpc2.CreateWatchpointIn
|
||||
var rpcRet rpc2.CreateWatchpointOut
|
||||
if len(args) > 0 && args[0] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[0], &rpcArgs.Scope, "Scope")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
} else {
|
||||
rpcArgs.Scope = env.ctx.Scope()
|
||||
}
|
||||
if len(args) > 1 && args[1] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[1], &rpcArgs.Expr, "Expr")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
if len(args) > 2 && args[2] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[2], &rpcArgs.Type, "Type")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
case "Scope":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Scope, "Scope")
|
||||
case "Expr":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Expr, "Expr")
|
||||
case "Type":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Type, "Type")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
err := env.ctx.Client().CallAPI("CreateWatchpoint", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["detach"] = starlark.NewBuiltin("detach", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
|
@ -33,6 +33,8 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
||||
Variables: bp.Variables,
|
||||
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
|
||||
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
|
||||
WatchExpr: bp.WatchExpr,
|
||||
WatchType: WatchType(bp.WatchType),
|
||||
TotalHitCount: bp.TotalHitCount,
|
||||
Addrs: []uint64{bp.Addr},
|
||||
}
|
||||
|
@ -83,6 +83,11 @@ type Breakpoint struct {
|
||||
LoadArgs *LoadConfig
|
||||
// LoadLocals requests loading function locals when the breakpoint is hit
|
||||
LoadLocals *LoadConfig
|
||||
|
||||
// WatchExpr is the expression used to create this watchpoint
|
||||
WatchExpr string
|
||||
WatchType WatchType
|
||||
|
||||
// number of times a breakpoint has been reached in a certain goroutine
|
||||
HitCount map[string]uint64 `json:"hitCount"`
|
||||
// number of times a breakpoint has been reached
|
||||
@ -109,6 +114,14 @@ func ValidBreakpointName(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchType is the watchpoint type
|
||||
type WatchType uint8
|
||||
|
||||
const (
|
||||
WatchRead WatchType = 1 << iota
|
||||
WatchWrite
|
||||
)
|
||||
|
||||
// Thread is a thread within the debugged process.
|
||||
type Thread struct {
|
||||
// ID is a unique identifier for the thread.
|
||||
|
@ -66,6 +66,8 @@ type Client interface {
|
||||
GetBreakpointByName(name string) (*api.Breakpoint, error)
|
||||
// CreateBreakpoint creates a new breakpoint.
|
||||
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
|
||||
// CreateWatchpoint creates a new watchpoint.
|
||||
CreateWatchpoint(api.EvalScope, string, api.WatchType) (*api.Breakpoint, error)
|
||||
// ListBreakpoints gets all breakpoints.
|
||||
ListBreakpoints() ([]*api.Breakpoint, error)
|
||||
// ClearBreakpoint deletes a breakpoint by ID.
|
||||
|
@ -517,7 +517,9 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
if oldBp.ID > maxID {
|
||||
maxID = oldBp.ID
|
||||
}
|
||||
if len(oldBp.File) > 0 {
|
||||
if oldBp.WatchExpr != "" {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate watchpoints on restart"})
|
||||
} else if len(oldBp.File) > 0 {
|
||||
addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
|
||||
if err != nil {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
||||
@ -527,6 +529,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
} else {
|
||||
// Avoid setting a breakpoint based on address when rebuilding
|
||||
if rebuild {
|
||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate address breakpoints on restart"})
|
||||
continue
|
||||
}
|
||||
newBp, err := p.SetBreakpointWithID(oldBp.ID, oldBp.Addr)
|
||||
@ -730,6 +733,11 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
||||
defer d.targetMutex.Unlock()
|
||||
|
||||
originals := d.findBreakpoint(amend.ID)
|
||||
|
||||
if len(originals) > 0 && originals[0].WatchExpr != "" && amend.Disabled {
|
||||
return errors.New("can not disable watchpoints")
|
||||
}
|
||||
|
||||
_, disabled := d.disabledBreakpoints[amend.ID]
|
||||
if originals == nil && !disabled {
|
||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||
@ -938,6 +946,22 @@ func (d *Debugger) findDisabledBreakpointByName(name string) *api.Breakpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWatchpoint creates a watchpoint on the specified expression.
|
||||
func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
|
||||
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bp, err := d.target.SetWatchpoint(s, expr, proc.WatchType(wtype), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if api.ValidBreakpointName(expr) == nil && d.findBreakpointByName(expr) == nil {
|
||||
bp.Name = expr
|
||||
}
|
||||
return api.ConvertBreakpoint(bp), nil
|
||||
}
|
||||
|
||||
// Threads returns the threads of the target process.
|
||||
func (d *Debugger) Threads() ([]proc.Thread, error) {
|
||||
d.targetMutex.Lock()
|
||||
@ -1889,6 +1913,9 @@ func (v breakpointsByLogicalID) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
|
||||
func (v breakpointsByLogicalID) Less(i, j int) bool {
|
||||
if v[i].LogicalID == v[j].LogicalID {
|
||||
if v[i].WatchType != v[j].WatchType {
|
||||
return v[i].WatchType > v[j].WatchType // if a logical breakpoint contains a watchpoint let the watchpoint sort first
|
||||
}
|
||||
return v[i].Addr < v[j].Addr
|
||||
}
|
||||
return v[i].LogicalID < v[j].LogicalID
|
||||
|
@ -232,6 +232,12 @@ func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoin
|
||||
return &out.Breakpoint, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) CreateWatchpoint(scope api.EvalScope, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
|
||||
var out CreateWatchpointOut
|
||||
err := c.call("CreateWatchpoint", CreateWatchpointIn{scope, expr, wtype}, &out)
|
||||
return out.Breakpoint, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListBreakpoints() ([]*api.Breakpoint, error) {
|
||||
var out ListBreakpointsOut
|
||||
err := c.call("ListBreakpoints", ListBreakpointsIn{}, &out)
|
||||
|
@ -939,3 +939,19 @@ type DumpCancelOut struct {
|
||||
func (s *RPCServer) DumpCancel(arg DumpCancelIn, out *DumpCancelOut) error {
|
||||
return s.debugger.DumpCancel()
|
||||
}
|
||||
|
||||
type CreateWatchpointIn struct {
|
||||
Scope api.EvalScope
|
||||
Expr string
|
||||
Type api.WatchType
|
||||
}
|
||||
|
||||
type CreateWatchpointOut struct {
|
||||
*api.Breakpoint
|
||||
}
|
||||
|
||||
func (s *RPCServer) CreateWatchpoint(arg CreateWatchpointIn, out *CreateWatchpointOut) error {
|
||||
var err error
|
||||
out.Breakpoint, err = s.debugger.CreateWatchpoint(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, arg.Type)
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user