mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			298 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package proc
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // 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 {
 | |
| 	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 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
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 | 
