
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 #1653 Updates #2551
298 lines
7.4 KiB
Go
298 lines
7.4 KiB
Go
package proc
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// TargetGroup reperesents a group of target processes being debugged that
|
|
// will be resumed and stopped simultaneously.
|
|
// New targets are automatically added to the group if exec catching is
|
|
// enabled and the backend supports it, otherwise the group will always
|
|
// contain a single target process.
|
|
type TargetGroup struct {
|
|
targets []*Target
|
|
Selected *Target
|
|
|
|
RecordingManipulation
|
|
recman RecordingManipulationInternal
|
|
|
|
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
|
|
// will keep the stepping breakpoints instead of clearing them.
|
|
KeepSteppingBreakpoints KeepSteppingBreakpoints
|
|
|
|
LogicalBreakpoints map[int]*LogicalBreakpoint
|
|
|
|
continueOnce ContinueOnceFunc
|
|
cctx *ContinueOnceContext
|
|
}
|
|
|
|
// NewGroup creates a TargetGroup containing the specified Target.
|
|
func NewGroup(t *Target) *TargetGroup {
|
|
if t.partOfGroup {
|
|
panic("internal error: target is already part of a group")
|
|
}
|
|
t.partOfGroup = true
|
|
if t.Breakpoints().Logical == nil {
|
|
t.Breakpoints().Logical = make(map[int]*LogicalBreakpoint)
|
|
}
|
|
return &TargetGroup{
|
|
RecordingManipulation: t.recman,
|
|
targets: []*Target{t},
|
|
Selected: t,
|
|
cctx: &ContinueOnceContext{},
|
|
recman: t.recman,
|
|
LogicalBreakpoints: t.Breakpoints().Logical,
|
|
continueOnce: t.continueOnce,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return grp.targets
|
|
}
|
|
|
|
// Valid returns true if any target in the target group is valid.
|
|
func (grp *TargetGroup) Valid() (bool, error) {
|
|
var err0 error
|
|
for _, t := range grp.targets {
|
|
ok, err := t.Valid()
|
|
if ok {
|
|
return true, nil
|
|
}
|
|
if err0 == nil {
|
|
err0 = err
|
|
}
|
|
}
|
|
return false, err0
|
|
}
|
|
|
|
// Detach detaches all targets in the group.
|
|
func (grp *TargetGroup) Detach(kill bool) error {
|
|
var errs []string
|
|
for _, t := range grp.targets {
|
|
isvalid, _ := t.Valid()
|
|
if !isvalid {
|
|
continue
|
|
}
|
|
err := t.detach(kill)
|
|
if err != nil {
|
|
errs = append(errs, fmt.Sprintf("could not detach process %d: %v", t.Pid(), err))
|
|
}
|
|
}
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("%s", strings.Join(errs, "\n"))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HasSteppingBreakpoints returns true if any of the targets has stepping breakpoints set.
|
|
func (grp *TargetGroup) HasSteppingBreakpoints() bool {
|
|
for _, t := range grp.targets {
|
|
if t.Breakpoints().HasSteppingBreakpoints() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ClearSteppingBreakpoints removes all stepping breakpoints.
|
|
func (grp *TargetGroup) ClearSteppingBreakpoints() error {
|
|
for _, t := range grp.targets {
|
|
if t.Breakpoints().HasSteppingBreakpoints() {
|
|
return t.ClearSteppingBreakpoints()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ThreadList returns a list of all threads in all target processes.
|
|
func (grp *TargetGroup) ThreadList() []Thread {
|
|
r := []Thread{}
|
|
for _, t := range grp.targets {
|
|
r = append(r, t.ThreadList()...)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// TargetForThread returns the target containing the given thread.
|
|
func (grp *TargetGroup) TargetForThread(thread Thread) *Target {
|
|
for _, t := range grp.targets {
|
|
for _, th := range t.ThreadList() {
|
|
if th == thread {
|
|
return t
|
|
}
|
|
}
|
|
}
|
|
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
|
|
Group *TargetGroup
|
|
start int
|
|
}
|
|
|
|
// Next moves to the next valid target, returns false if there aren't more
|
|
// valid targets in the group.
|
|
func (it *ValidTargets) Next() bool {
|
|
for i := it.start; i < len(it.Group.targets); i++ {
|
|
if ok, _ := it.Group.targets[i].Valid(); ok {
|
|
it.Target = it.Group.targets[i]
|
|
it.start = i + 1
|
|
return true
|
|
}
|
|
}
|
|
it.start = len(it.Group.targets)
|
|
it.Target = nil
|
|
return false
|
|
}
|