proc,service/debugger: introduce TargetGroup abstraction (#3030)

Introduces a new TargetGroup abstraction that can be used to manage
multiple related targets.
No actual management of child processes is implemented here, this is
just a refactoring to make it possible to do that in the future.

Updates #2551
This commit is contained in:
Alessandro Arzilli 2022-07-14 23:14:45 +02:00 committed by GitHub
parent 1fe03e728c
commit 6f34add5db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 619 additions and 339 deletions

@ -639,7 +639,7 @@ func (t *Target) SetWatchpoint(logicalID int, scope *EvalScope, expr string, wty
func (t *Target) setBreakpointInternal(logicalID int, addr uint64, kind BreakpointKind, wtype WatchType, cond ast.Expr) (*Breakpoint, error) {
if valid, err := t.Valid(); !valid {
recorded, _ := t.Recorded()
recorded, _ := t.recman.Recorded()
if !recorded {
return nil, err
}
@ -744,7 +744,7 @@ func (bp *Breakpoint) canOverlap(kind BreakpointKind) bool {
// ClearBreakpoint clears the breakpoint at addr.
func (t *Target) ClearBreakpoint(addr uint64) error {
if valid, err := t.Valid(); !valid {
recorded, _ := t.Recorded()
recorded, _ := t.recman.Recorded()
if !recorded {
return err
}

@ -227,7 +227,9 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e
DebugInfoDirs: debugInfoDirs,
DisableAsyncPreempt: false,
StopReason: proc.StopAttached,
CanDump: false})
CanDump: false,
ContinueOnce: continueOnce,
})
}
// BinInfo will return the binary info.
@ -417,9 +419,7 @@ func (p *process) ClearInternalBreakpoints() error {
return nil
}
// ContinueOnce will always return an error because you
// cannot control execution of a core file.
func (p *process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
return nil, proc.StopUnknown, ErrContinueCore
}

@ -256,8 +256,9 @@ func TestCore(t *testing.T) {
t.Skip("disabled on linux, Github Actions, with PIE buildmode")
}
p := withCoreFile(t, "panic", "")
grp := proc.NewGroup(p)
recorded, _ := p.Recorded()
recorded, _ := grp.Recorded()
if !recorded {
t.Fatalf("expecting recorded to be true")
}

@ -146,7 +146,8 @@ func (callCtx *callContext) doReturn(ret *Variable, err error) {
// EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
// Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope.
func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg LoadConfig, checkEscape bool) error {
t := grp.Selected
bi := t.BinInfo()
if !t.SupportsFunctionCalls() {
return errFuncCallUnsupportedBackend
@ -201,7 +202,7 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
contReq, ok := <-continueRequest
if contReq.cont {
return t.Continue()
return grp.Continue()
}
return finishEvalExpressionWithCalls(t, g, contReq, ok)

@ -709,7 +709,9 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason
DebugInfoDirs: debugInfoDirs,
DisableAsyncPreempt: runtime.GOOS == "darwin",
StopReason: stopReason,
CanDump: runtime.GOOS == "darwin"})
CanDump: runtime.GOOS == "darwin",
ContinueOnce: continueOnce,
})
if err != nil {
p.Detach(true)
return nil, err
@ -799,9 +801,11 @@ const (
debugServerTargetExcBreakpoint = 0x96
)
// ContinueOnce will continue execution of the process until
// a breakpoint is hit or signal is received.
func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
if len(procs) != 1 {
panic("not implemented")
}
p := procs[0].(*gdbProcess)
if p.exited {
return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid}
}

@ -23,7 +23,7 @@ func TestMain(m *testing.M) {
os.Exit(protest.RunTestsWithFixtures(m))
}
func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixture protest.Fixture)) {
func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup, fixture protest.Fixture)) {
fixture := protest.BuildFixture(name, 0)
protest.MustHaveRecordingAllowed(t)
if path, _ := exec.LookPath("rr"); path == "" {
@ -36,9 +36,11 @@ func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixtur
}
t.Logf("replaying %q", tracedir)
defer p.Detach(true)
grp := proc.NewGroup(p)
fn(p, fixture)
defer grp.Detach(true)
fn(grp, fixture)
}
func assertNoError(err error, t testing.TB, s string) {
@ -69,25 +71,26 @@ func setFunctionBreakpoint(p *proc.Target, t *testing.T, fname string) *proc.Bre
func TestRestartAfterExit(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
withTestRecording("testnextprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
p := grp.Selected
setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue")
assertNoError(grp.Continue(), t, "Continue")
loc, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location()")
err = p.Continue()
err = grp.Continue()
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
t.Fatalf("program did not exit: %v", err)
}
assertNoError(p.Restart(""), t, "Restart")
assertNoError(grp.Restart(""), t, "Restart")
assertNoError(p.Continue(), t, "Continue (after restart)")
assertNoError(grp.Continue(), t, "Continue (after restart)")
loc2, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location() (after restart)")
if loc2.Line != loc.Line {
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
}
err = p.Continue()
err = grp.Continue()
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
t.Fatalf("program did not exit (after exit): %v", err)
}
@ -96,21 +99,22 @@ func TestRestartAfterExit(t *testing.T) {
func TestRestartDuringStop(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
withTestRecording("testnextprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
p := grp.Selected
setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue")
assertNoError(grp.Continue(), t, "Continue")
loc, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location()")
assertNoError(p.Restart(""), t, "Restart")
assertNoError(grp.Restart(""), t, "Restart")
assertNoError(p.Continue(), t, "Continue (after restart)")
assertNoError(grp.Continue(), t, "Continue (after restart)")
loc2, err := p.CurrentThread().Location()
assertNoError(err, t, "CurrentThread().Location() (after restart)")
if loc2.Line != loc.Line {
t.Fatalf("stopped at %d (expected %d)", loc2.Line, loc.Line)
}
err = p.Continue()
err = grp.Continue()
if _, isexited := err.(proc.ErrProcessExited); err == nil || !isexited {
t.Fatalf("program did not exit (after exit): %v", err)
}
@ -137,22 +141,23 @@ func setFileBreakpoint(p *proc.Target, t *testing.T, fixture protest.Fixture, li
func TestReverseBreakpointCounts(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) {
withTestRecording("bpcountstest", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
p := grp.Selected
endbp := setFileBreakpoint(p, t, fixture, 28)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
loc, _ := p.CurrentThread().Location()
if loc.PC != endbp.Addr {
t.Fatalf("did not reach end of main.main function: %s:%d (%#x)", loc.File, loc.Line, loc.PC)
}
p.ClearBreakpoint(endbp.Addr)
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
assertNoError(grp.ChangeDirection(proc.Backward), t, "Switching to backward direction")
bp := setFileBreakpoint(p, t, fixture, 12)
startbp := setFileBreakpoint(p, t, fixture, 20)
countLoop:
for {
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
loc, _ := p.CurrentThread().Location()
switch loc.PC {
case startbp.Addr:
@ -181,40 +186,41 @@ func TestReverseBreakpointCounts(t *testing.T) {
})
}
func getPosition(p *proc.Target, t *testing.T) (when string, loc *proc.Location) {
func getPosition(grp *proc.TargetGroup, t *testing.T) (when string, loc *proc.Location) {
var err error
when, err = p.When()
when, err = grp.When()
assertNoError(err, t, "When")
loc, err = p.CurrentThread().Location()
loc, err = grp.Selected.CurrentThread().Location()
assertNoError(err, t, "Location")
return
}
func TestCheckpoints(t *testing.T) {
protest.AllowRecording(t)
withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
withTestRecording("continuetestprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
p := grp.Selected
// Continues until start of main.main, record output of 'when'
bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue")
when0, loc0 := getPosition(p, t)
assertNoError(grp.Continue(), t, "Continue")
when0, loc0 := getPosition(grp, t)
t.Logf("when0: %q (%#x) %x", when0, loc0.PC, p.CurrentThread().ThreadID())
// Create a checkpoint and check that the list of checkpoints reflects this
cpid, err := p.Checkpoint("checkpoint1")
cpid, err := grp.Checkpoint("checkpoint1")
if cpid != 1 {
t.Errorf("unexpected checkpoint id %d", cpid)
}
assertNoError(err, t, "Checkpoint")
checkpoints, err := p.Checkpoints()
checkpoints, err := grp.Checkpoints()
assertNoError(err, t, "Checkpoints")
if len(checkpoints) != 1 {
t.Fatalf("wrong number of checkpoints %v (one expected)", checkpoints)
}
// Move forward with next, check that the output of 'when' changes
assertNoError(p.Next(), t, "First Next")
assertNoError(p.Next(), t, "Second Next")
when1, loc1 := getPosition(p, t)
assertNoError(grp.Next(), t, "First Next")
assertNoError(grp.Next(), t, "Second Next")
when1, loc1 := getPosition(grp, t)
t.Logf("when1: %q (%#x) %x", when1, loc1.PC, p.CurrentThread().ThreadID())
if loc0.PC == loc1.PC {
t.Fatalf("next did not move process %#x", loc0.PC)
@ -225,10 +231,10 @@ func TestCheckpoints(t *testing.T) {
// Move back to checkpoint, check that the output of 'when' is the same as
// what it was when we set the breakpoint
p.Restart(fmt.Sprintf("c%d", cpid))
grp.Restart(fmt.Sprintf("c%d", cpid))
g, _ := proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
when2, loc2 := getPosition(p, t)
when2, loc2 := getPosition(grp, t)
t.Logf("when2: %q (%#x) %x", when2, loc2.PC, p.CurrentThread().ThreadID())
if loc2.PC != loc0.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc0.PC, loc2.PC)
@ -238,9 +244,9 @@ func TestCheckpoints(t *testing.T) {
}
// Move forward with next again, check that the output of 'when' matches
assertNoError(p.Next(), t, "First Next")
assertNoError(p.Next(), t, "Second Next")
when3, loc3 := getPosition(p, t)
assertNoError(grp.Next(), t, "First Next")
assertNoError(grp.Next(), t, "Second Next")
when3, loc3 := getPosition(grp, t)
t.Logf("when3: %q (%#x)", when3, loc3.PC)
if loc3.PC != loc1.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc3.PC)
@ -253,12 +259,12 @@ func TestCheckpoints(t *testing.T) {
// output of 'when' again
err = p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
p.Restart(fmt.Sprintf("c%d", cpid))
grp.Restart(fmt.Sprintf("c%d", cpid))
g, _ = proc.FindGoroutine(p, 1)
p.SwitchGoroutine(g)
assertNoError(p.Next(), t, "First Next")
assertNoError(p.Next(), t, "Second Next")
when4, loc4 := getPosition(p, t)
assertNoError(grp.Next(), t, "First Next")
assertNoError(grp.Next(), t, "Second Next")
when4, loc4 := getPosition(grp, t)
t.Logf("when4: %q (%#x)", when4, loc4.PC)
if loc4.PC != loc1.PC {
t.Fatalf("PC address mismatch %#x != %#x", loc1.PC, loc4.PC)
@ -268,8 +274,8 @@ func TestCheckpoints(t *testing.T) {
}
// Delete checkpoint, check that the list of checkpoints is updated
assertNoError(p.ClearCheckpoint(cpid), t, "ClearCheckpoint")
checkpoints, err = p.Checkpoints()
assertNoError(grp.ClearCheckpoint(cpid), t, "ClearCheckpoint")
checkpoints, err = grp.Checkpoints()
assertNoError(err, t, "Checkpoints")
if len(checkpoints) != 0 {
t.Fatalf("wrong number of checkpoints %v (zero expected)", checkpoints)
@ -280,12 +286,13 @@ func TestCheckpoints(t *testing.T) {
func TestIssue1376(t *testing.T) {
// Backward Continue should terminate when it encounters the start of the process.
protest.AllowRecording(t)
withTestRecording("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) {
withTestRecording("continuetestprog", t, func(grp *proc.TargetGroup, fixture protest.Fixture) {
p := grp.Selected
bp := setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue (forward)")
assertNoError(grp.Continue(), t, "Continue (forward)")
err := p.ClearBreakpoint(bp.Addr)
assertNoError(err, t, "ClearBreakpoint")
assertNoError(p.ChangeDirection(proc.Backward), t, "Switching to backward direction")
assertNoError(p.Continue(), t, "Continue (backward)")
assertNoError(grp.ChangeDirection(proc.Backward), t, "Switching to backward direction")
assertNoError(grp.Continue(), t, "Continue (backward)")
})
}

@ -38,7 +38,6 @@ type ProcessInternal interface {
// ErrProcessExited or ErrProcessDetached).
Valid() (bool, error)
Detach(bool) error
ContinueOnce(*ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error)
// RequestManualStop attempts to stop all the process' threads.
RequestManualStop(cctx *ContinueOnceContext) error

@ -172,9 +172,11 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
return dbp.memthread.clearSoftwareBreakpoint(bp)
}
// ContinueOnce will continue the target until it stops.
// This could be the result of a breakpoint or signal.
func (dbp *nativeProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
if len(procs) != 1 {
panic("not implemented")
}
dbp := procs[0].(*nativeProcess)
if dbp.exited {
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid}
}
@ -253,8 +255,9 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc
// See: https://go-review.googlesource.com/c/go/+/208126
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
StopReason: stopReason,
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows",
StopReason: stopReason,
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows",
ContinueOnce: continueOnce,
})
if err != nil {
return nil, err

@ -0,0 +1,42 @@
package proc
// Wrapper functions so that most tests proc_test.go don't need to worry
// about TargetGroup when they just need to resume a single process.
func newGroupTransient(tgt *Target) *TargetGroup {
grp := NewGroup(tgt)
tgt.partOfGroup = false
return grp
}
func (tgt *Target) Detach(kill bool) error {
return tgt.detach(kill)
}
func (tgt *Target) Continue() error {
return newGroupTransient(tgt).Continue()
}
func (tgt *Target) Next() error {
return newGroupTransient(tgt).Next()
}
func (tgt *Target) Step() error {
return newGroupTransient(tgt).Step()
}
func (tgt *Target) StepOut() error {
return newGroupTransient(tgt).StepOut()
}
func (tgt *Target) ChangeDirection(dir Direction) error {
return tgt.recman.ChangeDirection(dir)
}
func (tgt *Target) StepInstruction() error {
return newGroupTransient(tgt).StepInstruction()
}
func (tgt *Target) Recorded() (bool, string) {
return tgt.recman.Recorded()
}

@ -272,16 +272,17 @@ func findFileLocation(p *proc.Target, t *testing.T, file string, lineno int) uin
func TestHalt(t *testing.T) {
stopChan := make(chan interface{}, 1)
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFunctionBreakpoint(p, t, "main.loop")
assertNoError(p.Continue(), t, "Continue")
assertNoError(grp.Continue(), t, "Continue")
resumeChan := make(chan struct{}, 1)
go func() {
<-resumeChan
time.Sleep(100 * time.Millisecond)
stopChan <- p.RequestManualStop()
stopChan <- grp.RequestManualStop()
}()
p.ResumeNotify(resumeChan)
assertNoError(p.Continue(), t, "Continue")
grp.ResumeNotify(resumeChan)
assertNoError(grp.Continue(), t, "Continue")
retVal := <-stopChan
if err, ok := retVal.(error); ok && err != nil {
@ -2064,6 +2065,7 @@ func TestCmdLineArgs(t *testing.T) {
func TestIssue462(t *testing.T) {
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
go func() {
// Wait for program to start listening.
for {
@ -2075,10 +2077,10 @@ func TestIssue462(t *testing.T) {
time.Sleep(50 * time.Millisecond)
}
p.RequestManualStop()
grp.RequestManualStop()
}()
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
_, err := proc.ThreadStacktrace(p.CurrentThread(), 40)
assertNoError(err, t, "Stacktrace()")
})
@ -2638,22 +2640,23 @@ func TestNextBreakpoint(t *testing.T) {
func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
protest.AllowRecording(t)
withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) {
p.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints
grp := proc.NewGroup(p)
grp.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints
bp := setFileBreakpoint(p, t, fixture.Source, 34)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
p.ClearBreakpoint(bp.Addr)
// Next should be interrupted by a tracepoint on the same goroutine.
bp = setFileBreakpoint(p, t, fixture.Source, 14)
bp.Logical.Tracepoint = true
assertNoError(p.Next(), t, "Next()")
assertNoError(grp.Next(), t, "Next()")
assertLineNumber(p, t, 14, "wrong line number")
if !p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine")
}
// Continue to complete next.
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
assertLineNumber(p, t, 35, "wrong line number")
if p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("has internal breakpoints after completing next")
@ -3713,17 +3716,18 @@ func TestIssue1101(t *testing.T) {
func TestIssue1145(t *testing.T) {
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFileBreakpoint(p, t, fixture.Source, 18)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1)
p.ResumeNotify(resumeChan)
grp.ResumeNotify(resumeChan)
go func() {
<-resumeChan
time.Sleep(100 * time.Millisecond)
p.RequestManualStop()
grp.RequestManualStop()
}()
assertNoError(p.Next(), t, "Next()")
assertNoError(grp.Next(), t, "Next()")
if p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("has internal breakpoints after manual stop request")
}
@ -3732,18 +3736,19 @@ func TestIssue1145(t *testing.T) {
func TestHaltKeepsSteppingBreakpoints(t *testing.T) {
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
p.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints
grp := proc.NewGroup(p)
grp.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints
setFileBreakpoint(p, t, fixture.Source, 18)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1)
p.ResumeNotify(resumeChan)
grp.ResumeNotify(resumeChan)
go func() {
<-resumeChan
time.Sleep(100 * time.Millisecond)
p.RequestManualStop()
grp.RequestManualStop()
}()
assertNoError(p.Next(), t, "Next()")
assertNoError(grp.Next(), t, "Next()")
if !p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("does not have internal breakpoints after manual stop request")
}
@ -4346,11 +4351,12 @@ func TestIssue1374(t *testing.T) {
// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFileBreakpoint(p, t, fixture.Source, 7)
assertNoError(p.Continue(), t, "First Continue")
assertNoError(grp.Continue(), t, "First Continue")
assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),")
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
err := p.Continue()
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
err := grp.Continue()
if _, isexited := err.(proc.ErrProcessExited); !isexited {
regs, _ := p.CurrentThread().Registers()
f, l, _ := p.BinInfo().PCToLine(regs.PC())
@ -4577,14 +4583,15 @@ func TestCallConcurrent(t *testing.T) {
skipOn(t, "broken", "freebsd")
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
bp := setFileBreakpoint(p, t, fixture.Source, 24)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
//_, err := p.ClearBreakpoint(bp.Addr)
//assertNoError(err, t, "ClearBreakpoint() returned an error")
gid1 := p.SelectedGoroutine().ID
t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "Foo(10, 1)", normalLoadConfig, false), t, "EvalExpressionWithCalls()")
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 1)", normalLoadConfig, false), t, "EvalExpressionWithCalls()")
returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
@ -4599,7 +4606,7 @@ func TestCallConcurrent(t *testing.T) {
gid2 := p.SelectedGoroutine().ID
t.Logf("starting second injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID())
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "Foo(10, 2)", normalLoadConfig, false), t, "EvalExpressioniWithCalls")
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "Foo(10, 2)", normalLoadConfig, false), t, "EvalExpressioniWithCalls")
for {
returned += testCallConcurrentCheckReturns(p, t, gid1, gid2)
@ -4607,10 +4614,10 @@ func TestCallConcurrent(t *testing.T) {
break
}
t.Logf("Continuing... %d", returned)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
}
p.Continue()
grp.Continue()
})
}
@ -4964,8 +4971,9 @@ func TestIssue1925(t *testing.T) {
// altering the state of the target process.
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()")
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
grp := proc.NewGroup(p)
assertNoError(grp.Continue(), t, "Continue()")
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc)
if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source {
t.Errorf("wrong location for selected goroutine after call: %s:%d", loc.File, loc.Line)
@ -5059,31 +5067,32 @@ func TestStepoutOneliner(t *testing.T) {
func TestRequestManualStopWhileStopped(t *testing.T) {
// Requesting a manual stop while stopped shouldn't cause problems (issue #2138).
withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
resumed := make(chan struct{})
setFileBreakpoint(p, t, fixture.Source, 8)
assertNoError(p.Continue(), t, "Continue() 1")
p.ResumeNotify(resumed)
assertNoError(grp.Continue(), t, "Continue() 1")
grp.ResumeNotify(resumed)
go func() {
<-resumed
time.Sleep(1 * time.Second)
p.RequestManualStop()
grp.RequestManualStop()
}()
t.Logf("at time.Sleep call")
assertNoError(p.Continue(), t, "Continue() 2")
assertNoError(grp.Continue(), t, "Continue() 2")
t.Logf("manually stopped")
p.RequestManualStop()
p.RequestManualStop()
p.RequestManualStop()
grp.RequestManualStop()
grp.RequestManualStop()
grp.RequestManualStop()
resumed = make(chan struct{})
p.ResumeNotify(resumed)
grp.ResumeNotify(resumed)
go func() {
<-resumed
time.Sleep(1 * time.Second)
p.RequestManualStop()
grp.RequestManualStop()
}()
t.Logf("resuming sleep")
assertNoError(p.Continue(), t, "Continue() 3")
assertNoError(grp.Continue(), t, "Continue() 3")
t.Logf("done")
})
}
@ -5546,9 +5555,10 @@ func TestWatchpointCounts(t *testing.T) {
func TestManualStopWhileStopped(t *testing.T) {
// Checks that RequestManualStop sent to a stopped thread does not cause the target process to die.
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
asyncCont := func(done chan struct{}) {
defer close(done)
err := p.Continue()
err := grp.Continue()
t.Logf("%v\n", err)
if err != nil {
panic(err)
@ -5571,9 +5581,9 @@ func TestManualStopWhileStopped(t *testing.T) {
done := make(chan struct{})
go asyncCont(done)
time.Sleep(1 * time.Second)
p.RequestManualStop()
grp.RequestManualStop()
time.Sleep(1 * time.Second)
p.RequestManualStop()
grp.RequestManualStop()
time.Sleep(1 * time.Second)
<-done
}
@ -5581,11 +5591,11 @@ func TestManualStopWhileStopped(t *testing.T) {
t.Logf("Continue %d (fast)", i)
rch := make(chan struct{})
done := make(chan struct{})
p.ResumeNotify(rch)
grp.ResumeNotify(rch)
go asyncCont(done)
<-rch
p.RequestManualStop()
p.RequestManualStop()
grp.RequestManualStop()
grp.RequestManualStop()
<-done
}
})
@ -5865,6 +5875,8 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) {
mainfn := p.BinInfo().LookupFunc["main.main"]
grp := proc.NewGroup(p)
// Find JNZ instruction on line :14
var addr uint64
text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End)
@ -5886,7 +5898,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
assertNoError(err, t, "SetBreakpoint")
// Continue to breakpoint
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
assertLineNumber(p, t, 14, "expected line :14")
// Save RFLAGS register
@ -5894,7 +5906,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
t.Logf("rflags before = %#x", rflagsBeforeCall)
// Inject call to main.g()
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "g()", normalLoadConfig, true), t, "Call")
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "g()", normalLoadConfig, true), t, "Call")
// Check RFLAGS register after the call
rflagsAfterCall := p.BinInfo().Arch.RegistersToDwarfRegisters(0, getRegisters(p, t)).Uint64Val(regnum.AMD64_Rflags)
@ -5905,7 +5917,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
}
// Single step and check where we end up
assertNoError(p.Step(), t, "Step()")
assertNoError(grp.Step(), t, "Step()")
assertLineNumber(p, t, 17, "expected line :17") // since we passed "0" as argument we should be going into the false branch at line :17
})
}

@ -37,24 +37,25 @@ func TestIssue419(t *testing.T) {
// SIGINT directed at the inferior should be passed along not swallowed by delve
withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1)
go func() {
time.Sleep(500 * time.Millisecond)
<-resumeChan
if p.Pid() <= 0 {
// if we don't stop the inferior the test will never finish
p.RequestManualStop()
err := p.Detach(true)
grp.RequestManualStop()
err := grp.Detach(true)
errChan <- errIssue419{pid: p.Pid(), err: err}
return
}
err := syscall.Kill(p.Pid(), syscall.SIGINT)
errChan <- errIssue419{pid: p.Pid(), err: err}
}()
p.ResumeNotify(resumeChan)
errChan <- p.Continue()
grp.ResumeNotify(resumeChan)
errChan <- grp.Continue()
})
for i := 0; i < 2; i++ {

@ -72,7 +72,7 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi
retbreaklet.watchpoint = watchpoint
retbreaklet.callback = woos
if recorded, _ := t.Recorded(); recorded && retframe.Current.Fn != nil {
if recorded, _ := t.recman.Recorded(); recorded && retframe.Current.Fn != nil {
// Must also set a breakpoint on the call instruction immediately
// preceding retframe.Current.PC, because the watchpoint could also go out
// of scope while we are running backwards.

@ -36,10 +36,10 @@ const (
// Target represents the process being debugged.
type Target struct {
Process
RecordingManipulation
proc ProcessInternal
recman RecordingManipulationInternal
proc ProcessInternal
recman RecordingManipulationInternal
continueOnce ContinueOnceFunc
pid int
@ -51,10 +51,6 @@ type Target struct {
// CanDump is true if core dumping is supported.
CanDump bool
// KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts)
// will keep the stepping breakpoints instead of clearing them.
KeepSteppingBreakpoints KeepSteppingBreakpoints
// currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected.
currentThread Thread
@ -84,7 +80,7 @@ type Target struct {
fakeMemoryRegistry []*compositeMemory
fakeMemoryRegistryMap map[string]*compositeMemory
cctx *ContinueOnceContext
partOfGroup bool
}
type KeepSteppingBreakpoints uint8
@ -151,6 +147,8 @@ const (
StopWatchpoint // The target process hit one or more watchpoints
)
type ContinueOnceFunc func([]ProcessInternal, *ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error)
// NewTargetConfig contains the configuration for a new Target object,
type NewTargetConfig struct {
Path string // path of the main executable
@ -158,6 +156,7 @@ type NewTargetConfig struct {
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)
ContinueOnce ContinueOnceFunc
}
// DisableAsyncPreemptEnv returns a process environment (like os.Environ)
@ -200,7 +199,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
currentThread: currentThread,
CanDump: cfg.CanDump,
pid: pid,
cctx: &ContinueOnceContext{},
continueOnce: cfg.ContinueOnce,
}
if recman, ok := p.(RecordingManipulationInternal); ok {
@ -208,7 +207,6 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo
} else {
t.recman = &dummyRecordingManipulation{}
}
t.RecordingManipulation = t.recman
g, _ := GetG(currentThread)
t.selectedGoroutine = g
@ -281,12 +279,18 @@ func (t *Target) ClearCaches() {
}
}
// Restart will start the process over from the location specified by the "from" locspec.
// Restart will start the process group over from the location specified by the "from" locspec.
// This is only useful for recorded targets.
// Restarting of a normal process happens at a higher level (debugger.Restart).
func (t *Target) Restart(from string) error {
t.ClearCaches()
currentThread, err := t.recman.Restart(t.cctx, from)
func (grp *TargetGroup) Restart(from string) error {
if len(grp.targets) != 1 {
panic("multiple targets not implemented")
}
for _, t := range grp.targets {
t.ClearCaches()
}
t := grp.Selected
currentThread, err := t.recman.Restart(grp.cctx, from)
if err != nil {
return err
}
@ -333,11 +337,11 @@ func (p *Target) SwitchThread(tid int) error {
return fmt.Errorf("thread %d does not exist", tid)
}
// Detach will detach the target from the underylying process.
// detach will detach the target from the underylying process.
// This means the debugger will no longer receive events from the process
// we were previously debugging.
// If kill is true then the process will be killed when we detach.
func (t *Target) Detach(kill bool) error {
func (t *Target) detach(kill bool) error {
if !kill {
if t.asyncPreemptChanged {
setAsyncPreemptOff(t, t.asyncPreemptOff)
@ -475,17 +479,17 @@ func (t *Target) GetBufferedTracepoints() []*UProbeTraceResult {
}
// ResumeNotify specifies a channel that will be closed the next time
// Continue finishes resuming the target.
func (t *Target) ResumeNotify(ch chan<- struct{}) {
// Continue finishes resuming the targets.
func (t *TargetGroup) ResumeNotify(ch chan<- struct{}) {
t.cctx.ResumeChan = ch
}
// RequestManualStop attempts to stop all the process' threads.
func (t *Target) RequestManualStop() error {
// RequestManualStop attempts to stop all the processes' threads.
func (t *TargetGroup) RequestManualStop() error {
t.cctx.StopMu.Lock()
defer t.cctx.StopMu.Unlock()
t.cctx.manualStopRequested = true
return t.proc.RequestManualStop(t.cctx)
return t.Selected.proc.RequestManualStop(t.cctx)
}
const (

@ -26,27 +26,32 @@ func (err *ErrNoSourceForPC) Error() string {
return fmt.Sprintf("no source for PC %#x", err.pc)
}
// Next continues execution until the next source line.
func (dbp *Target) Next() (err error) {
if _, err := dbp.Valid(); err != nil {
// Next resumes the processes in the group, continuing the selected target
// until the next source line.
func (grp *TargetGroup) Next() (err error) {
if _, err := grp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
}
if err = next(dbp, false, false); err != nil {
dbp.ClearSteppingBreakpoints()
if err = next(grp.Selected, false, false); err != nil {
grp.Selected.ClearSteppingBreakpoints()
return
}
return dbp.Continue()
return grp.Continue()
}
// Continue continues execution of the debugged
// process. It will continue until it hits a breakpoint
// processes. It will continue until it hits a breakpoint
// or is otherwise stopped.
func (dbp *Target) Continue() error {
func (grp *TargetGroup) Continue() error {
if len(grp.targets) != 1 {
panic("multiple targets not implemented")
}
dbp := grp.Selected
if _, err := dbp.Valid(); err != nil {
return err
}
@ -56,29 +61,29 @@ func (dbp *Target) Continue() error {
}
dbp.Breakpoints().WatchOutOfScope = nil
dbp.clearHardcodedBreakpoints()
dbp.cctx.CheckAndClearManualStopRequest()
grp.cctx.CheckAndClearManualStopRequest()
defer func() {
// Make sure we clear internal breakpoints if we simultaneously receive a
// manual stop request and hit a breakpoint.
if dbp.cctx.CheckAndClearManualStopRequest() {
if grp.cctx.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.clearHardcodedBreakpoints()
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
dbp.ClearSteppingBreakpoints()
}
}
}()
for {
if dbp.cctx.CheckAndClearManualStopRequest() {
if grp.cctx.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual
dbp.clearHardcodedBreakpoints()
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
dbp.ClearSteppingBreakpoints()
}
return nil
}
dbp.ClearCaches()
trapthread, stopReason, contOnceErr := dbp.proc.ContinueOnce(dbp.cctx)
trapthread, stopReason, contOnceErr := grp.continueOnce([]ProcessInternal{grp.targets[0].proc}, grp.cctx)
dbp.StopReason = stopReason
threads := dbp.ThreadList()
@ -136,7 +141,7 @@ func (dbp *Target) Continue() error {
if err := conditionErrors(threads); err != nil {
return err
}
if dbp.GetDirection() == Forward {
if grp.GetDirection() == Forward {
text, err := disassembleCurrentInstruction(dbp, curthread, 0)
if err != nil {
return err
@ -155,7 +160,7 @@ func (dbp *Target) Continue() error {
if err := dbp.ClearSteppingBreakpoints(); err != nil {
return err
}
return dbp.StepInstruction()
return grp.StepInstruction()
}
} else {
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
@ -171,7 +176,7 @@ func (dbp *Target) Continue() error {
return err
}
if onNextGoroutine &&
(!isTraceOrTraceReturn(curbp.Breakpoint) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
(!isTraceOrTraceReturn(curbp.Breakpoint) || grp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
err := dbp.ClearSteppingBreakpoints()
if err != nil {
return err
@ -287,27 +292,27 @@ func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string)
}
}
// Step will continue until another source line is reached.
// Will step into functions.
func (dbp *Target) Step() (err error) {
if _, err := dbp.Valid(); err != nil {
// Step resumes the processes in the group, continuing the selected target
// until the next source line. Will step into functions.
func (grp *TargetGroup) Step() (err error) {
if _, err := grp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
}
if err = next(dbp, true, false); err != nil {
_ = dbp.ClearSteppingBreakpoints()
if err = next(grp.Selected, true, false); err != nil {
_ = grp.Selected.ClearSteppingBreakpoints()
return err
}
if bpstate := dbp.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && dbp.GetDirection() == Backward {
dbp.ClearSteppingBreakpoints()
return dbp.StepInstruction()
if bpstate := grp.Selected.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && grp.GetDirection() == Backward {
grp.Selected.ClearSteppingBreakpoints()
return grp.StepInstruction()
}
return dbp.Continue()
return grp.Continue()
}
// sameGoroutineCondition returns an expression that evaluates to true when
@ -323,17 +328,19 @@ func frameoffCondition(frame *Stackframe) ast.Expr {
return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset()))
}
// StepOut will continue until the current goroutine exits the
// function currently being executed or a deferred function is executed
func (dbp *Target) StepOut() error {
backward := dbp.GetDirection() == Backward
if _, err := dbp.Valid(); err != nil {
// StepOut resumes the processes in the group, continuing the selected target
// until until the current goroutine exits the function currently being
// executed or a deferred function is executed
func (grp *TargetGroup) StepOut() error {
backward := grp.GetDirection() == Backward
if _, err := grp.Valid(); err != nil {
return err
}
if dbp.Breakpoints().HasSteppingBreakpoints() {
if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting")
}
dbp := grp.Selected
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
@ -355,7 +362,7 @@ func (dbp *Target) StepOut() error {
}
success = true
return dbp.Continue()
return grp.Continue()
}
sameGCond := sameGoroutineCondition(selg)
@ -366,7 +373,7 @@ func (dbp *Target) StepOut() error {
}
success = true
return dbp.Continue()
return grp.Continue()
}
deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
@ -395,14 +402,15 @@ func (dbp *Target) StepOut() error {
}
success = true
return dbp.Continue()
return grp.Continue()
}
// StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread
// associated with the selected goroutine. All other
// threads will remain stopped.
func (dbp *Target) StepInstruction() (err error) {
func (grp *TargetGroup) StepInstruction() (err error) {
dbp := grp.Selected
thread := dbp.CurrentThread()
g := dbp.SelectedGoroutine()
if g != nil {
@ -412,7 +420,7 @@ func (dbp *Target) StepInstruction() (err error) {
sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err
}
return dbp.Continue()
return grp.Continue()
}
thread = g.Thread
}
@ -464,7 +472,7 @@ func (dbp *Target) StepInstruction() (err error) {
// when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call.
func next(dbp *Target, stepInto, inlinedStepOut bool) error {
backward := dbp.GetDirection() == Backward
backward := dbp.recman.GetDirection() == Backward
selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread)
@ -1086,7 +1094,7 @@ func (tgt *Target) clearHardcodedBreakpoints() {
func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error {
mem := tgt.Memory()
arch := tgt.BinInfo().Arch
recorded, _ := tgt.Recorded()
recorded, _ := tgt.recman.Recorded()
isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 {
for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} {
@ -1150,7 +1158,7 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa
switch {
case loc.Fn.Name == "runtime.breakpoint":
if recorded, _ := tgt.Recorded(); recorded {
if recorded, _ := tgt.recman.Recorded(); recorded {
setHardcodedBreakpoint(thread, loc)
continue
}

123
pkg/proc/target_group.go Normal file

@ -0,0 +1,123 @@
package proc
import (
"fmt"
"strings"
)
// TargetGroup reperesents 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
return &TargetGroup{
RecordingManipulation: t.recman,
targets: []*Target{t},
Selected: t,
cctx: &ContinueOnceContext{},
recman: t.recman,
LogicalBreakpoints: t.Breakpoints().Logical,
continueOnce: t.continueOnce,
}
}
// Targets returns a slice of targets in the group.
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
}
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
}

@ -1232,44 +1232,45 @@ func TestCallFunction(t *testing.T) {
}
withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
testCallFunctionSetBreakpoint(t, p, fixture)
assertNoError(p.Continue(), t, "Continue()")
assertNoError(grp.Continue(), t, "Continue()")
for _, tc := range testcases {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
for _, tc := range testcases112 {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcasesBefore114After112 {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
}
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
for _, tc := range testcases113 {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcases114 {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
for _, tc := range testcases117 {
testCallFunction(t, p, tc)
testCallFunction(t, grp, p, tc)
}
}
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
testCallFunction(t, grp, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
})
}
@ -1284,7 +1285,7 @@ func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest
}
}
func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc testCaseCallFunction) {
const unsafePrefix = "-unsafe "
var callExpr, varExpr string
@ -1302,7 +1303,7 @@ func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
checkEscape = false
}
t.Logf("call %q", tc.expr)
err := proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), callExpr, pnormalLoadConfig, checkEscape)
err := proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), callExpr, pnormalLoadConfig, checkEscape)
if tc.err != nil {
t.Logf("\terr = %v\n", err)
if err == nil {

@ -1558,7 +1558,7 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
}
s.send(e)
}
s.debugger.Target().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
s.debugger.TargetGroup().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
s.logToConsole("Type 'dlv help' for list of commands.")
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})

@ -61,7 +61,7 @@ type Debugger struct {
processArgs []string
targetMutex sync.Mutex
target *proc.Target
target *proc.TargetGroup
log *logrus.Entry
@ -161,7 +161,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
err = noDebugErrorWarning(err)
return nil, attachErrorMessage(d.config.AttachPid, err)
}
d.target = p
d.target = proc.NewGroup(p)
case d.config.CoreFile != "":
var p *proc.Target
@ -178,7 +178,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
err = go11DecodeErrorCheck(err)
return nil, err
}
d.target = p
d.target = proc.NewGroup(p)
if err := d.checkGoVersion(); err != nil {
d.target.Detach(true)
return nil, err
@ -197,7 +197,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
}
if p != nil {
// if p == nil and err == nil then we are doing a recording, don't touch d.target
d.target = p
d.target = proc.NewGroup(p)
}
if err := d.checkGoVersion(); err != nil {
d.target.Detach(true)
@ -225,7 +225,7 @@ func (d *Debugger) checkGoVersion() error {
// do not do anything if we are still recording
return nil
}
producer := d.target.BinInfo().Producer()
producer := d.target.Selected.BinInfo().Producer()
if producer == "" {
return nil
}
@ -235,7 +235,7 @@ func (d *Debugger) checkGoVersion() error {
func (d *Debugger) TargetGoVersion() string {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.BinInfo().Producer()
return d.target.Selected.BinInfo().Producer()
}
// Launch will start a process with the given args and working directory.
@ -285,7 +285,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
os.Exit(1)
}
d.recordingDone()
d.target = p
d.target = proc.NewGroup(p)
if err := d.checkGoVersion(); err != nil {
d.log.Error(err)
err := d.target.Detach(true)
@ -368,7 +368,7 @@ func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error)
func (d *Debugger) ProcessPid() int {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.Pid()
return d.target.Selected.Pid()
}
// LastModified returns the time that the process' executable was last
@ -376,7 +376,7 @@ func (d *Debugger) ProcessPid() int {
func (d *Debugger) LastModified() time.Time {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.BinInfo().LastModified()
return d.target.Selected.BinInfo().LastModified()
}
// FunctionReturnLocations returns all return locations
@ -387,7 +387,7 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
defer d.targetMutex.Unlock()
var (
p = d.target
p = d.target.Selected
g = p.SelectedGoroutine()
)
@ -463,12 +463,6 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
return nil, ErrCanNotRestart
}
if valid, _ := d.target.Valid(); valid && !recorded {
// Ensure the process is in a PTRACE_STOP.
if err := stopProcess(d.target.Pid()); err != nil {
return nil, err
}
}
if err := d.detach(true); err != nil {
return nil, err
}
@ -514,9 +508,9 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
}
discarded := []api.DiscardedBreakpoint{}
p.Breakpoints().Logical = d.target.Breakpoints().Logical
d.target = p
for _, oldBp := range d.target.Breakpoints().Logical {
p.Breakpoints().Logical = d.target.LogicalBreakpoints
d.target = proc.NewGroup(p)
for _, oldBp := range d.target.LogicalBreakpoints {
if oldBp.LogicalID < 0 || !oldBp.Enabled {
continue
}
@ -566,16 +560,19 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
goroutine *api.Goroutine
)
if d.target.SelectedGoroutine() != nil {
goroutine = api.ConvertGoroutine(d.target, d.target.SelectedGoroutine())
tgt := d.target.Selected
if tgt.SelectedGoroutine() != nil {
goroutine = api.ConvertGoroutine(tgt, tgt.SelectedGoroutine())
}
exited := false
if _, err := d.target.Valid(); err != nil {
if _, err := tgt.Valid(); err != nil {
_, exited = err.(proc.ErrProcessExited)
}
state = &api.DebuggerState{
Pid: tgt.Pid(),
SelectedGoroutine: goroutine,
Exited: exited,
}
@ -589,22 +586,23 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
}
state.Threads = append(state.Threads, th)
if thread.ThreadID() == d.target.CurrentThread().ThreadID() {
if thread.ThreadID() == tgt.CurrentThread().ThreadID() {
state.CurrentThread = th
}
}
state.NextInProgress = d.target.Breakpoints().HasSteppingBreakpoints()
state.NextInProgress = d.target.HasSteppingBreakpoints()
if recorded, _ := d.target.Recorded(); recorded {
state.When, _ = d.target.When()
}
state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope))
for _, bp := range d.target.Breakpoints().WatchOutOfScope {
abp := api.ConvertLogicalBreakpoint(bp.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
for _, t := range d.target.Targets() {
for _, bp := range t.Breakpoints().WatchOutOfScope {
abp := api.ConvertLogicalBreakpoint(bp.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
}
}
return state, nil
@ -641,6 +639,15 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
//TODO(aarzilli):
// - the calls to FindFileLocation and FindFunctionLocation need to be done on all targets
// - the Addrs slice and the Addr field need to be converted to a format
// that can specify to which target an address belongs when there are
// multiple targets (but this must happen in a backwards compatible way)
panic("multiple targets not implemented")
}
var (
addrs []uint64
err error
@ -660,16 +667,19 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
if runtime.GOOS == "windows" {
// Accept fileName which is case-insensitive and slash-insensitive match
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
for _, symFile := range d.target.BinInfo().Sources {
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
fileName = symFile
break
caseInsensitiveSearch:
for _, t := range d.target.Targets() {
for _, symFile := range t.BinInfo().Sources {
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
fileName = symFile
break caseInsensitiveSearch
}
}
}
}
addrs, err = proc.FindFileLocation(d.target, fileName, requestedBp.Line)
addrs, err = proc.FindFileLocation(d.target.Selected, fileName, requestedBp.Line)
case len(requestedBp.FunctionName) > 0:
addrs, err = proc.FindFunctionLocation(d.target, requestedBp.FunctionName, requestedBp.Line)
addrs, err = proc.FindFunctionLocation(d.target.Selected, requestedBp.FunctionName, requestedBp.Line)
case len(requestedBp.Addrs) > 0:
addrs = requestedBp.Addrs
default:
@ -704,7 +714,10 @@ func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
// createLogicalBreakpoint creates one physical breakpoint for each address
// in addrs and associates all of them with the same logical breakpoint.
func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Breakpoint, id int) (*api.Breakpoint, error) {
p := d.target
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
abp := d.convertBreakpoint(lbp)
@ -752,13 +765,17 @@ func createLogicalBreakpoint(d *Debugger, addrs []uint64, requestedBp *api.Break
}
func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error {
addrs, err := proc.FindFileLocation(d.target, lbp.File, lbp.Line)
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
addrs, err := proc.FindFileLocation(p, lbp.File, lbp.Line)
if err != nil {
return err
}
bps := make([]*proc.Breakpoint, len(addrs))
for i := range addrs {
bps[i], err = d.target.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
bps[i], err = p.SetBreakpoint(lbp.LogicalID, addrs[i], proc.UserBreakpoint, nil)
if err != nil {
break
}
@ -771,7 +788,7 @@ func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error
if bp == nil {
continue
}
if err1 := d.target.ClearBreakpoint(bp.Addr); err1 != nil {
if err1 := p.ClearBreakpoint(bp.Addr); err1 != nil {
return fmt.Errorf("error while creating breakpoint: %v, additionally the breakpoint could not be properly rolled back: %v", err, err1)
}
}
@ -781,12 +798,16 @@ func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error
}
func (d *Debugger) clearPhysicalBreakpoints(id int) error {
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
var errs []error
n := 0
for _, bp := range d.target.Breakpoints().M {
for _, bp := range p.Breakpoints().M {
if bp.LogicalID() == id {
n++
err := d.target.ClearBreakpoint(bp.Addr)
err := p.ClearBreakpoint(bp.Addr)
if err != nil {
errs = append(errs, err)
}
@ -817,15 +838,21 @@ func isBreakpointExistsErr(err error) bool {
func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.SetEBPFTracepoint(fnName)
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
return p.SetEBPFTracepoint(fnName)
}
// amendBreakpoint will update the breakpoint with the matching ID.
// It also enables or disables the breakpoint.
// We can consume this function to avoid locking a goroutine.
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error {
original := d.target.Breakpoints().Logical[amend.ID]
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
original := d.target.LogicalBreakpoints[amend.ID]
if original == nil {
return fmt.Errorf("no breakpoint with ID %d", amend.ID)
}
@ -956,15 +983,20 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint
// clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) {
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
if requestedBp.ID <= 0 {
bp := d.target.Breakpoints().M[requestedBp.Addr]
bp := p.Breakpoints().M[requestedBp.Addr]
requestedBp.ID = bp.LogicalID()
}
lbp := d.target.Breakpoints().Logical[requestedBp.ID]
lbp := d.target.LogicalBreakpoints[requestedBp.ID]
clearedBp := d.convertBreakpoint(lbp)
delete(d.target.Breakpoints().Logical, requestedBp.ID)
delete(d.target.LogicalBreakpoints, requestedBp.ID)
err := d.clearPhysicalBreakpoints(requestedBp.ID)
if err != nil {
@ -1006,10 +1038,14 @@ func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
abps := []*api.Breakpoint{}
if all {
for _, bp := range d.target.Breakpoints().M {
for _, bp := range p.Breakpoints().M {
var abp *api.Breakpoint
if bp.Logical != nil {
abp = api.ConvertLogicalBreakpoint(bp.Logical)
@ -1021,7 +1057,7 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
abps = append(abps, abp)
}
} else {
for _, lbp := range d.target.Breakpoints().Logical {
for _, lbp := range d.target.LogicalBreakpoints {
abps = append(abps, d.convertBreakpoint(lbp))
}
}
@ -1032,7 +1068,7 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
lbp := d.target.Breakpoints().Logical[id]
lbp := d.target.LogicalBreakpoints[id]
if lbp == nil {
return nil
}
@ -1040,8 +1076,13 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
}
func (d *Debugger) findBreakpoint(id int) []*proc.Breakpoint {
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
var bps []*proc.Breakpoint
for _, bp := range d.target.Breakpoints().M {
for _, bp := range p.Breakpoints().M {
if bp.LogicalID() == id {
bps = append(bps, bp)
}
@ -1057,7 +1098,7 @@ func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
}
func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
for _, lbp := range d.target.Breakpoints().Logical {
for _, lbp := range d.target.LogicalBreakpoints {
if lbp.Name == name {
return d.convertBreakpoint(lbp)
}
@ -1067,12 +1108,17 @@ func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
// CreateWatchpoint creates a watchpoint on the specified expression.
func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) {
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
s, err := proc.ConvertEvalScope(p, goid, frame, deferredCall)
if err != nil {
return nil, err
}
d.breakpointIDCounter++
bp, err := d.target.SetWatchpoint(d.breakpointIDCounter, s, expr, proc.WatchType(wtype), nil)
bp, err := p.SetWatchpoint(d.breakpointIDCounter, s, expr, proc.WatchType(wtype), nil)
if err != nil {
return nil, err
}
@ -1116,7 +1162,7 @@ func (d *Debugger) FindGoroutine(id int) (*proc.G, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return proc.FindGoroutine(d.target, id)
return proc.FindGoroutine(d.target.Selected, id)
}
func (d *Debugger) setRunning(running bool) {
@ -1184,9 +1230,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
if command.ReturnInfoLoadConfig == nil {
return nil, errors.New("can not call function with nil ReturnInfoLoadConfig")
}
g := d.target.SelectedGoroutine()
g := d.target.Selected.SelectedGoroutine()
if command.GoroutineID > 0 {
g, err = proc.FindGoroutine(d.target, command.GoroutineID)
g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
if err != nil {
return nil, err
}
@ -1248,14 +1294,14 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
err = d.target.StepOut()
case api.SwitchThread:
d.log.Debugf("switching to thread %d", command.ThreadID)
err = d.target.SwitchThread(command.ThreadID)
err = d.target.Selected.SwitchThread(command.ThreadID)
withBreakpointInfo = false
case api.SwitchGoroutine:
d.log.Debugf("switching to goroutine %d", command.GoroutineID)
var g *proc.G
g, err = proc.FindGoroutine(d.target, command.GoroutineID)
g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
if err == nil {
err = d.target.SwitchGoroutine(g)
err = d.target.Selected.SwitchGoroutine(g)
}
withBreakpointInfo = false
case api.Halt:
@ -1266,7 +1312,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
if err != nil {
if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
state := &api.DebuggerState{}
state.Pid = d.target.Pid()
state.Pid = d.target.Selected.Pid()
state.Exited = true
state.ExitStatus = pe.Status
state.Err = pe
@ -1298,6 +1344,8 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
}
func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error {
//TODO(aarzilli): this doesn't work when there are multiple targets because the state.Threads slice will contain threads from all targets, not just d.target .Selected
if state == nil {
return nil
}
@ -1312,15 +1360,15 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
state.Threads[i].BreakpointInfo = bpi
if bp.Goroutine {
g, err := proc.GetG(d.target.CurrentThread())
g, err := proc.GetG(d.target.Selected.CurrentThread())
if err != nil {
return err
}
bpi.Goroutine = api.ConvertGoroutine(d.target, g)
bpi.Goroutine = api.ConvertGoroutine(d.target.Selected, g)
}
if bp.Stacktrace > 0 {
rawlocs, err := proc.ThreadStacktrace(d.target.CurrentThread(), bp.Stacktrace)
rawlocs, err := proc.ThreadStacktrace(d.target.Selected.CurrentThread(), bp.Stacktrace)
if err != nil {
return err
}
@ -1330,7 +1378,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
}
}
thread, found := d.target.FindThread(state.Threads[i].ID)
thread, found := d.target.Selected.FindThread(state.Threads[i].ID)
if !found {
return fmt.Errorf("could not find thread %d", state.Threads[i].ID)
}
@ -1340,7 +1388,7 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
continue
}
s, err := proc.GoroutineScope(d.target, thread)
s, err := proc.GoroutineScope(d.target.Selected, thread)
if err != nil {
return err
}
@ -1382,14 +1430,33 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
}
files := []string{}
for _, f := range d.target.BinInfo().Sources {
if regex.Match([]byte(f)) {
files = append(files, f)
for _, t := range d.target.Targets() {
for _, f := range t.BinInfo().Sources {
if regex.Match([]byte(f)) {
files = append(files, f)
}
}
}
sort.Strings(files)
files = uniq(files)
return files, nil
}
func uniq(s []string) []string {
if len(s) <= 0 {
return s
}
src, dst := 1, 1
for src < len(s) {
if s[src] != s[dst-1] {
s[dst] = s[src]
dst++
}
src++
}
return s[:dst]
}
// Functions returns a list of functions in the target process.
func (d *Debugger) Functions(filter string) ([]string, error) {
d.targetMutex.Lock()
@ -1401,11 +1468,15 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
}
funcs := []string{}
for _, f := range d.target.BinInfo().Functions {
if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
for _, t := range d.target.Targets() {
for _, f := range t.BinInfo().Functions {
if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
}
}
}
sort.Strings(funcs)
funcs = uniq(funcs)
return funcs, nil
}
@ -1419,17 +1490,22 @@ func (d *Debugger) Types(filter string) ([]string, error) {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
types, err := d.target.BinInfo().Types()
if err != nil {
return nil, err
}
r := []string{}
r := make([]string, 0, len(types))
for _, typ := range types {
if regex.Match([]byte(typ)) {
r = append(r, typ)
for _, t := range d.target.Targets() {
types, err := t.BinInfo().Types()
if err != nil {
return nil, err
}
for _, typ := range types {
if regex.Match([]byte(typ)) {
r = append(r, typ)
}
}
}
sort.Strings(r)
r = uniq(r)
return r, nil
}
@ -1439,13 +1515,14 @@ func (d *Debugger) Types(filter string) ([]string, error) {
func (d *Debugger) PackageVariables(filter string, cfg proc.LoadConfig) ([]*proc.Variable, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
p := d.target.Selected
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
}
scope, err := proc.ThreadScope(d.target, d.target.CurrentThread())
scope, err := proc.ThreadScope(p, p.CurrentThread())
if err != nil {
return nil, err
}
@ -1467,7 +1544,7 @@ func (d *Debugger) ThreadRegisters(threadID int, floatingPoint bool) (*op.DwarfR
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
thread, found := d.target.FindThread(threadID)
thread, found := d.target.Selected.FindThread(threadID)
if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID)
}
@ -1475,7 +1552,7 @@ func (d *Debugger) ThreadRegisters(threadID int, floatingPoint bool) (*op.DwarfR
if err != nil {
return nil, err
}
return d.target.BinInfo().Arch.RegistersToDwarfRegisters(0, regs), nil
return d.target.Selected.BinInfo().Arch.RegistersToDwarfRegisters(0, regs), nil
}
// ScopeRegisters returns registers for the specified scope.
@ -1483,7 +1560,7 @@ func (d *Debugger) ScopeRegisters(goid, frame, deferredCall int, floatingPoint b
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return nil, err
}
@ -1492,7 +1569,7 @@ func (d *Debugger) ScopeRegisters(goid, frame, deferredCall int, floatingPoint b
// DwarfRegisterToString returns the name and value representation of the given register.
func (d *Debugger) DwarfRegisterToString(i int, reg *op.DwarfRegister) (string, bool, string) {
return d.target.BinInfo().Arch.DwarfRegisterToString(i, reg)
return d.target.Selected.BinInfo().Arch.DwarfRegisterToString(i, reg)
}
// LocalVariables returns a list of the local variables.
@ -1500,7 +1577,7 @@ func (d *Debugger) LocalVariables(goid, frame, deferredCall int, cfg proc.LoadCo
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return nil, err
}
@ -1512,7 +1589,7 @@ func (d *Debugger) FunctionArguments(goid, frame, deferredCall int, cfg proc.Loa
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return nil, err
}
@ -1524,7 +1601,7 @@ func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig)
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return nil, err
}
@ -1537,7 +1614,7 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, expr strin
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return nil, err
}
@ -1558,7 +1635,7 @@ func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, val
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
if err != nil {
return err
}
@ -1569,7 +1646,7 @@ func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, val
func (d *Debugger) Goroutines(start, count int) ([]*proc.G, int, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return proc.GoroutinesInfo(d.target, start, count)
return proc.GoroutinesInfo(d.target.Selected, start, count)
}
// FilterGoroutines returns the goroutines in gs that satisfy the specified filters.
@ -1583,7 +1660,7 @@ func (d *Debugger) FilterGoroutines(gs []*proc.G, filters []api.ListGoroutinesFi
for _, g := range gs {
ok := true
for i := range filters {
if !matchGoroutineFilter(d.target, g, &filters[i]) {
if !matchGoroutineFilter(d.target.Selected, g, &filters[i]) {
ok = false
break
}
@ -1663,13 +1740,13 @@ func (d *Debugger) GroupGoroutines(gs []*proc.G, group *api.GoroutineGroupingOpt
case api.GoroutineGoLoc:
key = formatLoc(g.Go())
case api.GoroutineStartLoc:
key = formatLoc(g.StartLoc(d.target))
key = formatLoc(g.StartLoc(d.target.Selected))
case api.GoroutineLabel:
key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey])
case api.GoroutineRunning:
key = fmt.Sprintf("running=%v", g.Thread != nil)
case api.GoroutineUser:
key = fmt.Sprintf("user=%v", !g.System(d.target))
key = fmt.Sprintf("user=%v", !g.System(d.target.Selected))
}
if len(groupMembers[key]) < group.MaxGroupMembers {
groupMembers[key] = append(groupMembers[key], g)
@ -1708,13 +1785,13 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, opts api.StacktraceOptions
return nil, err
}
g, err := proc.FindGoroutine(d.target, goroutineID)
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil {
return nil, err
}
if g == nil {
return proc.ThreadStacktrace(d.target.CurrentThread(), depth)
return proc.ThreadStacktrace(d.target.Selected.CurrentThread(), depth)
} else {
return g.Stacktrace(depth, proc.StacktraceOptions(opts))
}
@ -1729,7 +1806,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
return nil, err
}
g, err := proc.FindGoroutine(d.target, goroutineID)
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil {
return nil, err
}
@ -1737,7 +1814,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
return nil, errors.New("no selected goroutine")
}
ancestors, err := proc.Ancestors(d.target, g, numAncestors)
ancestors, err := proc.Ancestors(d.target.Selected, g, numAncestors)
if err != nil {
return nil, err
}
@ -1789,7 +1866,7 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
}
if cfg != nil && rawlocs[i].Current.Fn != nil {
var err error
scope := proc.FrameToScope(d.target, d.target.Memory(), nil, rawlocs[i:]...)
scope := proc.FrameToScope(d.target.Selected, d.target.Selected.Memory(), nil, rawlocs[i:]...)
locals, err := scope.LocalVariables(*cfg)
if err != nil {
return nil, err
@ -1811,8 +1888,8 @@ func (d *Debugger) convertStacktrace(rawlocs []proc.Stackframe, cfg *proc.LoadCo
func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
r := make([]api.Defer, len(defers))
for i := range defers {
ddf, ddl, ddfn := defers[i].DeferredFunc(d.target)
drf, drl, drfn := d.target.BinInfo().PCToLine(defers[i].DeferPC)
ddf, ddl, ddfn := defers[i].DeferredFunc(d.target.Selected)
drf, drl, drfn := d.target.Selected.BinInfo().PCToLine(defers[i].DeferPC)
r[i] = api.Defer{
DeferredLoc: api.ConvertLocation(proc.Location{
@ -1848,7 +1925,7 @@ func (d *Debugger) CurrentPackage() (string, error) {
if _, err := d.target.Valid(); err != nil {
return "", err
}
loc, err := d.target.CurrentThread().Location()
loc, err := d.target.Selected.CurrentThread().Location()
if err != nil {
return "", err
}
@ -1863,6 +1940,13 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
//TODO(aarzilli): if there is more than one target process all must be
//searched and the addresses returned need to specify which target process
//they belong to.
panic("multiple targets not implemented")
}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
@ -1872,7 +1956,7 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
return nil, err
}
return d.findLocation(goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, loc, includeNonExecutableLines, substitutePathRules)
}
// FindLocationSpec will find the location specified by 'locStr' and 'locSpec'.
@ -1883,22 +1967,29 @@ func (d *Debugger) FindLocationSpec(goid, frame, deferredCall int, locStr string
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
//TODO(aarzilli): if there is more than one target process all must be
//searched and the addresses returned need to specify which target process
//they belong to.
panic("multiple targets not implemented")
}
if _, err := d.target.Valid(); err != nil {
return nil, err
}
return d.findLocation(goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
return d.findLocation(d.target.Selected, goid, frame, deferredCall, locStr, locSpec, includeNonExecutableLines, substitutePathRules)
}
func (d *Debugger) findLocation(goid, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
s, _ := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
func (d *Debugger) findLocation(p *proc.Target, goid, frame, deferredCall int, locStr string, locSpec locspec.LocationSpec, includeNonExecutableLines bool, substitutePathRules [][2]string) ([]api.Location, error) {
s, _ := proc.ConvertEvalScope(p, goid, frame, deferredCall)
locs, err := locSpec.Find(d.target, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
locs, err := locSpec.Find(p, d.processArgs, s, locStr, includeNonExecutableLines, substitutePathRules)
for i := range locs {
if locs[i].PC == 0 {
continue
}
file, line, fn := d.target.BinInfo().PCToLine(locs[i].PC)
file, line, fn := p.BinInfo().PCToLine(locs[i].PC)
locs[i].File = file
locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn)
@ -1917,7 +2008,7 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
}
if addr2 == 0 {
fn := d.target.BinInfo().PCToFunc(addr1)
fn := d.target.Selected.BinInfo().PCToFunc(addr1)
if fn == nil {
return nil, fmt.Errorf("address %#x does not belong to any function", addr1)
}
@ -1925,24 +2016,24 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
addr2 = fn.End
}
g, err := proc.FindGoroutine(d.target, goroutineID)
g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil {
return nil, err
}
curthread := d.target.CurrentThread()
curthread := d.target.Selected.CurrentThread()
if g != nil && g.Thread != nil {
curthread = g.Thread
}
regs, _ := curthread.Registers()
return proc.Disassemble(d.target.Memory(), regs, d.target.Breakpoints(), d.target.BinInfo(), addr1, addr2)
return proc.Disassemble(d.target.Selected.Memory(), regs, d.target.Selected.Breakpoints(), d.target.Selected.BinInfo(), addr1, addr2)
}
func (d *Debugger) AsmInstructionText(inst *proc.AsmInstruction, flavour proc.AssemblyFlavour) string {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return inst.Text(flavour, d.target.BinInfo())
return inst.Text(flavour, d.target.Selected.BinInfo())
}
// Recorded returns true if the target is a recording.
@ -1962,7 +2053,7 @@ func (d *Debugger) FindThreadReturnValues(id int, cfg proc.LoadConfig) ([]*proc.
return nil, err
}
thread, found := d.target.FindThread(id)
thread, found := d.target.Selected.FindThread(id)
if !found {
return nil, fmt.Errorf("could not find thread %d", id)
}
@ -1995,7 +2086,7 @@ func (d *Debugger) ClearCheckpoint(id int) error {
func (d *Debugger) ListDynamicLibraries() []*proc.Image {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.BinInfo().Images[1:] // skips the first image because it's the executable file
return d.target.Selected.BinInfo().Images[1:] // skips the first image because it's the executable file
}
@ -2006,7 +2097,7 @@ func (d *Debugger) ExamineMemory(address uint64, length int) ([]byte, error) {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
mem := d.target.Memory()
mem := d.target.Selected.Memory()
data := make([]byte, length)
n, err := mem.ReadMemory(data, address)
if err != nil {
@ -2038,7 +2129,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
}
if !d.isRecording() && !d.IsRunning() {
out.TargetGoVersion = d.target.BinInfo().Producer()
out.TargetGoVersion = d.target.Selected.BinInfo().Producer()
}
out.MinSupportedVersionOfGo = fmt.Sprintf("%d.%d.0", goversion.MinSupportedVersionOfGoMajor, goversion.MinSupportedVersionOfGoMinor)
@ -2053,7 +2144,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
func (d *Debugger) ListPackagesBuildInfo(includeFiles bool) []*proc.PackageBuildInfo {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.BinInfo().ListPackagesBuildInfo(includeFiles)
return d.target.Selected.BinInfo().ListPackagesBuildInfo(includeFiles)
}
// StopRecording stops a recording (if one is in progress)
@ -2072,7 +2163,7 @@ func (d *Debugger) StopRecording() error {
func (d *Debugger) StopReason() proc.StopReason {
d.targetMutex.Lock()
defer d.targetMutex.Unlock()
return d.target.StopReason
return d.target.Selected.StopReason
}
// LockTarget acquires the target mutex.
@ -2090,7 +2181,9 @@ func (d *Debugger) DumpStart(dest string) error {
d.targetMutex.Lock()
// targetMutex will only be unlocked when the dump is done
if !d.target.CanDump {
//TODO(aarzilli): what do we do if the user switches to a different target after starting a dump but before it's finished?
if !d.target.Selected.CanDump {
d.targetMutex.Unlock()
return ErrCoreDumpNotSupported
}
@ -2120,7 +2213,7 @@ func (d *Debugger) DumpStart(dest string) error {
d.dumpState.Err = nil
go func() {
defer d.targetMutex.Unlock()
d.target.Dump(fh, 0, &d.dumpState)
d.target.Selected.Dump(fh, 0, &d.dumpState)
}()
return nil
@ -2157,11 +2250,15 @@ func (d *Debugger) DumpCancel() error {
}
func (d *Debugger) Target() *proc.Target {
return d.target.Selected
}
func (d *Debugger) TargetGroup() *proc.TargetGroup {
return d.target
}
func (d *Debugger) BuildID() string {
return d.target.BinInfo().BuildID
return d.target.Selected.BinInfo().BuildID
}
func (d *Debugger) AttachPid() int {
@ -2169,13 +2266,13 @@ func (d *Debugger) AttachPid() int {
}
func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
traces := d.target.GetBufferedTracepoints()
traces := d.target.Selected.GetBufferedTracepoints()
if traces == nil {
return nil
}
results := make([]api.TracepointResult, len(traces))
for i, trace := range traces {
f, l, fn := d.target.BinInfo().PCToLine(uint64(trace.FnAddr))
f, l, fn := d.target.Selected.BinInfo().PCToLine(uint64(trace.FnAddr))
results[i].FunctionName = fn.Name
results[i].Line = l

@ -2,14 +2,9 @@ package debugger
import (
"fmt"
sys "golang.org/x/sys/unix"
)
func attachErrorMessage(pid int, err error) error {
//TODO: mention certificates?
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
}
func stopProcess(pid int) error {
return sys.Kill(pid, sys.SIGSTOP)
}

@ -2,13 +2,8 @@ package debugger
import (
"fmt"
sys "golang.org/x/sys/unix"
)
func attachErrorMessage(pid int, err error) error {
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
}
func stopProcess(pid int) error {
return sys.Kill(pid, sys.SIGSTOP)
}

@ -5,8 +5,6 @@ import (
"io/ioutil"
"os"
"syscall"
sys "golang.org/x/sys/unix"
)
//lint:file-ignore ST1005 errors here can be capitalized
@ -32,7 +30,3 @@ func attachErrorMessage(pid int, err error) error {
}
return fallbackerr
}
func stopProcess(pid int) error {
return sys.Kill(pid, sys.SIGSTOP)
}

@ -14,13 +14,6 @@ func attachErrorMessage(pid int, err error) error {
return fmt.Errorf("could not attach to pid %d: %s", pid, err)
}
func stopProcess(pid int) error {
// We cannot gracefully stop a process on Windows,
// so just ignore this request and let `Detach` kill
// the process.
return nil
}
func verifyBinaryFormat(exePath string) error {
f, err := os.Open(exePath)
if err != nil {