gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/target_group.go (about)

     1  package proc
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"gitlab.com/Raven-IO/raven-delve/pkg/logflags"
    10  )
    11  
    12  // TargetGroup represents a group of target processes being debugged that
    13  // will be resumed and stopped simultaneously.
    14  // New targets are automatically added to the group if exec catching is
    15  // enabled and the backend supports it, otherwise the group will always
    16  // contain a single target process.
    17  type TargetGroup struct {
    18  	procgrp ProcessGroup
    19  
    20  	targets           []*Target
    21  	Selected          *Target
    22  	followExecEnabled bool
    23  	followExecRegex   *regexp.Regexp
    24  
    25  	RecordingManipulation
    26  	recman RecordingManipulationInternal
    27  
    28  	// StopReason describes the reason why the selected target process is stopped.
    29  	// A process could be stopped for multiple simultaneous reasons, in which
    30  	// case only one will be reported.
    31  	StopReason StopReason
    32  
    33  	// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
    34  	// will keep the stepping breakpoints instead of clearing them.
    35  	KeepSteppingBreakpoints KeepSteppingBreakpoints
    36  
    37  	LogicalBreakpoints map[int]*LogicalBreakpoint
    38  
    39  	cctx    *ContinueOnceContext
    40  	cfg     NewTargetGroupConfig
    41  	CanDump bool
    42  }
    43  
    44  // NewTargetGroupConfig contains the configuration for a new TargetGroup object,
    45  type NewTargetGroupConfig struct {
    46  	DebugInfoDirs       []string   // Directories to search for split debug info
    47  	DisableAsyncPreempt bool       // Go 1.14 asynchronous preemption should be disabled
    48  	StopReason          StopReason // Initial stop reason
    49  	CanDump             bool       // Can create core dumps (must implement ProcessInternal.MemoryMap)
    50  }
    51  
    52  type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason, string) (*Target, error)
    53  
    54  // NewGroup creates a TargetGroup containing the specified Target.
    55  func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) {
    56  	grp := &TargetGroup{
    57  		procgrp:            procgrp,
    58  		cctx:               &ContinueOnceContext{},
    59  		LogicalBreakpoints: make(map[int]*LogicalBreakpoint),
    60  		StopReason:         cfg.StopReason,
    61  		cfg:                cfg,
    62  		CanDump:            cfg.CanDump,
    63  	}
    64  	return grp, grp.addTarget
    65  }
    66  
    67  // Restart copies breakpoints and follow exec status from oldgrp into grp.
    68  // Breakpoints that can not be set will be discarded, if discard is not nil
    69  // it will be called for each discarded breakpoint.
    70  func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) {
    71  	for _, bp := range oldgrp.LogicalBreakpoints {
    72  		if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok {
    73  			continue
    74  		}
    75  		grp.LogicalBreakpoints[bp.LogicalID] = bp
    76  		bp.TotalHitCount = 0
    77  		bp.HitCount = make(map[int64]uint64)
    78  		bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart
    79  		if bp.Enabled {
    80  			err := grp.EnableBreakpoint(bp)
    81  			if err != nil {
    82  				if discard != nil {
    83  					discard(bp, err)
    84  				}
    85  				delete(grp.LogicalBreakpoints, bp.LogicalID)
    86  			}
    87  		}
    88  	}
    89  	if oldgrp.followExecEnabled {
    90  		rgx := ""
    91  		if oldgrp.followExecRegex != nil {
    92  			rgx = oldgrp.followExecRegex.String()
    93  		}
    94  		grp.FollowExec(true, rgx)
    95  	}
    96  }
    97  
    98  func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason, cmdline string) (*Target, error) {
    99  	logger := logflags.DebuggerLogger()
   100  	if len(grp.targets) > 0 {
   101  		if !grp.followExecEnabled {
   102  			logger.Debugf("Detaching from child target (follow-exec disabled) %d %q", pid, cmdline)
   103  			return nil, nil
   104  		}
   105  		if grp.followExecRegex != nil && !grp.followExecRegex.MatchString(cmdline) {
   106  			logger.Debugf("Detaching from child target (follow-exec regex not matched) %d %q", pid, cmdline)
   107  			return nil, nil
   108  		}
   109  	}
   110  	t, err := grp.newTarget(p, pid, currentThread, path, cmdline)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	t.StopReason = stopReason
   115  	logger.Debugf("Adding target %d %q", t.Pid(), t.CmdLine)
   116  	if t.partOfGroup {
   117  		panic("internal error: target is already part of group")
   118  	}
   119  	t.partOfGroup = true
   120  	if grp.RecordingManipulation == nil {
   121  		grp.RecordingManipulation = t.recman
   122  		grp.recman = t.recman
   123  	}
   124  	if grp.Selected == nil {
   125  		grp.Selected = t
   126  	}
   127  	t.Breakpoints().Logical = grp.LogicalBreakpoints
   128  	for _, lbp := range grp.LogicalBreakpoints {
   129  		if lbp.LogicalID < 0 {
   130  			continue
   131  		}
   132  		err := enableBreakpointOnTarget(t, lbp)
   133  		if err != nil {
   134  			logger.Debugf("could not enable breakpoint %d on new target %d: %v", lbp.LogicalID, t.Pid(), err)
   135  		} else {
   136  			logger.Debugf("breakpoint %d enabled on new target %d: %v", lbp.LogicalID, t.Pid(), err)
   137  		}
   138  	}
   139  	grp.targets = append(grp.targets, t)
   140  	return t, nil
   141  }
   142  
   143  // Targets returns a slice of all targets in the group, including the
   144  // ones that are no longer valid.
   145  func (grp *TargetGroup) Targets() []*Target {
   146  	return grp.targets
   147  }
   148  
   149  // Valid returns true if any target in the target group is valid.
   150  func (grp *TargetGroup) Valid() (bool, error) {
   151  	var err0 error
   152  	for _, t := range grp.targets {
   153  		ok, err := t.Valid()
   154  		if ok {
   155  			return true, nil
   156  		}
   157  		if err0 == nil {
   158  			err0 = err
   159  		}
   160  	}
   161  	return false, err0
   162  }
   163  
   164  func (grp *TargetGroup) numValid() int {
   165  	r := 0
   166  	for _, t := range grp.targets {
   167  		ok, _ := t.Valid()
   168  		if ok {
   169  			r++
   170  		}
   171  	}
   172  	return r
   173  }
   174  
   175  // Detach detaches all targets in the group.
   176  func (grp *TargetGroup) Detach(kill bool) error {
   177  	var errs []string
   178  	for i := len(grp.targets) - 1; i >= 0; i-- {
   179  		t := grp.targets[i]
   180  		isvalid, _ := t.Valid()
   181  		if !isvalid {
   182  			continue
   183  		}
   184  		err := grp.detachTarget(t, kill)
   185  		if err != nil {
   186  			errs = append(errs, fmt.Sprintf("could not detach process %d: %v", t.Pid(), err))
   187  		}
   188  	}
   189  	if len(errs) > 0 {
   190  		return fmt.Errorf("%s", strings.Join(errs, "\n"))
   191  	}
   192  	return grp.procgrp.Close()
   193  }
   194  
   195  // detachTarget will detach the target from the underlying process.
   196  // This means the debugger will no longer receive events from the process
   197  // we were previously debugging.
   198  // If kill is true then the process will be killed when we detach.
   199  func (grp *TargetGroup) detachTarget(t *Target, kill bool) error {
   200  	if !kill {
   201  		if t.asyncPreemptChanged {
   202  			setAsyncPreemptOff(t, t.asyncPreemptOff)
   203  		}
   204  		for _, bp := range t.Breakpoints().M {
   205  			if bp != nil {
   206  				err := t.ClearBreakpoint(bp.Addr)
   207  				if err != nil {
   208  					return err
   209  				}
   210  			}
   211  		}
   212  	}
   213  	t.StopReason = StopUnknown
   214  	return grp.procgrp.Detach(t.Pid(), kill)
   215  }
   216  
   217  // HasSteppingBreakpoints returns true if any of the targets has stepping breakpoints set.
   218  func (grp *TargetGroup) HasSteppingBreakpoints() bool {
   219  	for _, t := range grp.targets {
   220  		if t.Breakpoints().HasSteppingBreakpoints() {
   221  			return true
   222  		}
   223  	}
   224  	return false
   225  }
   226  
   227  // ClearSteppingBreakpoints removes all stepping breakpoints.
   228  func (grp *TargetGroup) ClearSteppingBreakpoints() error {
   229  	for _, t := range grp.targets {
   230  		if t.Breakpoints().HasSteppingBreakpoints() {
   231  			return t.ClearSteppingBreakpoints()
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  // ThreadList returns a list of all threads in all target processes.
   238  func (grp *TargetGroup) ThreadList() []Thread {
   239  	r := []Thread{}
   240  	for _, t := range grp.targets {
   241  		r = append(r, t.ThreadList()...)
   242  	}
   243  	return r
   244  }
   245  
   246  // TargetForThread returns the target containing the given thread.
   247  func (grp *TargetGroup) TargetForThread(tid int) *Target {
   248  	for _, t := range grp.targets {
   249  		if _, ok := t.FindThread(tid); ok {
   250  			return t
   251  		}
   252  	}
   253  	return nil
   254  }
   255  
   256  // EnableBreakpoint re-enables a disabled logical breakpoint.
   257  func (grp *TargetGroup) EnableBreakpoint(lbp *LogicalBreakpoint) error {
   258  	var err0, errNotFound, errExists error
   259  	didSet := false
   260  targetLoop:
   261  	for _, p := range grp.targets {
   262  		err := enableBreakpointOnTarget(p, lbp)
   263  
   264  		switch err.(type) {
   265  		case nil:
   266  			didSet = true
   267  		case *ErrFunctionNotFound, *ErrCouldNotFindLine:
   268  			errNotFound = err
   269  		case BreakpointExistsError:
   270  			errExists = err
   271  		default:
   272  			err0 = err
   273  			break targetLoop
   274  		}
   275  	}
   276  	if errNotFound != nil && !didSet {
   277  		return errNotFound
   278  	}
   279  	if errExists != nil && !didSet {
   280  		return errExists
   281  	}
   282  	if !didSet {
   283  		if _, err := grp.Valid(); err != nil {
   284  			return err
   285  		}
   286  	}
   287  	if err0 != nil {
   288  		it := ValidTargets{Group: grp}
   289  		for it.Next() {
   290  			for _, bp := range it.Breakpoints().M {
   291  				if bp.LogicalID() == lbp.LogicalID {
   292  					if err1 := it.ClearBreakpoint(bp.Addr); err1 != nil {
   293  						return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err0, err1)
   294  					}
   295  				}
   296  			}
   297  		}
   298  		return err0
   299  	}
   300  	lbp.Enabled = true
   301  	return nil
   302  }
   303  
   304  func enableBreakpointOnTarget(p *Target, lbp *LogicalBreakpoint) error {
   305  	var err error
   306  	var addrs []uint64
   307  	switch {
   308  	case lbp.Set.File != "":
   309  		addrs, err = FindFileLocation(p, lbp.Set.File, lbp.Set.Line)
   310  	case lbp.Set.FunctionName != "":
   311  		addrs, err = FindFunctionLocation(p, lbp.Set.FunctionName, lbp.Set.Line)
   312  	case len(lbp.Set.PidAddrs) > 0:
   313  		for _, pidAddr := range lbp.Set.PidAddrs {
   314  			if pidAddr.Pid == p.Pid() {
   315  				addrs = append(addrs, pidAddr.Addr)
   316  			}
   317  		}
   318  	case lbp.Set.Expr != nil:
   319  		addrs = lbp.Set.Expr(p)
   320  	default:
   321  		return fmt.Errorf("breakpoint %d can not be enabled", lbp.LogicalID)
   322  	}
   323  
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	for _, addr := range addrs {
   329  		_, err = p.SetBreakpoint(lbp.LogicalID, addr, UserBreakpoint, nil)
   330  		if err != nil {
   331  			if _, isexists := err.(BreakpointExistsError); isexists {
   332  				continue
   333  			}
   334  			return err
   335  		}
   336  	}
   337  
   338  	return err
   339  }
   340  
   341  // DisableBreakpoint disables a logical breakpoint.
   342  func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error {
   343  	var errs []error
   344  	n := 0
   345  	it := ValidTargets{Group: grp}
   346  	for it.Next() {
   347  		for _, bp := range it.Breakpoints().M {
   348  			if bp.LogicalID() == lbp.LogicalID {
   349  				n++
   350  				err := it.ClearBreakpoint(bp.Addr)
   351  				if err != nil {
   352  					errs = append(errs, err)
   353  				}
   354  			}
   355  		}
   356  	}
   357  	if len(errs) > 0 {
   358  		buf := new(bytes.Buffer)
   359  		for i, err := range errs {
   360  			fmt.Fprintf(buf, "%s", err)
   361  			if i != len(errs)-1 {
   362  				fmt.Fprintf(buf, ", ")
   363  			}
   364  		}
   365  
   366  		if len(errs) == n {
   367  			return fmt.Errorf("unable to clear breakpoint %d: %v", lbp.LogicalID, buf.String())
   368  		}
   369  		return fmt.Errorf("unable to clear breakpoint %d (partial): %s", lbp.LogicalID, buf.String())
   370  	}
   371  	lbp.Enabled = false
   372  	return nil
   373  }
   374  
   375  // FollowExec enables or disables follow exec mode. When follow exec mode is
   376  // enabled new processes spawned by the target process are automatically
   377  // added to the target group.
   378  // If regex is not the empty string only processes whose command line
   379  // matches regex will be added to the target group.
   380  func (grp *TargetGroup) FollowExec(v bool, regex string) error {
   381  	grp.followExecRegex = nil
   382  	if regex != "" && v {
   383  		var err error
   384  		grp.followExecRegex, err = regexp.Compile(regex)
   385  		if err != nil {
   386  			return err
   387  		}
   388  	}
   389  	it := ValidTargets{Group: grp}
   390  	for it.Next() {
   391  		err := it.proc.FollowExec(v)
   392  		if err != nil {
   393  			return err
   394  		}
   395  	}
   396  	grp.followExecEnabled = v
   397  	return nil
   398  }
   399  
   400  // FollowExecEnabled returns true if follow exec is enabled
   401  func (grp *TargetGroup) FollowExecEnabled() bool {
   402  	return grp.followExecEnabled
   403  }
   404  
   405  // ValidTargets iterates through all valid targets in Group.
   406  type ValidTargets struct {
   407  	*Target
   408  	Group *TargetGroup
   409  	start int
   410  }
   411  
   412  // Next moves to the next valid target, returns false if there aren't more
   413  // valid targets in the group.
   414  func (it *ValidTargets) Next() bool {
   415  	for i := it.start; i < len(it.Group.targets); i++ {
   416  		if ok, _ := it.Group.targets[i].Valid(); ok {
   417  			it.Target = it.Group.targets[i]
   418  			it.start = i + 1
   419  			return true
   420  		}
   421  	}
   422  	it.start = len(it.Group.targets)
   423  	it.Target = nil
   424  	return false
   425  }
   426  
   427  // Reset returns the iterator to the start of the group.
   428  func (it *ValidTargets) Reset() {
   429  	it.Target = nil
   430  	it.start = 0
   431  }