Skip to content

Commit

Permalink
proc,service/debugger: track how breakpoints were originally set (go-…
Browse files Browse the repository at this point in the history
…delve#3148)

Adds field to breakpoint struct to track how a breakpoint was
originally set, moves the logic for disabling and enabling a breakpoint
to proc.
This will allow creating suspended breakpoints that are automatically
enabled when a plugin is loaded. When follow exec mode is implemented
it will also be possible to automatically enable breakpoints (whether
or not they were suspended) on new child processes, as they are
spawned.

It also improves breakpoint restore after a restart, before this after
a restart breakpoints would be re-enabled using their file:line
position, for breakpoints set using a function name or a location
expression this could be the wrong location after a recompile.

Updates go-delve#1653
Updates go-delve#2551
  • Loading branch information
aarzilli authored Sep 28, 2022
1 parent a73eaef commit ec5fcc0
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 189 deletions.
2 changes: 1 addition & 1 deletion Documentation/cli/starlark.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ checkpoint(Where) | Equivalent to API call [Checkpoint](https://godoc.org/github
clear_breakpoint(Id, Name) | Equivalent to API call [ClearBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ClearBreakpoint)
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_breakpoint(Breakpoint, LocExpr, SubstitutePathRules) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint)
create_ebpf_tracepoint(FunctionName) | Equivalent to API call [CreateEBPFTracepoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateEBPFTracepoint)
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)
Expand Down
16 changes: 16 additions & 0 deletions _fixtures/testfnpos1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import "fmt"

func f1() {
fmt.Printf("f1\n")
}

func f2() {
fmt.Printf("f2\n")
}

func main() {
f1()
f2()
}
16 changes: 16 additions & 0 deletions _fixtures/testfnpos2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import "fmt"

func f2() {
fmt.Printf("f2\n")
}

func f1() {
fmt.Printf("f1\n")
}

func main() {
f1()
f2()
}
16 changes: 16 additions & 0 deletions pkg/proc/breakpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,8 @@ type LogicalBreakpoint struct {
Line int
Enabled bool

Set SetBreakpoint

Tracepoint bool // Tracepoint flag
TraceReturn bool
Goroutine bool // Retrieve goroutine information
Expand All @@ -990,3 +992,17 @@ type LogicalBreakpoint struct {

UserData interface{} // Any additional information about the breakpoint
}

// SetBreakpoint describes how a breakpoint should be set.
type SetBreakpoint struct {
FunctionName string
File string
Line int
Expr func(*Target) []uint64
PidAddrs []PidAddr
}

type PidAddr struct {
Pid int
Addr uint64
}
146 changes: 146 additions & 0 deletions pkg/proc/target_group.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package proc

import (
"bytes"
"fmt"
"strings"
)
Expand Down Expand Up @@ -47,6 +48,32 @@ func NewGroup(t *Target) *TargetGroup {
}
}

// NewGroupRestart creates a new group of targets containing t and
// sets breakpoints and other attributes from oldgrp.
// Breakpoints that can not be set will be discarded, if discard is not nil
// it will be called for each discarded breakpoint.
func NewGroupRestart(t *Target, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) *TargetGroup {
grp := NewGroup(t)
grp.LogicalBreakpoints = oldgrp.LogicalBreakpoints
t.Breakpoints().Logical = grp.LogicalBreakpoints
for _, bp := range grp.LogicalBreakpoints {
if bp.LogicalID < 0 || !bp.Enabled {
continue
}
bp.TotalHitCount = 0
bp.HitCount = make(map[int64]uint64)
bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart
err := grp.EnableBreakpoint(bp)
if err != nil {
if discard != nil {
discard(bp, err)
}
delete(grp.LogicalBreakpoints, bp.LogicalID)
}
}
return grp
}

// Targets returns a slice of all targets in the group, including the
// ones that are no longer valid.
func (grp *TargetGroup) Targets() []*Target {
Expand Down Expand Up @@ -128,6 +155,125 @@ func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
return nil
}

// EnableBreakpoint re-enables a disabled logical breakpoint.
func (grp *TargetGroup) EnableBreakpoint(lbp *LogicalBreakpoint) error {
var err0, errNotFound, errExists error
didSet := false
targetLoop:
for _, p := range grp.targets {
err := enableBreakpointOnTarget(p, lbp)

switch err.(type) {
case nil:
didSet = true
case *ErrFunctionNotFound, *ErrCouldNotFindLine:
errNotFound = err
case BreakpointExistsError:
errExists = err
default:
err0 = err
break targetLoop
}
}
if errNotFound != nil && !didSet {
return errNotFound
}
if errExists != nil && !didSet {
return errExists
}
if !didSet {
if _, err := grp.Valid(); err != nil {
return err
}
}
if err0 != nil {
it := ValidTargets{Group: grp}
for it.Next() {
for _, bp := range it.Breakpoints().M {
if bp.LogicalID() == lbp.LogicalID {
if err1 := it.ClearBreakpoint(bp.Addr); err1 != nil {
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err0, err1)
}
}
}
}
return err0
}
lbp.Enabled = true
return nil
}

func enableBreakpointOnTarget(p *Target, lbp *LogicalBreakpoint) error {
var err error
var addrs []uint64
switch {
case lbp.Set.File != "":
addrs, err = FindFileLocation(p, lbp.Set.File, lbp.Set.Line)
case lbp.Set.FunctionName != "":
addrs, err = FindFunctionLocation(p, lbp.Set.FunctionName, lbp.Set.Line)
case lbp.Set.Expr != nil:
addrs = lbp.Set.Expr(p)
case len(lbp.Set.PidAddrs) > 0:
for _, pidAddr := range lbp.Set.PidAddrs {
if pidAddr.Pid == p.Pid() {
addrs = append(addrs, pidAddr.Addr)
}
}
default:
return fmt.Errorf("breakpoint %d can not be enabled", lbp.LogicalID)
}

if err != nil {
return err
}

for _, addr := range addrs {
_, err = p.SetBreakpoint(lbp.LogicalID, addr, UserBreakpoint, nil)
if err != nil {
if _, isexists := err.(BreakpointExistsError); isexists {
continue
}
return err
}
}

return err
}

// DisableBreakpoint disables a logical breakpoint.
func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error {
var errs []error
n := 0
it := ValidTargets{Group: grp}
for it.Next() {
for _, bp := range it.Breakpoints().M {
if bp.LogicalID() == lbp.LogicalID {
n++
err := it.ClearBreakpoint(bp.Addr)
if err != nil {
errs = append(errs, err)
}
}
}
}
if len(errs) > 0 {
buf := new(bytes.Buffer)
for i, err := range errs {
fmt.Fprintf(buf, "%s", err)
if i != len(errs)-1 {
fmt.Fprintf(buf, ", ")
}
}

if len(errs) == n {
return fmt.Errorf("unable to clear breakpoint %d: %v", lbp.LogicalID, buf.String())
}
return fmt.Errorf("unable to clear breakpoint %d (partial): %s", lbp.LogicalID, buf.String())
}
lbp.Enabled = false
return nil
}

// ValidTargets iterates through all valid targets in Group.
type ValidTargets struct {
*Target
Expand Down
2 changes: 1 addition & 1 deletion pkg/terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -1763,7 +1763,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]
requestedBp.LoadArgs = &ShortLoadConfig
}

bp, err := t.client.CreateBreakpoint(requestedBp)
bp, err := t.client.CreateBreakpointWithExpr(requestedBp, spec, t.substitutePathRules())
if err != nil {
return nil, err
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/terminal/starbind/starlark_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,27 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
return starlark.None, decorateError(thread, err)
}
}
if len(args) > 1 && args[1] != starlark.None {
err := unmarshalStarlarkValue(args[1], &rpcArgs.LocExpr, "LocExpr")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
if len(args) > 2 && args[2] != starlark.None {
err := unmarshalStarlarkValue(args[2], &rpcArgs.SubstitutePathRules, "SubstitutePathRules")
if err != nil {
return starlark.None, decorateError(thread, err)
}
}
for _, kv := range kwargs {
var err error
switch kv[0].(starlark.String) {
case "Breakpoint":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Breakpoint, "Breakpoint")
case "LocExpr":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.LocExpr, "LocExpr")
case "SubstitutePathRules":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.SubstitutePathRules, "SubstitutePathRules")
default:
err = fmt.Errorf("unknown argument %q", kv[0])
}
Expand Down
2 changes: 2 additions & 0 deletions service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Client interface {
GetBreakpointByName(name string) (*api.Breakpoint, error)
// CreateBreakpoint creates a new breakpoint.
CreateBreakpoint(*api.Breakpoint) (*api.Breakpoint, error)
// CreateBreakpointWithExpr creates a new breakpoint and sets an expression to restore it after it is disabled.
CreateBreakpointWithExpr(*api.Breakpoint, string, [][2]string) (*api.Breakpoint, error)
// CreateWatchpoint creates a new watchpoint.
CreateWatchpoint(api.EvalScope, string, api.WatchType) (*api.Breakpoint, error)
// ListBreakpoints gets all breakpoints.
Expand Down
2 changes: 1 addition & 1 deletion service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ func (s *Session) setBreakpoints(prefix string, totalBps int, metadataFunc func(
err = setLogMessage(bp, want.logMessage)
if err == nil {
// Create new breakpoints.
got, err = s.debugger.CreateBreakpoint(bp)
got, err = s.debugger.CreateBreakpoint(bp, "", nil)
}
}
}
Expand Down
Loading

0 comments on commit ec5fcc0

Please sign in to comment.