delve/pkg/proc/target_group.go

432 lines
11 KiB
Go
Raw Normal View History

package proc
import (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/go-delve/delve/pkg/logflags"
)
// TargetGroup represents 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 {
procgrp ProcessGroup
targets []*Target
Selected *Target
followExecEnabled bool
followExecRegex *regexp.Regexp
RecordingManipulation
recman RecordingManipulationInternal
// StopReason describes the reason why the selected target process is stopped.
// A process could be stopped for multiple simultaneous reasons, in which
// case only one will be reported.
StopReason StopReason
// 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
cctx *ContinueOnceContext
cfg NewTargetGroupConfig
CanDump bool
}
// NewTargetGroupConfig contains the configuration for a new TargetGroup object,
type NewTargetGroupConfig struct {
DebugInfoDirs []string // Directories to search for split debug info
DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled
StopReason StopReason // Initial stop reason
CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap)
}
type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason, string) (*Target, error)
// NewGroup creates a TargetGroup containing the specified Target.
func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) {
grp := &TargetGroup{
procgrp: procgrp,
cctx: &ContinueOnceContext{},
LogicalBreakpoints: make(map[int]*LogicalBreakpoint),
StopReason: cfg.StopReason,
cfg: cfg,
CanDump: cfg.CanDump,
}
return grp, grp.addTarget
}
// Restart copies breakpoints and follow exec status from oldgrp into grp.
// Breakpoints that can not be set will be discarded, if discard is not nil
// it will be called for each discarded breakpoint.
func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) {
for _, bp := range oldgrp.LogicalBreakpoints {
if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok {
continue
}
grp.LogicalBreakpoints[bp.LogicalID] = bp
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
if bp.Enabled {
err := grp.EnableBreakpoint(bp)
if err != nil {
if discard != nil {
discard(bp, err)
}
delete(grp.LogicalBreakpoints, bp.LogicalID)
}
}
}
if oldgrp.followExecEnabled {
rgx := ""
if oldgrp.followExecRegex != nil {
rgx = oldgrp.followExecRegex.String()
}
grp.FollowExec(true, rgx)
}
}
func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason, cmdline string) (*Target, error) {
logger := logflags.DebuggerLogger()
if len(grp.targets) > 0 {
if !grp.followExecEnabled {
logger.Debugf("Detaching from child target (follow-exec disabled) %d %q", pid, cmdline)
return nil, nil
}
if grp.followExecRegex != nil && !grp.followExecRegex.MatchString(cmdline) {
logger.Debugf("Detaching from child target (follow-exec regex not matched) %d %q", pid, cmdline)
return nil, nil
}
}
t, err := grp.newTarget(p, pid, currentThread, path, cmdline)
if err != nil {
return nil, err
}
t.StopReason = stopReason
logger.Debugf("Adding target %d %q", t.Pid(), t.CmdLine)
if t.partOfGroup {
panic("internal error: target is already part of group")
}
t.partOfGroup = true
if grp.RecordingManipulation == nil {
grp.RecordingManipulation = t.recman
grp.recman = t.recman
}
if grp.Selected == nil {
grp.Selected = t
}
t.Breakpoints().Logical = grp.LogicalBreakpoints
for _, lbp := range grp.LogicalBreakpoints {
if lbp.LogicalID < 0 {
continue
}
err := enableBreakpointOnTarget(t, lbp)
if err != nil {
logger.Debugf("could not enable breakpoint %d on new target %d: %v", lbp.LogicalID, t.Pid(), err)
} else {
logger.Debugf("breakpoint %d enabled on new target %d: %v", lbp.LogicalID, t.Pid(), err)
}
}
grp.targets = append(grp.targets, t)
return t, nil
}
// 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
}
func (grp *TargetGroup) numValid() int {
r := 0
for _, t := range grp.targets {
ok, _ := t.Valid()
if ok {
r++
}
}
return r
}
// Detach detaches all targets in the group.
func (grp *TargetGroup) Detach(kill bool) error {
var errs []string
for i := len(grp.targets) - 1; i >= 0; i-- {
t := grp.targets[i]
isvalid, _ := t.Valid()
if !isvalid {
continue
}
err := grp.detachTarget(t, 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
}
// detachTarget will detach the target from the underlying process.
// This means the debugger will no longer receive events from the process
// we were previously debugging.
// If kill is true then the process will be killed when we detach.
func (grp *TargetGroup) detachTarget(t *Target, kill bool) error {
if !kill {
if t.asyncPreemptChanged {
setAsyncPreemptOff(t, t.asyncPreemptOff)
}
for _, bp := range t.Breakpoints().M {
if bp != nil {
err := t.ClearBreakpoint(bp.Addr)
if err != nil {
return err
}
}
}
}
t.StopReason = StopUnknown
return grp.procgrp.Detach(t.Pid(), kill)
}
// 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(tid int) *Target {
for _, t := range grp.targets {
if _, ok := t.FindThread(tid); ok {
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 len(lbp.Set.PidAddrs) > 0:
for _, pidAddr := range lbp.Set.PidAddrs {
if pidAddr.Pid == p.Pid() {
addrs = append(addrs, pidAddr.Addr)
}
}
case lbp.Set.Expr != nil:
addrs = lbp.Set.Expr(p)
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
}
// FollowExec enables or disables follow exec mode. When follow exec mode is
// enabled new processes spawned by the target process are automatically
// added to the target group.
// If regex is not the empty string only processes whose command line
// matches regex will be added to the target group.
func (grp *TargetGroup) FollowExec(v bool, regex string) error {
grp.followExecRegex = nil
if regex != "" && v {
var err error
grp.followExecRegex, err = regexp.Compile(regex)
if err != nil {
return err
}
}
it := ValidTargets{Group: grp}
for it.Next() {
err := it.proc.FollowExec(v)
if err != nil {
return err
}
}
grp.followExecEnabled = v
return nil
}
// FollowExecEnabled returns true if follow exec is enabled
func (grp *TargetGroup) FollowExecEnabled() bool {
return grp.followExecEnabled
}
// 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
}
// Reset returns the iterator to the start of the group.
func (it *ValidTargets) Reset() {
it.Target = nil
it.start = 0
}