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 }