*: Adds toggle command (#2208)
* Adds toggle command Also adds two rpc2 tests for testing the new functionality * Removes Debuggers' ToggleBreakpoint method rpc2's ToggleBreakpoint now calls AmendBreakpoint Refactors the ClearBreakpoint to avoid a lock.
This commit is contained in:
parent
333e84a0cb
commit
f5d2e132bc
@ -32,6 +32,7 @@ Command | Description
|
|||||||
[clearall](#clearall) | Deletes multiple breakpoints.
|
[clearall](#clearall) | Deletes multiple breakpoints.
|
||||||
[condition](#condition) | Set breakpoint condition.
|
[condition](#condition) | Set breakpoint condition.
|
||||||
[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.
|
||||||
[trace](#trace) | Set tracepoint.
|
[trace](#trace) | Set tracepoint.
|
||||||
|
|
||||||
|
|
||||||
@ -531,6 +532,12 @@ Aliases: tr
|
|||||||
Print out info for every traced thread.
|
Print out info for every traced thread.
|
||||||
|
|
||||||
|
|
||||||
|
## toggle
|
||||||
|
Toggles on or off a breakpoint.
|
||||||
|
|
||||||
|
toggle <breakpoint name or id>
|
||||||
|
|
||||||
|
|
||||||
## trace
|
## trace
|
||||||
Set tracepoint.
|
Set tracepoint.
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild, NewRedirects) | Equival
|
|||||||
set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set)
|
set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set)
|
||||||
stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace)
|
stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace)
|
||||||
state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State)
|
state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State)
|
||||||
|
toggle_breakpoint(Id, Name) | Equivalent to API call [ToggleBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ToggleBreakpoint)
|
||||||
dlv_command(command) | Executes the specified command as if typed at the dlv_prompt
|
dlv_command(command) | Executes the specified command as if typed at the dlv_prompt
|
||||||
read_file(path) | Reads the file as a string
|
read_file(path) | Reads the file as a string
|
||||||
write_file(path, contents) | Writes string to a file
|
write_file(path, contents) | Writes string to a file
|
||||||
|
23
_fixtures/testtoggle.go
Normal file
23
_fixtures/testtoggle.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lineOne() {
|
||||||
|
fmt.Println("lineOne function")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lineTwo() {
|
||||||
|
fmt.Println("lineTwo function")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lineThree() {
|
||||||
|
fmt.Println("lineThree function")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
lineOne()
|
||||||
|
lineTwo()
|
||||||
|
lineThree()
|
||||||
|
}
|
@ -299,8 +299,8 @@ func (t *Target) SetBreakpoint(addr uint64, kind BreakpointKind, cond ast.Expr)
|
|||||||
return newBreakpoint, nil
|
return newBreakpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setBreakpointWithID creates a breakpoint at addr, with the specified logical ID.
|
// SetBreakpointWithID creates a breakpoint at addr, with the specified logical ID.
|
||||||
func (t *Target) setBreakpointWithID(id int, addr uint64) (*Breakpoint, error) {
|
func (t *Target) SetBreakpointWithID(id int, addr uint64) (*Breakpoint, error) {
|
||||||
bpmap := t.Breakpoints()
|
bpmap := t.Breakpoints()
|
||||||
bp, err := t.SetBreakpoint(addr, UserBreakpoint, nil)
|
bp, err := t.SetBreakpoint(addr, UserBreakpoint, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -334,7 +334,7 @@ func (t *Target) createUnrecoveredPanicBreakpoint() {
|
|||||||
panicpcs, err = FindFunctionLocation(t.Process, "runtime.fatalpanic", 0)
|
panicpcs, err = FindFunctionLocation(t.Process, "runtime.fatalpanic", 0)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp, err := t.setBreakpointWithID(unrecoveredPanicID, panicpcs[0])
|
bp, err := t.SetBreakpointWithID(unrecoveredPanicID, panicpcs[0])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp.Name = UnrecoveredPanic
|
bp.Name = UnrecoveredPanic
|
||||||
bp.Variables = []string{"runtime.curg._panic.arg"}
|
bp.Variables = []string{"runtime.curg._panic.arg"}
|
||||||
@ -346,7 +346,7 @@ func (t *Target) createUnrecoveredPanicBreakpoint() {
|
|||||||
func (t *Target) createFatalThrowBreakpoint() {
|
func (t *Target) createFatalThrowBreakpoint() {
|
||||||
fatalpcs, err := FindFunctionLocation(t.Process, "runtime.fatalthrow", 0)
|
fatalpcs, err := FindFunctionLocation(t.Process, "runtime.fatalthrow", 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp, err := t.setBreakpointWithID(fatalThrowID, fatalpcs[0])
|
bp, err := t.SetBreakpointWithID(fatalThrowID, fatalpcs[0])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bp.Name = FatalThrow
|
bp.Name = FatalThrow
|
||||||
}
|
}
|
||||||
|
@ -200,6 +200,9 @@ Current limitations:
|
|||||||
clearall [<linespec>]
|
clearall [<linespec>]
|
||||||
|
|
||||||
If called with the linespec argument it will delete all the breakpoints matching the linespec. If linespec is omitted all breakpoints are deleted.`},
|
If called with the linespec argument it will delete all the breakpoints matching the linespec. If linespec is omitted all breakpoints are deleted.`},
|
||||||
|
{aliases: []string{"toggle"}, group: breakCmds, cmdFn: toggle, helpMsg: `Toggles on or off a breakpoint.
|
||||||
|
|
||||||
|
toggle <breakpoint name or id>`},
|
||||||
{aliases: []string{"goroutines", "grs"}, group: goroutineCmds, cmdFn: goroutines, helpMsg: `List program goroutines.
|
{aliases: []string{"goroutines", "grs"}, group: goroutineCmds, cmdFn: goroutines, helpMsg: `List program goroutines.
|
||||||
|
|
||||||
goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)|-s (start location)] [-t (stack trace)] [-l (labels)]
|
goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)|-s (start location)] [-t (stack trace)] [-l (labels)]
|
||||||
@ -1470,6 +1473,24 @@ func clearAll(t *Term, ctx callContext, args string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toggle(t *Term, ctx callContext, args string) error {
|
||||||
|
if args == "" {
|
||||||
|
return fmt.Errorf("not enough arguments")
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(args)
|
||||||
|
var bp *api.Breakpoint
|
||||||
|
if err == nil {
|
||||||
|
bp, err = t.client.ToggleBreakpoint(id)
|
||||||
|
} else {
|
||||||
|
bp, err = t.client.ToggleBreakpointByName(args)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%s toggled at %s\n", formatBreakpointName(bp, true), t.formatBreakpointLocation(bp))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// byID sorts breakpoints by ID.
|
// byID sorts breakpoints by ID.
|
||||||
type byID []*api.Breakpoint
|
type byID []*api.Breakpoint
|
||||||
|
|
||||||
@ -2708,7 +2729,11 @@ func formatBreakpointName(bp *api.Breakpoint, upcase bool) string {
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
id = strconv.Itoa(bp.ID)
|
id = strconv.Itoa(bp.ID)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s %s", thing, id)
|
state := "(enabled)"
|
||||||
|
if bp.Disabled {
|
||||||
|
state = "(disabled)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s %s", thing, id, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string {
|
func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string {
|
||||||
|
@ -1359,5 +1359,43 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
|||||||
}
|
}
|
||||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||||
})
|
})
|
||||||
|
r["toggle_breakpoint"] = starlark.NewBuiltin("toggle_breakpoint", 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.ToggleBreakpointIn
|
||||||
|
var rpcRet rpc2.ToggleBreakpointOut
|
||||||
|
if len(args) > 0 && args[0] != starlark.None {
|
||||||
|
err := unmarshalStarlarkValue(args[0], &rpcArgs.Id, "Id")
|
||||||
|
if err != nil {
|
||||||
|
return starlark.None, decorateError(thread, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 1 && args[1] != starlark.None {
|
||||||
|
err := unmarshalStarlarkValue(args[1], &rpcArgs.Name, "Name")
|
||||||
|
if err != nil {
|
||||||
|
return starlark.None, decorateError(thread, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, kv := range kwargs {
|
||||||
|
var err error
|
||||||
|
switch kv[0].(starlark.String) {
|
||||||
|
case "Id":
|
||||||
|
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Id, "Id")
|
||||||
|
case "Name":
|
||||||
|
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Name, "Name")
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return starlark.None, decorateError(thread, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := env.ctx.Client().CallAPI("ToggleBreakpoint", &rpcArgs, &rpcRet)
|
||||||
|
if err != nil {
|
||||||
|
return starlark.None, err
|
||||||
|
}
|
||||||
|
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||||
|
})
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,8 @@ type Breakpoint struct {
|
|||||||
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
|
||||||
TotalHitCount uint64 `json:"totalHitCount"`
|
TotalHitCount uint64 `json:"totalHitCount"`
|
||||||
|
// Disabled flag, signifying the state of the breakpoint
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidBreakpointName returns an error if
|
// ValidBreakpointName returns an error if
|
||||||
|
@ -72,6 +72,10 @@ type Client interface {
|
|||||||
ClearBreakpoint(id int) (*api.Breakpoint, error)
|
ClearBreakpoint(id int) (*api.Breakpoint, error)
|
||||||
// ClearBreakpointByName deletes a breakpoint by name
|
// ClearBreakpointByName deletes a breakpoint by name
|
||||||
ClearBreakpointByName(name string) (*api.Breakpoint, error)
|
ClearBreakpointByName(name string) (*api.Breakpoint, error)
|
||||||
|
// ToggleBreakpoint toggles on or off a breakpoint by ID.
|
||||||
|
ToggleBreakpoint(id int) (*api.Breakpoint, error)
|
||||||
|
// ToggleBreakpointByName toggles on or off a breakpoint by name.
|
||||||
|
ToggleBreakpointByName(name string) (*api.Breakpoint, error)
|
||||||
// Allows user to update an existing breakpoint for example to change the information
|
// Allows user to update an existing breakpoint for example to change the information
|
||||||
// retrieved when the breakpoint is hit or to change, add or remove the break condition
|
// retrieved when the breakpoint is hit or to change, add or remove the break condition
|
||||||
AmendBreakpoint(*api.Breakpoint) error
|
AmendBreakpoint(*api.Breakpoint) error
|
||||||
|
@ -70,6 +70,10 @@ type Debugger struct {
|
|||||||
recordMutex sync.Mutex
|
recordMutex sync.Mutex
|
||||||
|
|
||||||
dumpState proc.DumpState
|
dumpState proc.DumpState
|
||||||
|
// Debugger keeps a map of disabled breakpoints
|
||||||
|
// so lower layers like proc doesn't need to deal
|
||||||
|
// with them
|
||||||
|
disabledBreakpoints map[int]*api.Breakpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExecuteKind int
|
type ExecuteKind int
|
||||||
@ -198,6 +202,9 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.disabledBreakpoints = make(map[int]*api.Breakpoint)
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +509,9 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
}
|
}
|
||||||
|
|
||||||
discarded := []api.DiscardedBreakpoint{}
|
discarded := []api.DiscardedBreakpoint{}
|
||||||
for _, oldBp := range api.ConvertBreakpoints(d.breakpoints()) {
|
breakpoints := api.ConvertBreakpoints(d.breakpoints())
|
||||||
|
d.target = p
|
||||||
|
for _, oldBp := range breakpoints {
|
||||||
if oldBp.ID < 0 {
|
if oldBp.ID < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -512,7 +521,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: oldBp, Reason: err.Error()})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
createLogicalBreakpoint(p, addrs, oldBp)
|
createLogicalBreakpoint(d, addrs, oldBp)
|
||||||
} else {
|
} else {
|
||||||
// Avoid setting a breakpoint based on address when rebuilding
|
// Avoid setting a breakpoint based on address when rebuilding
|
||||||
if rebuild {
|
if rebuild {
|
||||||
@ -527,7 +536,6 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.target = p
|
|
||||||
return discarded, nil
|
return discarded, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,7 +621,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
if err = api.ValidBreakpointName(requestedBp.Name); err != nil {
|
if err = api.ValidBreakpointName(requestedBp.Name); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if d.findBreakpointByName(requestedBp.Name) != nil {
|
if (d.findBreakpointByName(requestedBp.Name) != nil) || (d.findDisabledBreakpointByName(requestedBp.Name) != nil) {
|
||||||
return nil, errors.New("breakpoint name already exists")
|
return nil, errors.New("breakpoint name already exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -646,7 +654,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
createdBp, err := createLogicalBreakpoint(d.target, addrs, requestedBp)
|
createdBp, err := createLogicalBreakpoint(d, addrs, requestedBp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -656,7 +664,13 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
|
|||||||
|
|
||||||
// createLogicalBreakpoint creates one physical breakpoint for each address
|
// createLogicalBreakpoint creates one physical breakpoint for each address
|
||||||
// in addrs and associates all of them with the same logical breakpoint.
|
// in addrs and associates all of them with the same logical breakpoint.
|
||||||
func createLogicalBreakpoint(p *proc.Target, addrs []uint64, requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||||
|
p := d.target
|
||||||
|
|
||||||
|
if dbp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
|
||||||
|
return dbp, proc.BreakpointExistsError{File: dbp.File, Line: dbp.Line, Addr: dbp.Addr}
|
||||||
|
}
|
||||||
|
|
||||||
bps := make([]*proc.Breakpoint, len(addrs))
|
bps := make([]*proc.Breakpoint, len(addrs))
|
||||||
var err error
|
var err error
|
||||||
for i := range addrs {
|
for i := range addrs {
|
||||||
@ -687,6 +701,7 @@ func createLogicalBreakpoint(p *proc.Target, addrs []uint64, requestedBp *api.Br
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
createdBp := api.ConvertBreakpoints(bps)
|
createdBp := api.ConvertBreakpoints(bps)
|
||||||
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1
|
return createdBp[0], nil // we created a single logical breakpoint, the slice here will always have len == 1
|
||||||
}
|
}
|
||||||
@ -697,22 +712,39 @@ func isBreakpointExistsErr(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AmendBreakpoint will update the breakpoint with the matching ID.
|
// AmendBreakpoint will update the breakpoint with the matching ID.
|
||||||
|
// It also enables or disables the breakpoint.
|
||||||
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
func (d *Debugger) AmendBreakpoint(amend *api.Breakpoint) error {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
|
||||||
originals := d.findBreakpoint(amend.ID)
|
originals := d.findBreakpoint(amend.ID)
|
||||||
if originals == nil {
|
_, disabled := d.disabledBreakpoints[amend.ID]
|
||||||
|
if originals == nil && !disabled {
|
||||||
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
|
||||||
}
|
}
|
||||||
if err := api.ValidBreakpointName(amend.Name); err != nil {
|
if err := api.ValidBreakpointName(amend.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !amend.Disabled && disabled { // enable the breakpoint
|
||||||
|
bp, err := d.target.SetBreakpointWithID(amend.ID, amend.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copyBreakpointInfo(bp, amend)
|
||||||
|
delete(d.disabledBreakpoints, amend.ID)
|
||||||
|
}
|
||||||
|
if amend.Disabled && !disabled { // disable the breakpoint
|
||||||
|
if _, err := d.clearBreakpoint(amend); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.disabledBreakpoints[amend.ID] = amend
|
||||||
|
}
|
||||||
for _, original := range originals {
|
for _, original := range originals {
|
||||||
if err := copyBreakpointInfo(original, amend); err != nil {
|
if err := copyBreakpointInfo(original, amend); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -744,6 +776,15 @@ func copyBreakpointInfo(bp *proc.Breakpoint, requested *api.Breakpoint) (err err
|
|||||||
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
|
return d.clearBreakpoint(requestedBp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
|
||||||
|
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
|
||||||
|
if bp, ok := d.disabledBreakpoints[requestedBp.ID]; ok {
|
||||||
|
delete(d.disabledBreakpoints, bp.ID)
|
||||||
|
return bp, nil
|
||||||
|
}
|
||||||
|
|
||||||
var bps []*proc.Breakpoint
|
var bps []*proc.Breakpoint
|
||||||
var errs []error
|
var errs []error
|
||||||
@ -796,7 +837,14 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
|
|||||||
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
func (d *Debugger) Breakpoints() []*api.Breakpoint {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return api.ConvertBreakpoints(d.breakpoints())
|
|
||||||
|
bps := api.ConvertBreakpoints(d.breakpoints())
|
||||||
|
|
||||||
|
for _, bp := range d.disabledBreakpoints {
|
||||||
|
bps = append(bps, bp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) breakpoints() []*proc.Breakpoint {
|
func (d *Debugger) breakpoints() []*proc.Breakpoint {
|
||||||
@ -815,6 +863,7 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
|
|||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
bps := api.ConvertBreakpoints(d.findBreakpoint(id))
|
bps := api.ConvertBreakpoints(d.findBreakpoint(id))
|
||||||
|
bps = append(bps, d.findDisabledBreakpoint(id)...)
|
||||||
if len(bps) <= 0 {
|
if len(bps) <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -831,11 +880,26 @@ func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
|
|||||||
return bps
|
return bps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Debugger) findDisabledBreakpoint(id int) []*api.Breakpoint {
|
||||||
|
var bps []*api.Breakpoint
|
||||||
|
for _, dbp := range d.disabledBreakpoints {
|
||||||
|
if dbp.ID == id {
|
||||||
|
bps = append(bps, dbp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bps
|
||||||
|
}
|
||||||
|
|
||||||
// FindBreakpointByName returns the breakpoint specified by 'name'
|
// FindBreakpointByName returns the breakpoint specified by 'name'
|
||||||
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
|
||||||
d.targetMutex.Lock()
|
d.targetMutex.Lock()
|
||||||
defer d.targetMutex.Unlock()
|
defer d.targetMutex.Unlock()
|
||||||
return d.findBreakpointByName(name)
|
|
||||||
|
bp := d.findBreakpointByName(name)
|
||||||
|
if bp == nil {
|
||||||
|
bp = d.findDisabledBreakpointByName(name)
|
||||||
|
}
|
||||||
|
return bp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
||||||
@ -853,6 +917,15 @@ func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
|
|||||||
return r[0] // there can only be one logical breakpoint with the same name
|
return r[0] // there can only be one logical breakpoint with the same name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Debugger) findDisabledBreakpointByName(name string) *api.Breakpoint {
|
||||||
|
for _, dbp := range d.disabledBreakpoints {
|
||||||
|
if dbp.Name == name {
|
||||||
|
return dbp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 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()
|
||||||
|
@ -250,6 +250,18 @@ func (c *RPCClient) ClearBreakpointByName(name string) (*api.Breakpoint, error)
|
|||||||
return out.Breakpoint, err
|
return out.Breakpoint, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ToggleBreakpoint(id int) (*api.Breakpoint, error) {
|
||||||
|
var out ToggleBreakpointOut
|
||||||
|
err := c.call("ToggleBreakpoint", ToggleBreakpointIn{id, ""}, &out)
|
||||||
|
return out.Breakpoint, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) ToggleBreakpointByName(name string) (*api.Breakpoint, error) {
|
||||||
|
var out ToggleBreakpointOut
|
||||||
|
err := c.call("ToggleBreakpoint", ToggleBreakpointIn{0, name}, &out)
|
||||||
|
return out.Breakpoint, err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error {
|
func (c *RPCClient) AmendBreakpoint(bp *api.Breakpoint) error {
|
||||||
out := new(AmendBreakpointOut)
|
out := new(AmendBreakpointOut)
|
||||||
err := c.call("AmendBreakpoint", AmendBreakpointIn{*bp}, out)
|
err := c.call("AmendBreakpoint", AmendBreakpointIn{*bp}, out)
|
||||||
|
@ -294,6 +294,38 @@ func (s *RPCServer) ClearBreakpoint(arg ClearBreakpointIn, out *ClearBreakpointO
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ToggleBreakpointIn struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToggleBreakpointOut struct {
|
||||||
|
Breakpoint *api.Breakpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleBreakpoint toggles on or off a breakpoint by Name (if Name is not an
|
||||||
|
// empty string) or by ID.
|
||||||
|
func (s *RPCServer) ToggleBreakpoint(arg ToggleBreakpointIn, out *ToggleBreakpointOut) error {
|
||||||
|
var bp *api.Breakpoint
|
||||||
|
if arg.Name != "" {
|
||||||
|
bp = s.debugger.FindBreakpointByName(arg.Name)
|
||||||
|
if bp == nil {
|
||||||
|
return fmt.Errorf("no breakpoint with name %s", arg.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bp = s.debugger.FindBreakpoint(arg.Id)
|
||||||
|
if bp == nil {
|
||||||
|
return fmt.Errorf("no breakpoint with id %d", arg.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bp.Disabled = !bp.Disabled
|
||||||
|
if err := s.debugger.AmendBreakpoint(bp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Breakpoint = bp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type AmendBreakpointIn struct {
|
type AmendBreakpointIn struct {
|
||||||
Breakpoint api.Breakpoint
|
Breakpoint api.Breakpoint
|
||||||
}
|
}
|
||||||
|
@ -507,6 +507,113 @@ func TestClientServer_clearBreakpoint(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientServer_toggleBreakpoint(t *testing.T) {
|
||||||
|
withTestClient2("testtoggle", t, func(c service.Client) {
|
||||||
|
toggle := func(bp *api.Breakpoint) {
|
||||||
|
dbp, err := c.ToggleBreakpoint(bp.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if dbp.ID != bp.ID {
|
||||||
|
t.Fatalf("The IDs don't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This one is toggled twice
|
||||||
|
bp1, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.lineOne", Tracepoint: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(bp1)
|
||||||
|
toggle(bp1)
|
||||||
|
|
||||||
|
// This one is toggled once
|
||||||
|
bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.lineTwo", Tracepoint: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(bp2)
|
||||||
|
|
||||||
|
// This one is never toggled
|
||||||
|
bp3, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.lineThree", Tracepoint: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, a := 3, countBreakpoints(t, c); e != a {
|
||||||
|
t.Fatalf("Expected breakpoint count %d, got %d", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableCount := 0
|
||||||
|
disabledCount := 0
|
||||||
|
|
||||||
|
contChan := c.Continue()
|
||||||
|
for state := range contChan {
|
||||||
|
if state.CurrentThread != nil && state.CurrentThread.Breakpoint != nil {
|
||||||
|
switch state.CurrentThread.Breakpoint.ID {
|
||||||
|
case bp1.ID, bp3.ID:
|
||||||
|
enableCount++
|
||||||
|
case bp2.ID:
|
||||||
|
disabledCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%v", state)
|
||||||
|
}
|
||||||
|
if state.Exited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if state.Err != nil {
|
||||||
|
t.Fatalf("Unexpected error during continue: %v\n", state.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enableCount != 2 {
|
||||||
|
t.Fatalf("Wrong number of enabled hits: %d\n", enableCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if disabledCount != 0 {
|
||||||
|
t.Fatalf("A disabled breakpoint was hit: %d\n", disabledCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientServer_toggleAmendedBreakpoint(t *testing.T) {
|
||||||
|
withTestClient2("testtoggle", t, func(c service.Client) {
|
||||||
|
toggle := func(bp *api.Breakpoint) {
|
||||||
|
dbp, err := c.ToggleBreakpoint(bp.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if dbp.ID != bp.ID {
|
||||||
|
t.Fatalf("The IDs don't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This one is toggled twice
|
||||||
|
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.lineOne", Tracepoint: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
|
}
|
||||||
|
bp.Cond = "n == 7"
|
||||||
|
assertNoError(c.AmendBreakpoint(bp), t, "AmendBreakpoint() 1")
|
||||||
|
|
||||||
|
// Toggle off.
|
||||||
|
toggle(bp)
|
||||||
|
// Toggle on.
|
||||||
|
toggle(bp)
|
||||||
|
|
||||||
|
amended, err := c.GetBreakpoint(bp.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if amended.Cond == "" {
|
||||||
|
t.Fatal("breakpoint amendedments not preserved after toggle")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientServer_switchThread(t *testing.T) {
|
func TestClientServer_switchThread(t *testing.T) {
|
||||||
protest.AllowRecording(t)
|
protest.AllowRecording(t)
|
||||||
withTestClient2("testnextprog", t, func(c service.Client) {
|
withTestClient2("testnextprog", t, func(c service.Client) {
|
||||||
|
Loading…
Reference in New Issue
Block a user