2022-07-14 21:14:45 +00:00
|
|
|
package proc
|
|
|
|
|
|
|
|
import (
|
2022-09-28 18:35:07 +00:00
|
|
|
"bytes"
|
2022-07-14 21:14:45 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
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 {
|
|
|
|
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
|
2022-08-22 19:48:34 +00:00
|
|
|
if t.Breakpoints().Logical == nil {
|
|
|
|
t.Breakpoints().Logical = make(map[int]*LogicalBreakpoint)
|
|
|
|
}
|
2022-07-14 21:14:45 +00:00
|
|
|
return &TargetGroup{
|
|
|
|
RecordingManipulation: t.recman,
|
|
|
|
targets: []*Target{t},
|
|
|
|
Selected: t,
|
|
|
|
cctx: &ContinueOnceContext{},
|
|
|
|
recman: t.recman,
|
|
|
|
LogicalBreakpoints: t.Breakpoints().Logical,
|
|
|
|
continueOnce: t.continueOnce,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 18:35:07 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|