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.
|
[on](#on) | Executes a command when a breakpoint is hit.
|
||||||
[toggle](#toggle) | Toggles on or off a breakpoint.
|
[toggle](#toggle) | Toggles on or off a breakpoint.
|
||||||
[trace](#trace) | Set tracepoint.
|
[trace](#trace) | Set tracepoint.
|
||||||
|
[watch](#watch) | Set watchpoint.
|
||||||
|
|
||||||
|
|
||||||
## Viewing program variables and memory
|
## 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.
|
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
|
## whatis
|
||||||
Prints type of an expression.
|
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)
|
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)
|
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_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)
|
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)
|
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)
|
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
|
Name string // User defined name of the breakpoint
|
||||||
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
LogicalID int // ID of the logical breakpoint that owns this physical breakpoint
|
||||||
|
|
||||||
|
WatchExpr string
|
||||||
WatchType WatchType
|
WatchType WatchType
|
||||||
HWBreakIndex uint8 // hardware breakpoint index
|
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 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) {
|
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.
|
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"`},
|
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.
|
{aliases: []string{"restart", "r"}, group: runCmds, cmdFn: restart, helpMsg: `Restart process.
|
||||||
|
|
||||||
For recorded targets the command takes the following forms:
|
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()
|
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 {
|
func examineMemoryCmd(t *Term, ctx callContext, argstr string) error {
|
||||||
var (
|
var (
|
||||||
address uint64
|
address uint64
|
||||||
@ -2432,7 +2471,9 @@ func printcontextThread(t *Term, th *api.Thread) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bpname := ""
|
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)
|
bpname = fmt.Sprintf("[%s] ", th.Breakpoint.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2794,6 +2835,9 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
|||||||
if bp.Tracepoint {
|
if bp.Tracepoint {
|
||||||
thing = "tracepoint"
|
thing = "tracepoint"
|
||||||
}
|
}
|
||||||
|
if bp.WatchExpr != "" {
|
||||||
|
thing = "watchpoint"
|
||||||
|
}
|
||||||
if upcase {
|
if upcase {
|
||||||
thing = strings.Title(thing)
|
thing = strings.Title(thing)
|
||||||
}
|
}
|
||||||
@ -2801,6 +2845,9 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
id = strconv.Itoa(bp.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)"
|
state := "(enabled)"
|
||||||
if bp.Disabled {
|
if bp.Disabled {
|
||||||
state = "(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.
|
// 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, "%#x", bp.Addr)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(&out, " for ")
|
if bp.WatchExpr == "" {
|
||||||
p := t.formatPath(bp.File)
|
fmt.Fprintf(&out, " for ")
|
||||||
if bp.FunctionName != "" {
|
p := t.formatPath(bp.File)
|
||||||
fmt.Fprintf(&out, "%s() ", bp.FunctionName)
|
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()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
@ -313,6 +313,54 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
|||||||
}
|
}
|
||||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
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) {
|
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 {
|
if err := isCancelled(thread); err != nil {
|
||||||
return starlark.None, decorateError(thread, err)
|
return starlark.None, decorateError(thread, err)
|
||||||
|
@ -33,6 +33,8 @@ func ConvertBreakpoint(bp *proc.Breakpoint) *Breakpoint {
|
|||||||
Variables: bp.Variables,
|
Variables: bp.Variables,
|
||||||
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
|
LoadArgs: LoadConfigFromProc(bp.LoadArgs),
|
||||||
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
|
LoadLocals: LoadConfigFromProc(bp.LoadLocals),
|
||||||
|
WatchExpr: bp.WatchExpr,
|
||||||
|
WatchType: WatchType(bp.WatchType),
|
||||||
TotalHitCount: bp.TotalHitCount,
|
TotalHitCount: bp.TotalHitCount,
|
||||||
Addrs: []uint64{bp.Addr},
|
Addrs: []uint64{bp.Addr},
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,11 @@ type Breakpoint struct {
|
|||||||
LoadArgs *LoadConfig
|
LoadArgs *LoadConfig
|
||||||
// LoadLocals requests loading function locals when the breakpoint is hit
|
// LoadLocals requests loading function locals when the breakpoint is hit
|
||||||
LoadLocals *LoadConfig
|
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
|
// number of times a breakpoint has been reached in a certain goroutine
|
||||||
HitCount map[string]uint64 `json:"hitCount"`
|
HitCount map[string]uint64 `json:"hitCount"`
|
||||||
// number of times a breakpoint has been reached
|
// number of times a breakpoint has been reached
|
||||||
@ -109,6 +114,14 @@ func ValidBreakpointName(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WatchType is the watchpoint type
|
||||||
|
type WatchType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
WatchRead WatchType = 1 << iota
|
||||||
|
WatchWrite
|
||||||
|
)
|
||||||
|
|
||||||
// Thread is a thread within the debugged process.
|
// Thread is a thread within the debugged process.
|
||||||
type Thread struct {
|
type Thread struct {
|
||||||
// ID is a unique identifier for the thread.
|
// ID is a unique identifier for the thread.
|
||||||
|
@ -66,6 +66,8 @@ type Client interface {
|
|||||||
GetBreakpointByName(name string) (*api.Breakpoint, error)
|
GetBreakpointByName(name string) (*api.Breakpoint, error)
|
||||||
// CreateBreakpoint creates a new breakpoint.
|
// CreateBreakpoint creates a new breakpoint.
|
||||||
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
|
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 gets all breakpoints.
|
||||||
ListBreakpoints() ([]*api.Breakpoint, error)
|
ListBreakpoints() ([]*api.Breakpoint, error)
|
||||||
// ClearBreakpoint deletes a breakpoint by ID.
|
// 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 {
|
if oldBp.ID > maxID {
|
||||||
maxID = oldBp.ID
|
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)
|
addrs, err := proc.FindFileLocation(p, oldBp.File, oldBp.Line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
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 {
|
} else {
|
||||||
// Avoid setting a breakpoint based on address when rebuilding
|
// Avoid setting a breakpoint based on address when rebuilding
|
||||||
if rebuild {
|
if rebuild {
|
||||||
|
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: "can not recreate address breakpoints on restart"})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newBp, err := p.SetBreakpointWithID(oldBp.ID, oldBp.Addr)
|
newBp, err := p.SetBreakpointWithID(oldBp.ID, oldBp.Addr)
|
||||||
@ -730,6 +733,11 @@ func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
|||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
originals := d.findBreakpoint(amend.ID)
|
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]
|
_, disabled := d.disabledBreakpoints[amend.ID]
|
||||||
if originals == nil && !disabled {
|
if originals == nil && !disabled {
|
||||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||||
@ -938,6 +946,22 @@ func (d *Debugger) findDisabledBreakpointByName(name string) *api.Breakpoint {
|
|||||||
return nil
|
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.
|
// Threads returns the threads of the target process.
|
||||||
func (d *Debugger) Threads() ([]proc.Thread, error) {
|
func (d *Debugger) Threads() ([]proc.Thread, error) {
|
||||||
d.targetMutex.Lock()
|
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 {
|
func (v breakpointsByLogicalID) Less(i, j int) bool {
|
||||||
if v[i].LogicalID == v[j].LogicalID {
|
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].Addr < v[j].Addr
|
||||||
}
|
}
|
||||||
return v[i].LogicalID < v[j].LogicalID
|
return v[i].LogicalID < v[j].LogicalID
|
||||||
|
@ -232,6 +232,12 @@ func (c *RPCClient) CreateBreakpoint(breakPoint *api.Breakpoint) (*api.Breakpoin
|
|||||||
return &out.Breakpoint, err
|
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) {
|
func (c *RPCClient) ListBreakpoints() ([]*api.Breakpoint, error) {
|
||||||
var out ListBreakpointsOut
|
var out ListBreakpointsOut
|
||||||
err := c.call("ListBreakpoints", ListBreakpointsIn{}, &out)
|
err := c.call("ListBreakpoints", ListBreakpointsIn{}, &out)
|
||||||
|
@ -939,3 +939,19 @@ type DumpCancelOut struct {
|
|||||||
func (s *RPCServer) DumpCancel(arg DumpCancelIn, out *DumpCancelOut) error {
|
func (s *RPCServer) DumpCancel(arg DumpCancelIn, out *DumpCancelOut) error {
|
||||||
return s.debugger.DumpCancel()
|
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