2022-07-14 21:14:45 +00:00
|
|
|
package proc
|
|
|
|
|
|
|
|
import (
|
2022-09-28 18:35:07 +00:00
|
|
|
"bytes"
|
2023-02-22 17:26:28 +00:00
|
|
|
"errors"
|
2022-07-14 21:14:45 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2023-02-22 17:26:28 +00:00
|
|
|
|
|
|
|
"github.com/go-delve/delve/pkg/logflags"
|
2022-07-14 21:14:45 +00:00
|
|
|
)
|
|
|
|
|
2022-12-28 11:41:13 +00:00
|
|
|
// TargetGroup represents a group of target processes being debugged that
|
2022-07-14 21:14:45 +00:00
|
|
|
// 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 {
|
2023-02-22 17:26:28 +00:00
|
|
|
procgrp ProcessGroup
|
|
|
|
|
|
|
|
targets []*Target
|
|
|
|
Selected *Target
|
|
|
|
followExecEnabled bool
|
2022-07-14 21:14:45 +00:00
|
|
|
|
|
|
|
RecordingManipulation
|
|
|
|
recman RecordingManipulationInternal
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
// 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
|
|
|
|
|
2022-07-14 21:14:45 +00:00
|
|
|
// 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
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
cctx *ContinueOnceContext
|
|
|
|
cfg NewTargetGroupConfig
|
|
|
|
CanDump bool
|
2022-07-14 21:14:45 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
// 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) (*Target, error)
|
|
|
|
|
2022-07-14 21:14:45 +00:00
|
|
|
// NewGroup creates a TargetGroup containing the specified Target.
|
2023-02-22 17:26:28 +00:00
|
|
|
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,
|
2022-07-14 21:14:45 +00:00
|
|
|
}
|
2023-02-22 17:26:28 +00:00
|
|
|
return grp, grp.addTarget
|
2022-07-14 21:14:45 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
// Restart copies breakpoints and follow exec status from oldgrp into grp.
|
2022-09-28 18:35:07 +00:00
|
|
|
// Breakpoints that can not be set will be discarded, if discard is not nil
|
|
|
|
// it will be called for each discarded breakpoint.
|
2023-02-22 17:26:28 +00:00
|
|
|
func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) {
|
|
|
|
for _, bp := range oldgrp.LogicalBreakpoints {
|
|
|
|
if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok {
|
2022-09-28 18:35:07 +00:00
|
|
|
continue
|
|
|
|
}
|
2023-02-22 17:26:28 +00:00
|
|
|
grp.LogicalBreakpoints[bp.LogicalID] = bp
|
2022-09-28 18:35:07 +00:00
|
|
|
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
|
2023-02-22 17:26:28 +00:00
|
|
|
if bp.Enabled {
|
|
|
|
err := grp.EnableBreakpoint(bp)
|
|
|
|
if err != nil {
|
|
|
|
if discard != nil {
|
|
|
|
discard(bp, err)
|
|
|
|
}
|
|
|
|
delete(grp.LogicalBreakpoints, bp.LogicalID)
|
2022-09-28 18:35:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-22 17:26:28 +00:00
|
|
|
if oldgrp.followExecEnabled {
|
|
|
|
grp.FollowExec(true, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason) (*Target, error) {
|
|
|
|
t, err := grp.newTarget(p, pid, currentThread, path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
t.StopReason = stopReason
|
|
|
|
//TODO(aarzilli): check if the target's command line matches the regex
|
|
|
|
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
|
|
|
|
}
|
|
|
|
logger := logflags.DebuggerLogger()
|
|
|
|
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
|
2022-09-28 18:35:07 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 17:12:34 +00:00
|
|
|
// Targets returns a slice of all targets in the group, including the
|
|
|
|
// ones that are no longer valid.
|
2022-07-14 21:14:45 +00:00
|
|
|
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
|
|
|
|
}
|
2022-09-26 17:12:34 +00:00
|
|
|
if err0 == nil {
|
|
|
|
err0 = err
|
|
|
|
}
|
2022-07-14 21:14:45 +00:00
|
|
|
}
|
|
|
|
return false, err0
|
|
|
|
}
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
func (grp *TargetGroup) numValid() int {
|
|
|
|
r := 0
|
|
|
|
for _, t := range grp.targets {
|
|
|
|
ok, _ := t.Valid()
|
|
|
|
if ok {
|
|
|
|
r++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2022-07-14 21:14:45 +00:00
|
|
|
// Detach detaches all targets in the group.
|
|
|
|
func (grp *TargetGroup) Detach(kill bool) error {
|
|
|
|
var errs []string
|
2023-02-22 17:26:28 +00:00
|
|
|
for i := len(grp.targets) - 1; i >= 0; i-- {
|
|
|
|
t := grp.targets[i]
|
2022-07-14 21:14:45 +00:00
|
|
|
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.
|
2023-02-22 17:26:28 +00:00
|
|
|
func (grp *TargetGroup) TargetForThread(tid int) *Target {
|
2022-07-14 21:14:45 +00:00
|
|
|
for _, t := range grp.targets {
|
2023-02-22 17:26:28 +00:00
|
|
|
if _, ok := t.FindThread(tid); ok {
|
|
|
|
return t
|
2022-07-14 21:14:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2022-09-26 17:12:34 +00:00
|
|
|
|
2022-09-28 18:35:07 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
2022-11-16 17:31:33 +00:00
|
|
|
case lbp.Set.Expr != nil:
|
|
|
|
addrs = lbp.Set.Expr(p)
|
2022-09-28 18:35:07 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-02-22 17:26:28 +00:00
|
|
|
// 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 {
|
|
|
|
if regex != "" {
|
|
|
|
return errors.New("regex not implemented")
|
|
|
|
}
|
|
|
|
it := ValidTargets{Group: grp}
|
|
|
|
for it.Next() {
|
|
|
|
err := it.proc.FollowExec(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
grp.followExecEnabled = v
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:12:34 +00:00
|
|
|
// 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
|
|
|
|
}
|
2023-02-22 17:26:28 +00:00
|
|
|
|
|
|
|
// Reset returns the iterator to the start of the group.
|
|
|
|
func (it *ValidTargets) Reset() {
|
|
|
|
it.Target = nil
|
|
|
|
it.start = 0
|
|
|
|
}
|