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

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

@ -256,8 +256,9 @@ func TestCore(t *testing.T) {
t.Skip("disabled on linux, Github Actions, with PIE buildmode") t.Skip("disabled on linux, Github Actions, with PIE buildmode")
} }
p := withCoreFile(t, "panic", "") p := withCoreFile(t, "panic", "")
grp := proc.NewGroup(p)
recorded, _ := p.Recorded() recorded, _ := grp.Recorded()
if !recorded { if !recorded {
t.Fatalf("expecting recorded to be true") 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'. // EvalExpressionWithCalls is like EvalExpression but allows function calls in 'expr'.
// Because this can only be done in the current goroutine, unlike // Because this can only be done in the current goroutine, unlike
// EvalExpression, EvalExpressionWithCalls is not a method of EvalScope. // 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() bi := t.BinInfo()
if !t.SupportsFunctionCalls() { if !t.SupportsFunctionCalls() {
return errFuncCallUnsupportedBackend return errFuncCallUnsupportedBackend
@ -201,7 +202,7 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
contReq, ok := <-continueRequest contReq, ok := <-continueRequest
if contReq.cont { if contReq.cont {
return t.Continue() return grp.Continue()
} }
return finishEvalExpressionWithCalls(t, g, contReq, ok) return finishEvalExpressionWithCalls(t, g, contReq, ok)

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

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

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

@ -172,9 +172,11 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error {
return dbp.memthread.clearSoftwareBreakpoint(bp) return dbp.memthread.clearSoftwareBreakpoint(bp)
} }
// ContinueOnce will continue the target until it stops. func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
// This could be the result of a breakpoint or signal. if len(procs) != 1 {
func (dbp *nativeProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { panic("not implemented")
}
dbp := procs[0].(*nativeProcess)
if dbp.exited { if dbp.exited {
return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid} 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 // See: https://go-review.googlesource.com/c/go/+/208126
DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"),
StopReason: stopReason, StopReason: stopReason,
CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows", CanDump: runtime.GOOS == "linux" || runtime.GOOS == "windows",
ContinueOnce: continueOnce,
}) })
if err != nil { if err != nil {
return nil, err 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) { func TestHalt(t *testing.T) {
stopChan := make(chan interface{}, 1) stopChan := make(chan interface{}, 1)
withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFunctionBreakpoint(p, t, "main.loop") setFunctionBreakpoint(p, t, "main.loop")
assertNoError(p.Continue(), t, "Continue") assertNoError(grp.Continue(), t, "Continue")
resumeChan := make(chan struct{}, 1) resumeChan := make(chan struct{}, 1)
go func() { go func() {
<-resumeChan <-resumeChan
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
stopChan <- p.RequestManualStop() stopChan <- grp.RequestManualStop()
}() }()
p.ResumeNotify(resumeChan) grp.ResumeNotify(resumeChan)
assertNoError(p.Continue(), t, "Continue") assertNoError(grp.Continue(), t, "Continue")
retVal := <-stopChan retVal := <-stopChan
if err, ok := retVal.(error); ok && err != nil { if err, ok := retVal.(error); ok && err != nil {
@ -2064,6 +2065,7 @@ func TestCmdLineArgs(t *testing.T) {
func TestIssue462(t *testing.T) { func TestIssue462(t *testing.T) {
skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error
withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
go func() { go func() {
// Wait for program to start listening. // Wait for program to start listening.
for { for {
@ -2075,10 +2077,10 @@ func TestIssue462(t *testing.T) {
time.Sleep(50 * time.Millisecond) 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) _, err := proc.ThreadStacktrace(p.CurrentThread(), 40)
assertNoError(err, t, "Stacktrace()") assertNoError(err, t, "Stacktrace()")
}) })
@ -2638,22 +2640,23 @@ func TestNextBreakpoint(t *testing.T) {
func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) { func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) {
protest.AllowRecording(t) protest.AllowRecording(t)
withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { 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) bp := setFileBreakpoint(p, t, fixture.Source, 34)
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
p.ClearBreakpoint(bp.Addr) p.ClearBreakpoint(bp.Addr)
// Next should be interrupted by a tracepoint on the same goroutine. // Next should be interrupted by a tracepoint on the same goroutine.
bp = setFileBreakpoint(p, t, fixture.Source, 14) bp = setFileBreakpoint(p, t, fixture.Source, 14)
bp.Logical.Tracepoint = true bp.Logical.Tracepoint = true
assertNoError(p.Next(), t, "Next()") assertNoError(grp.Next(), t, "Next()")
assertLineNumber(p, t, 14, "wrong line number") assertLineNumber(p, t, 14, "wrong line number")
if !p.Breakpoints().HasSteppingBreakpoints() { if !p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine") t.Fatal("does not have internal breakpoints after hitting tracepoint on same goroutine")
} }
// Continue to complete next. // Continue to complete next.
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
assertLineNumber(p, t, 35, "wrong line number") assertLineNumber(p, t, 35, "wrong line number")
if p.Breakpoints().HasSteppingBreakpoints() { if p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("has internal breakpoints after completing next") t.Fatal("has internal breakpoints after completing next")
@ -3713,17 +3716,18 @@ func TestIssue1101(t *testing.T) {
func TestIssue1145(t *testing.T) { func TestIssue1145(t *testing.T) {
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFileBreakpoint(p, t, fixture.Source, 18) setFileBreakpoint(p, t, fixture.Source, 18)
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1) resumeChan := make(chan struct{}, 1)
p.ResumeNotify(resumeChan) grp.ResumeNotify(resumeChan)
go func() { go func() {
<-resumeChan <-resumeChan
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
p.RequestManualStop() grp.RequestManualStop()
}() }()
assertNoError(p.Next(), t, "Next()") assertNoError(grp.Next(), t, "Next()")
if p.Breakpoints().HasSteppingBreakpoints() { if p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("has internal breakpoints after manual stop request") t.Fatal("has internal breakpoints after manual stop request")
} }
@ -3732,18 +3736,19 @@ func TestIssue1145(t *testing.T) {
func TestHaltKeepsSteppingBreakpoints(t *testing.T) { func TestHaltKeepsSteppingBreakpoints(t *testing.T) {
withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { 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) setFileBreakpoint(p, t, fixture.Source, 18)
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1) resumeChan := make(chan struct{}, 1)
p.ResumeNotify(resumeChan) grp.ResumeNotify(resumeChan)
go func() { go func() {
<-resumeChan <-resumeChan
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
p.RequestManualStop() grp.RequestManualStop()
}() }()
assertNoError(p.Next(), t, "Next()") assertNoError(grp.Next(), t, "Next()")
if !p.Breakpoints().HasSteppingBreakpoints() { if !p.Breakpoints().HasSteppingBreakpoints() {
t.Fatal("does not have internal breakpoints after manual stop request") 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. // Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
protest.MustSupportFunctionCalls(t, testBackend) protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFileBreakpoint(p, t, fixture.Source, 7) 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),") assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),")
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call") assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "getNum()", normalLoadConfig, true), t, "Call")
err := p.Continue() err := grp.Continue()
if _, isexited := err.(proc.ErrProcessExited); !isexited { if _, isexited := err.(proc.ErrProcessExited); !isexited {
regs, _ := p.CurrentThread().Registers() regs, _ := p.CurrentThread().Registers()
f, l, _ := p.BinInfo().PCToLine(regs.PC()) f, l, _ := p.BinInfo().PCToLine(regs.PC())
@ -4577,14 +4583,15 @@ func TestCallConcurrent(t *testing.T) {
skipOn(t, "broken", "freebsd") skipOn(t, "broken", "freebsd")
protest.MustSupportFunctionCalls(t, testBackend) protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
bp := setFileBreakpoint(p, t, fixture.Source, 24) bp := setFileBreakpoint(p, t, fixture.Source, 24)
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
//_, err := p.ClearBreakpoint(bp.Addr) //_, err := p.ClearBreakpoint(bp.Addr)
//assertNoError(err, t, "ClearBreakpoint() returned an error") //assertNoError(err, t, "ClearBreakpoint() returned an error")
gid1 := p.SelectedGoroutine().ID gid1 := p.SelectedGoroutine().ID
t.Logf("starting injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID()) 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) returned := testCallConcurrentCheckReturns(p, t, gid1, -1)
@ -4599,7 +4606,7 @@ func TestCallConcurrent(t *testing.T) {
gid2 := p.SelectedGoroutine().ID gid2 := p.SelectedGoroutine().ID
t.Logf("starting second injection in %d / %d", p.SelectedGoroutine().ID, p.CurrentThread().ThreadID()) 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 { for {
returned += testCallConcurrentCheckReturns(p, t, gid1, gid2) returned += testCallConcurrentCheckReturns(p, t, gid1, gid2)
@ -4607,10 +4614,10 @@ func TestCallConcurrent(t *testing.T) {
break break
} }
t.Logf("Continuing... %d", returned) 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. // altering the state of the target process.
protest.MustSupportFunctionCalls(t, testBackend) protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) {
assertNoError(p.Continue(), t, "Continue()") grp := proc.NewGroup(p)
assertNoError(proc.EvalExpressionWithCalls(p, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call") assertNoError(grp.Continue(), t, "Continue()")
assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call")
t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc) t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc)
if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source { if loc := p.SelectedGoroutine().CurrentLoc; loc.File != fixture.Source {
t.Errorf("wrong location for selected goroutine after call: %s:%d", loc.File, loc.Line) 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) { func TestRequestManualStopWhileStopped(t *testing.T) {
// Requesting a manual stop while stopped shouldn't cause problems (issue #2138). // Requesting a manual stop while stopped shouldn't cause problems (issue #2138).
withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
resumed := make(chan struct{}) resumed := make(chan struct{})
setFileBreakpoint(p, t, fixture.Source, 8) setFileBreakpoint(p, t, fixture.Source, 8)
assertNoError(p.Continue(), t, "Continue() 1") assertNoError(grp.Continue(), t, "Continue() 1")
p.ResumeNotify(resumed) grp.ResumeNotify(resumed)
go func() { go func() {
<-resumed <-resumed
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
p.RequestManualStop() grp.RequestManualStop()
}() }()
t.Logf("at time.Sleep call") t.Logf("at time.Sleep call")
assertNoError(p.Continue(), t, "Continue() 2") assertNoError(grp.Continue(), t, "Continue() 2")
t.Logf("manually stopped") t.Logf("manually stopped")
p.RequestManualStop() grp.RequestManualStop()
p.RequestManualStop() grp.RequestManualStop()
p.RequestManualStop() grp.RequestManualStop()
resumed = make(chan struct{}) resumed = make(chan struct{})
p.ResumeNotify(resumed) grp.ResumeNotify(resumed)
go func() { go func() {
<-resumed <-resumed
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
p.RequestManualStop() grp.RequestManualStop()
}() }()
t.Logf("resuming sleep") t.Logf("resuming sleep")
assertNoError(p.Continue(), t, "Continue() 3") assertNoError(grp.Continue(), t, "Continue() 3")
t.Logf("done") t.Logf("done")
}) })
} }
@ -5546,9 +5555,10 @@ func TestWatchpointCounts(t *testing.T) {
func TestManualStopWhileStopped(t *testing.T) { func TestManualStopWhileStopped(t *testing.T) {
// Checks that RequestManualStop sent to a stopped thread does not cause the target process to die. // 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) { withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
asyncCont := func(done chan struct{}) { asyncCont := func(done chan struct{}) {
defer close(done) defer close(done)
err := p.Continue() err := grp.Continue()
t.Logf("%v\n", err) t.Logf("%v\n", err)
if err != nil { if err != nil {
panic(err) panic(err)
@ -5571,9 +5581,9 @@ func TestManualStopWhileStopped(t *testing.T) {
done := make(chan struct{}) done := make(chan struct{})
go asyncCont(done) go asyncCont(done)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
p.RequestManualStop() grp.RequestManualStop()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
p.RequestManualStop() grp.RequestManualStop()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
<-done <-done
} }
@ -5581,11 +5591,11 @@ func TestManualStopWhileStopped(t *testing.T) {
t.Logf("Continue %d (fast)", i) t.Logf("Continue %d (fast)", i)
rch := make(chan struct{}) rch := make(chan struct{})
done := make(chan struct{}) done := make(chan struct{})
p.ResumeNotify(rch) grp.ResumeNotify(rch)
go asyncCont(done) go asyncCont(done)
<-rch <-rch
p.RequestManualStop() grp.RequestManualStop()
p.RequestManualStop() grp.RequestManualStop()
<-done <-done
} }
}) })
@ -5865,6 +5875,8 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) { withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) {
mainfn := p.BinInfo().LookupFunc["main.main"] mainfn := p.BinInfo().LookupFunc["main.main"]
grp := proc.NewGroup(p)
// Find JNZ instruction on line :14 // Find JNZ instruction on line :14
var addr uint64 var addr uint64
text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) 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") assertNoError(err, t, "SetBreakpoint")
// Continue to breakpoint // Continue to breakpoint
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
assertLineNumber(p, t, 14, "expected line :14") assertLineNumber(p, t, 14, "expected line :14")
// Save RFLAGS register // Save RFLAGS register
@ -5894,7 +5906,7 @@ func TestCallInjectionFlagCorruption(t *testing.T) {
t.Logf("rflags before = %#x", rflagsBeforeCall) t.Logf("rflags before = %#x", rflagsBeforeCall)
// Inject call to main.g() // 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 // Check RFLAGS register after the call
rflagsAfterCall := p.BinInfo().Arch.RegistersToDwarfRegisters(0, getRegisters(p, t)).Uint64Val(regnum.AMD64_Rflags) 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 // 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 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 // SIGINT directed at the inferior should be passed along not swallowed by delve
withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) { withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
setFunctionBreakpoint(p, t, "main.main") setFunctionBreakpoint(p, t, "main.main")
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
resumeChan := make(chan struct{}, 1) resumeChan := make(chan struct{}, 1)
go func() { go func() {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
<-resumeChan <-resumeChan
if p.Pid() <= 0 { if p.Pid() <= 0 {
// if we don't stop the inferior the test will never finish // if we don't stop the inferior the test will never finish
p.RequestManualStop() grp.RequestManualStop()
err := p.Detach(true) err := grp.Detach(true)
errChan <- errIssue419{pid: p.Pid(), err: err} errChan <- errIssue419{pid: p.Pid(), err: err}
return return
} }
err := syscall.Kill(p.Pid(), syscall.SIGINT) err := syscall.Kill(p.Pid(), syscall.SIGINT)
errChan <- errIssue419{pid: p.Pid(), err: err} errChan <- errIssue419{pid: p.Pid(), err: err}
}() }()
p.ResumeNotify(resumeChan) grp.ResumeNotify(resumeChan)
errChan <- p.Continue() errChan <- grp.Continue()
}) })
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {

@ -72,7 +72,7 @@ func (t *Target) setStackWatchBreakpoints(scope *EvalScope, watchpoint *Breakpoi
retbreaklet.watchpoint = watchpoint retbreaklet.watchpoint = watchpoint
retbreaklet.callback = woos 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 // Must also set a breakpoint on the call instruction immediately
// preceding retframe.Current.PC, because the watchpoint could also go out // preceding retframe.Current.PC, because the watchpoint could also go out
// of scope while we are running backwards. // of scope while we are running backwards.

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

@ -26,27 +26,32 @@ func (err *ErrNoSourceForPC) Error() string {
return fmt.Sprintf("no source for PC %#x", err.pc) return fmt.Sprintf("no source for PC %#x", err.pc)
} }
// Next continues execution until the next source line. // Next resumes the processes in the group, continuing the selected target
func (dbp *Target) Next() (err error) { // until the next source line.
if _, err := dbp.Valid(); err != nil { func (grp *TargetGroup) Next() (err error) {
if _, err := grp.Valid(); err != nil {
return err return err
} }
if dbp.Breakpoints().HasSteppingBreakpoints() { if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting") return fmt.Errorf("next while nexting")
} }
if err = next(dbp, false, false); err != nil { if err = next(grp.Selected, false, false); err != nil {
dbp.ClearSteppingBreakpoints() grp.Selected.ClearSteppingBreakpoints()
return return
} }
return dbp.Continue() return grp.Continue()
} }
// Continue continues execution of the debugged // 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. // 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 { if _, err := dbp.Valid(); err != nil {
return err return err
} }
@ -56,29 +61,29 @@ func (dbp *Target) Continue() error {
} }
dbp.Breakpoints().WatchOutOfScope = nil dbp.Breakpoints().WatchOutOfScope = nil
dbp.clearHardcodedBreakpoints() dbp.clearHardcodedBreakpoints()
dbp.cctx.CheckAndClearManualStopRequest() grp.cctx.CheckAndClearManualStopRequest()
defer func() { defer func() {
// Make sure we clear internal breakpoints if we simultaneously receive a // Make sure we clear internal breakpoints if we simultaneously receive a
// manual stop request and hit a breakpoint. // manual stop request and hit a breakpoint.
if dbp.cctx.CheckAndClearManualStopRequest() { if grp.cctx.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual dbp.StopReason = StopManual
dbp.clearHardcodedBreakpoints() dbp.clearHardcodedBreakpoints()
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
dbp.ClearSteppingBreakpoints() dbp.ClearSteppingBreakpoints()
} }
} }
}() }()
for { for {
if dbp.cctx.CheckAndClearManualStopRequest() { if grp.cctx.CheckAndClearManualStopRequest() {
dbp.StopReason = StopManual dbp.StopReason = StopManual
dbp.clearHardcodedBreakpoints() dbp.clearHardcodedBreakpoints()
if dbp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 {
dbp.ClearSteppingBreakpoints() dbp.ClearSteppingBreakpoints()
} }
return nil return nil
} }
dbp.ClearCaches() 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 dbp.StopReason = stopReason
threads := dbp.ThreadList() threads := dbp.ThreadList()
@ -136,7 +141,7 @@ func (dbp *Target) Continue() error {
if err := conditionErrors(threads); err != nil { if err := conditionErrors(threads); err != nil {
return err return err
} }
if dbp.GetDirection() == Forward { if grp.GetDirection() == Forward {
text, err := disassembleCurrentInstruction(dbp, curthread, 0) text, err := disassembleCurrentInstruction(dbp, curthread, 0)
if err != nil { if err != nil {
return err return err
@ -155,7 +160,7 @@ func (dbp *Target) Continue() error {
if err := dbp.ClearSteppingBreakpoints(); err != nil { if err := dbp.ClearSteppingBreakpoints(); err != nil {
return err return err
} }
return dbp.StepInstruction() return grp.StepInstruction()
} }
} else { } else {
curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread) curthread.Common().returnValues = curbp.Breakpoint.returnInfo.Collect(dbp, curthread)
@ -171,7 +176,7 @@ func (dbp *Target) Continue() error {
return err return err
} }
if onNextGoroutine && if onNextGoroutine &&
(!isTraceOrTraceReturn(curbp.Breakpoint) || dbp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) { (!isTraceOrTraceReturn(curbp.Breakpoint) || grp.KeepSteppingBreakpoints&TracepointKeepsSteppingBreakpoints == 0) {
err := dbp.ClearSteppingBreakpoints() err := dbp.ClearSteppingBreakpoints()
if err != nil { if err != nil {
return err return err
@ -287,27 +292,27 @@ func stepInstructionOut(dbp *Target, curthread Thread, fnname1, fnname2 string)
} }
} }
// Step will continue until another source line is reached. // Step resumes the processes in the group, continuing the selected target
// Will step into functions. // until the next source line. Will step into functions.
func (dbp *Target) Step() (err error) { func (grp *TargetGroup) Step() (err error) {
if _, err := dbp.Valid(); err != nil { if _, err := grp.Valid(); err != nil {
return err return err
} }
if dbp.Breakpoints().HasSteppingBreakpoints() { if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting") return fmt.Errorf("next while nexting")
} }
if err = next(dbp, true, false); err != nil { if err = next(grp.Selected, true, false); err != nil {
_ = dbp.ClearSteppingBreakpoints() _ = grp.Selected.ClearSteppingBreakpoints()
return err return err
} }
if bpstate := dbp.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && dbp.GetDirection() == Backward { if bpstate := grp.Selected.CurrentThread().Breakpoint(); bpstate.Breakpoint != nil && bpstate.Active && bpstate.SteppingInto && grp.GetDirection() == Backward {
dbp.ClearSteppingBreakpoints() grp.Selected.ClearSteppingBreakpoints()
return dbp.StepInstruction() return grp.StepInstruction()
} }
return dbp.Continue() return grp.Continue()
} }
// sameGoroutineCondition returns an expression that evaluates to true when // 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())) return astutil.Eql(astutil.PkgVar("runtime", "frameoff"), astutil.Int(frame.FrameOffset()))
} }
// StepOut will continue until the current goroutine exits the // StepOut resumes the processes in the group, continuing the selected target
// function currently being executed or a deferred function is executed // until until the current goroutine exits the function currently being
func (dbp *Target) StepOut() error { // executed or a deferred function is executed
backward := dbp.GetDirection() == Backward func (grp *TargetGroup) StepOut() error {
if _, err := dbp.Valid(); err != nil { backward := grp.GetDirection() == Backward
if _, err := grp.Valid(); err != nil {
return err return err
} }
if dbp.Breakpoints().HasSteppingBreakpoints() { if grp.HasSteppingBreakpoints() {
return fmt.Errorf("next while nexting") return fmt.Errorf("next while nexting")
} }
dbp := grp.Selected
selg := dbp.SelectedGoroutine() selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread() curthread := dbp.CurrentThread()
@ -355,7 +362,7 @@ func (dbp *Target) StepOut() error {
} }
success = true success = true
return dbp.Continue() return grp.Continue()
} }
sameGCond := sameGoroutineCondition(selg) sameGCond := sameGoroutineCondition(selg)
@ -366,7 +373,7 @@ func (dbp *Target) StepOut() error {
} }
success = true success = true
return dbp.Continue() return grp.Continue()
} }
deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false) deferpc, err := setDeferBreakpoint(dbp, nil, topframe, sameGCond, false)
@ -395,14 +402,15 @@ func (dbp *Target) StepOut() error {
} }
success = true success = true
return dbp.Continue() return grp.Continue()
} }
// StepInstruction will continue the current thread for exactly // StepInstruction will continue the current thread for exactly
// one instruction. This method affects only the thread // one instruction. This method affects only the thread
// associated with the selected goroutine. All other // associated with the selected goroutine. All other
// threads will remain stopped. // threads will remain stopped.
func (dbp *Target) StepInstruction() (err error) { func (grp *TargetGroup) StepInstruction() (err error) {
dbp := grp.Selected
thread := dbp.CurrentThread() thread := dbp.CurrentThread()
g := dbp.SelectedGoroutine() g := dbp.SelectedGoroutine()
if g != nil { if g != nil {
@ -412,7 +420,7 @@ func (dbp *Target) StepInstruction() (err error) {
sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil { sameGoroutineCondition(dbp.SelectedGoroutine())); err != nil {
return err return err
} }
return dbp.Continue() return grp.Continue()
} }
thread = g.Thread thread = g.Thread
} }
@ -464,7 +472,7 @@ func (dbp *Target) StepInstruction() (err error) {
// when removing instructions belonging to inlined calls we also remove all // when removing instructions belonging to inlined calls we also remove all
// instructions belonging to the current inlined call. // instructions belonging to the current inlined call.
func next(dbp *Target, stepInto, inlinedStepOut bool) error { func next(dbp *Target, stepInto, inlinedStepOut bool) error {
backward := dbp.GetDirection() == Backward backward := dbp.recman.GetDirection() == Backward
selg := dbp.SelectedGoroutine() selg := dbp.SelectedGoroutine()
curthread := dbp.CurrentThread() curthread := dbp.CurrentThread()
topframe, retframe, err := topframe(selg, curthread) topframe, retframe, err := topframe(selg, curthread)
@ -1086,7 +1094,7 @@ func (tgt *Target) clearHardcodedBreakpoints() {
func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error { func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Thread) error {
mem := tgt.Memory() mem := tgt.Memory()
arch := tgt.BinInfo().Arch arch := tgt.BinInfo().Arch
recorded, _ := tgt.Recorded() recorded, _ := tgt.recman.Recorded()
isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 { isHardcodedBreakpoint := func(thread Thread, pc uint64) uint64 {
for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} { for _, bpinstr := range [][]byte{arch.BreakpointInstruction(), arch.AltBreakpointInstruction()} {
@ -1150,7 +1158,7 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa
switch { switch {
case loc.Fn.Name == "runtime.breakpoint": case loc.Fn.Name == "runtime.breakpoint":
if recorded, _ := tgt.Recorded(); recorded { if recorded, _ := tgt.recman.Recorded(); recorded {
setHardcodedBreakpoint(thread, loc) setHardcodedBreakpoint(thread, loc)
continue 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) { withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) {
grp := proc.NewGroup(p)
testCallFunctionSetBreakpoint(t, p, fixture) testCallFunctionSetBreakpoint(t, p, fixture)
assertNoError(p.Continue(), t, "Continue()") assertNoError(grp.Continue(), t, "Continue()")
for _, tc := range testcases { for _, tc := range testcases {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) {
for _, tc := range testcases112 { for _, tc := range testcases112 {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcasesBefore114After112 { for _, tc := range testcasesBefore114After112 {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
} }
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
for _, tc := range testcases113 { for _, tc := range testcases113 {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
for _, tc := range testcases114 { for _, tc := range testcases114 {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
} }
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) { if goversion.VersionAfterOrEqual(runtime.Version(), 1, 17) {
for _, tc := range testcases117 { for _, tc := range testcases117 {
testCallFunction(t, p, tc) testCallFunction(t, grp, p, tc)
} }
} }
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!! // 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 " const unsafePrefix = "-unsafe "
var callExpr, varExpr string var callExpr, varExpr string
@ -1302,7 +1303,7 @@ func testCallFunction(t *testing.T, p *proc.Target, tc testCaseCallFunction) {
checkEscape = false checkEscape = false
} }
t.Logf("call %q", tc.expr) 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 { if tc.err != nil {
t.Logf("\terr = %v\n", err) t.Logf("\terr = %v\n", err)
if err == nil { if err == nil {

@ -1558,7 +1558,7 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
} }
s.send(e) 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.logToConsole("Type 'dlv help' for list of commands.")
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)}) s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})

@ -61,7 +61,7 @@ type Debugger struct {
processArgs []string processArgs []string
targetMutex sync.Mutex targetMutex sync.Mutex
target *proc.Target target *proc.TargetGroup
log *logrus.Entry log *logrus.Entry
@ -161,7 +161,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
err = noDebugErrorWarning(err) err = noDebugErrorWarning(err)
return nil, attachErrorMessage(d.config.AttachPid, err) return nil, attachErrorMessage(d.config.AttachPid, err)
} }
d.target = p d.target = proc.NewGroup(p)
case d.config.CoreFile != "": case d.config.CoreFile != "":
var p *proc.Target var p *proc.Target
@ -178,7 +178,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
err = go11DecodeErrorCheck(err) err = go11DecodeErrorCheck(err)
return nil, err return nil, err
} }
d.target = p d.target = proc.NewGroup(p)
if err := d.checkGoVersion(); err != nil { if err := d.checkGoVersion(); err != nil {
d.target.Detach(true) d.target.Detach(true)
return nil, err return nil, err
@ -197,7 +197,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
} }
if p != nil { if p != nil {
// if p == nil and err == nil then we are doing a recording, don't touch d.target // 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 { if err := d.checkGoVersion(); err != nil {
d.target.Detach(true) d.target.Detach(true)
@ -225,7 +225,7 @@ func (d *Debugger) checkGoVersion() error {
// do not do anything if we are still recording // do not do anything if we are still recording
return nil return nil
} }
producer := d.target.BinInfo().Producer() producer := d.target.Selected.BinInfo().Producer()
if producer == "" { if producer == "" {
return nil return nil
} }
@ -235,7 +235,7 @@ func (d *Debugger) checkGoVersion() error {
func (d *Debugger) TargetGoVersion() string { func (d *Debugger) TargetGoVersion() string {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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. // 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) os.Exit(1)
} }
d.recordingDone() d.recordingDone()
d.target = p d.target = proc.NewGroup(p)
if err := d.checkGoVersion(); err != nil { if err := d.checkGoVersion(); err != nil {
d.log.Error(err) d.log.Error(err)
err := d.target.Detach(true) err := d.target.Detach(true)
@ -368,7 +368,7 @@ func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error)
func (d *Debugger) ProcessPid() int { func (d *Debugger) ProcessPid() int {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
return d.target.Pid() return d.target.Selected.Pid()
} }
// LastModified returns the time that the process' executable was last // 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 { func (d *Debugger) LastModified() time.Time {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
return d.target.BinInfo().LastModified() return d.target.Selected.BinInfo().LastModified()
} }
// FunctionReturnLocations returns all return locations // FunctionReturnLocations returns all return locations
@ -387,7 +387,7 @@ func (d *Debugger) FunctionReturnLocations(fnName string) ([]uint64, error) {
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
var ( var (
p = d.target p = d.target.Selected
g = p.SelectedGoroutine() g = p.SelectedGoroutine()
) )
@ -463,12 +463,6 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
return nil, ErrCanNotRestart 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 { if err := d.detach(true); err != nil {
return nil, err return nil, err
} }
@ -514,9 +508,9 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
discarded := []api.DiscardedBreakpoint{} discarded := []api.DiscardedBreakpoint{}
p.Breakpoints().Logical = d.target.Breakpoints().Logical p.Breakpoints().Logical = d.target.LogicalBreakpoints
d.target = p d.target = proc.NewGroup(p)
for _, oldBp := range d.target.Breakpoints().Logical { for _, oldBp := range d.target.LogicalBreakpoints {
if oldBp.LogicalID < 0 || !oldBp.Enabled { if oldBp.LogicalID < 0 || !oldBp.Enabled {
continue continue
} }
@ -566,16 +560,19 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
goroutine *api.Goroutine goroutine *api.Goroutine
) )
if d.target.SelectedGoroutine() != nil { tgt := d.target.Selected
goroutine = api.ConvertGoroutine(d.target, d.target.SelectedGoroutine())
if tgt.SelectedGoroutine() != nil {
goroutine = api.ConvertGoroutine(tgt, tgt.SelectedGoroutine())
} }
exited := false exited := false
if _, err := d.target.Valid(); err != nil { if _, err := tgt.Valid(); err != nil {
_, exited = err.(proc.ErrProcessExited) _, exited = err.(proc.ErrProcessExited)
} }
state = &api.DebuggerState{ state = &api.DebuggerState{
Pid: tgt.Pid(),
SelectedGoroutine: goroutine, SelectedGoroutine: goroutine,
Exited: exited, Exited: exited,
} }
@ -589,22 +586,23 @@ func (d *Debugger) state(retLoadCfg *proc.LoadConfig) (*api.DebuggerState, error
} }
state.Threads = append(state.Threads, th) state.Threads = append(state.Threads, th)
if thread.ThreadID() == d.target.CurrentThread().ThreadID() { if thread.ThreadID() == tgt.CurrentThread().ThreadID() {
state.CurrentThread = th state.CurrentThread = th
} }
} }
state.NextInProgress = d.target.Breakpoints().HasSteppingBreakpoints() state.NextInProgress = d.target.HasSteppingBreakpoints()
if recorded, _ := d.target.Recorded(); recorded { if recorded, _ := d.target.Recorded(); recorded {
state.When, _ = d.target.When() state.When, _ = d.target.When()
} }
state.WatchOutOfScope = make([]*api.Breakpoint, 0, len(d.target.Breakpoints().WatchOutOfScope)) for _, t := range d.target.Targets() {
for _, bp := range d.target.Breakpoints().WatchOutOfScope { for _, bp := range t.Breakpoints().WatchOutOfScope {
abp := api.ConvertLogicalBreakpoint(bp.Logical) abp := api.ConvertLogicalBreakpoint(bp.Logical)
api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp}) api.ConvertPhysicalBreakpoints(abp, []*proc.Breakpoint{bp})
state.WatchOutOfScope = append(state.WatchOutOfScope, abp) state.WatchOutOfScope = append(state.WatchOutOfScope, abp)
}
} }
return state, nil return state, nil
@ -641,6 +639,15 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 ( var (
addrs []uint64 addrs []uint64
err error err error
@ -660,16 +667,19 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Accept fileName which is case-insensitive and slash-insensitive match // Accept fileName which is case-insensitive and slash-insensitive match
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName)) fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
for _, symFile := range d.target.BinInfo().Sources { caseInsensitiveSearch:
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) { for _, t := range d.target.Targets() {
fileName = symFile for _, symFile := range t.BinInfo().Sources {
break 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: 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: case len(requestedBp.Addrs) > 0:
addrs = requestedBp.Addrs addrs = requestedBp.Addrs
default: default:
@ -704,7 +714,10 @@ func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint {
// createLogicalBreakpoint creates one physical breakpoint for each address // createLogicalBreakpoint creates one physical breakpoint for each address
// in addrs and associates all of them with the same logical breakpoint. // 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) { 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 { if lbp := p.Breakpoints().Logical[requestedBp.ID]; lbp != nil {
abp := d.convertBreakpoint(lbp) 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 { 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 { if err != nil {
return err return err
} }
bps := make([]*proc.Breakpoint, len(addrs)) bps := make([]*proc.Breakpoint, len(addrs))
for i := range 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 { if err != nil {
break break
} }
@ -771,7 +788,7 @@ func (d *Debugger) createPhysicalBreakpoints(lbp *proc.LogicalBreakpoint) error
if bp == nil { if bp == nil {
continue 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) 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 { 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 var errs []error
n := 0 n := 0
for _, bp := range d.target.Breakpoints().M { for _, bp := range p.Breakpoints().M {
if bp.LogicalID() == id { if bp.LogicalID() == id {
n++ n++
err := d.target.ClearBreakpoint(bp.Addr) err := p.ClearBreakpoint(bp.Addr)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
@ -817,15 +838,21 @@ func isBreakpointExistsErr(err error) bool {
func (d *Debugger) CreateEBPFTracepoint(fnName string) error { func (d *Debugger) CreateEBPFTracepoint(fnName string) error {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
return d.target.SetEBPFTracepoint(fnName) panic("multiple targets not implemented")
}
p := d.target.Selected
return p.SetEBPFTracepoint(fnName)
} }
// amendBreakpoint will update the breakpoint with the matching ID. // amendBreakpoint will update the breakpoint with the matching ID.
// It also enables or disables the breakpoint. // It also enables or disables the breakpoint.
// We can consume this function to avoid locking a goroutine. // We can consume this function to avoid locking a goroutine.
func (d *Debugger) amendBreakpoint(amend *api.Breakpoint) error { 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 { if original == nil {
return fmt.Errorf("no breakpoint with ID %d", amend.ID) 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 // clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine
func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) { 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 { if requestedBp.ID <= 0 {
bp := d.target.Breakpoints().M[requestedBp.Addr] bp := p.Breakpoints().M[requestedBp.Addr]
requestedBp.ID = bp.LogicalID() requestedBp.ID = bp.LogicalID()
} }
lbp := d.target.Breakpoints().Logical[requestedBp.ID] lbp := d.target.LogicalBreakpoints[requestedBp.ID]
clearedBp := d.convertBreakpoint(lbp) clearedBp := d.convertBreakpoint(lbp)
delete(d.target.Breakpoints().Logical, requestedBp.ID) delete(d.target.LogicalBreakpoints, requestedBp.ID)
err := d.clearPhysicalBreakpoints(requestedBp.ID) err := d.clearPhysicalBreakpoints(requestedBp.ID)
if err != nil { if err != nil {
@ -1006,10 +1038,14 @@ func isBpHitCondNotSatisfiable(bp *api.Breakpoint) bool {
func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint { func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
if len(d.target.Targets()) != 1 {
panic("multiple targets not implemented")
}
p := d.target.Selected
abps := []*api.Breakpoint{} abps := []*api.Breakpoint{}
if all { if all {
for _, bp := range d.target.Breakpoints().M { for _, bp := range p.Breakpoints().M {
var abp *api.Breakpoint var abp *api.Breakpoint
if bp.Logical != nil { if bp.Logical != nil {
abp = api.ConvertLogicalBreakpoint(bp.Logical) abp = api.ConvertLogicalBreakpoint(bp.Logical)
@ -1021,7 +1057,7 @@ func (d *Debugger) Breakpoints(all bool) []*api.Breakpoint {
abps = append(abps, abp) abps = append(abps, abp)
} }
} else { } else {
for _, lbp := range d.target.Breakpoints().Logical { for _, lbp := range d.target.LogicalBreakpoints {
abps = append(abps, d.convertBreakpoint(lbp)) 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 { func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
lbp := d.target.Breakpoints().Logical[id] lbp := d.target.LogicalBreakpoints[id]
if lbp == nil { if lbp == nil {
return nil return nil
} }
@ -1040,8 +1076,13 @@ func (d *Debugger) FindBreakpoint(id int) *api.Breakpoint {
} }
func (d *Debugger) findBreakpoint(id int) []*proc.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 var bps []*proc.Breakpoint
for _, bp := range d.target.Breakpoints().M { for _, bp := range p.Breakpoints().M {
if bp.LogicalID() == id { if bp.LogicalID() == id {
bps = append(bps, bp) bps = append(bps, bp)
} }
@ -1057,7 +1098,7 @@ func (d *Debugger) FindBreakpointByName(name string) *api.Breakpoint {
} }
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 { if lbp.Name == name {
return d.convertBreakpoint(lbp) return d.convertBreakpoint(lbp)
} }
@ -1067,12 +1108,17 @@ func (d *Debugger) findBreakpointByName(name string) *api.Breakpoint {
// CreateWatchpoint creates a watchpoint on the specified expression. // CreateWatchpoint creates a watchpoint on the specified expression.
func (d *Debugger) CreateWatchpoint(goid, frame, deferredCall int, expr string, wtype api.WatchType) (*api.Breakpoint, error) { 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 { if err != nil {
return nil, err return nil, err
} }
d.breakpointIDCounter++ 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 { if err != nil {
return nil, err return nil, err
} }
@ -1116,7 +1162,7 @@ func (d *Debugger) FindGoroutine(id int) (*proc.G, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
return proc.FindGoroutine(d.target, id) return proc.FindGoroutine(d.target.Selected, id)
} }
func (d *Debugger) setRunning(running bool) { func (d *Debugger) setRunning(running bool) {
@ -1184,9 +1230,9 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
if command.ReturnInfoLoadConfig == nil { if command.ReturnInfoLoadConfig == nil {
return nil, errors.New("can not call function with nil ReturnInfoLoadConfig") return nil, errors.New("can not call function with nil ReturnInfoLoadConfig")
} }
g := d.target.SelectedGoroutine() g := d.target.Selected.SelectedGoroutine()
if command.GoroutineID > 0 { if command.GoroutineID > 0 {
g, err = proc.FindGoroutine(d.target, command.GoroutineID) g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1248,14 +1294,14 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
err = d.target.StepOut() err = d.target.StepOut()
case api.SwitchThread: case api.SwitchThread:
d.log.Debugf("switching to thread %d", command.ThreadID) d.log.Debugf("switching to thread %d", command.ThreadID)
err = d.target.SwitchThread(command.ThreadID) err = d.target.Selected.SwitchThread(command.ThreadID)
withBreakpointInfo = false withBreakpointInfo = false
case api.SwitchGoroutine: case api.SwitchGoroutine:
d.log.Debugf("switching to goroutine %d", command.GoroutineID) d.log.Debugf("switching to goroutine %d", command.GoroutineID)
var g *proc.G var g *proc.G
g, err = proc.FindGoroutine(d.target, command.GoroutineID) g, err = proc.FindGoroutine(d.target.Selected, command.GoroutineID)
if err == nil { if err == nil {
err = d.target.SwitchGoroutine(g) err = d.target.Selected.SwitchGoroutine(g)
} }
withBreakpointInfo = false withBreakpointInfo = false
case api.Halt: case api.Halt:
@ -1266,7 +1312,7 @@ func (d *Debugger) Command(command *api.DebuggerCommand, resumeNotify chan struc
if err != nil { if err != nil {
if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread { if pe, ok := err.(proc.ErrProcessExited); ok && command.Name != api.SwitchGoroutine && command.Name != api.SwitchThread {
state := &api.DebuggerState{} state := &api.DebuggerState{}
state.Pid = d.target.Pid() state.Pid = d.target.Selected.Pid()
state.Exited = true state.Exited = true
state.ExitStatus = pe.Status state.ExitStatus = pe.Status
state.Err = pe 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 { 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 { if state == nil {
return nil return nil
} }
@ -1312,15 +1360,15 @@ func (d *Debugger) collectBreakpointInformation(state *api.DebuggerState) error
state.Threads[i].BreakpointInfo = bpi state.Threads[i].BreakpointInfo = bpi
if bp.Goroutine { if bp.Goroutine {
g, err := proc.GetG(d.target.CurrentThread()) g, err := proc.GetG(d.target.Selected.CurrentThread())
if err != nil { if err != nil {
return err return err
} }
bpi.Goroutine = api.ConvertGoroutine(d.target, g) bpi.Goroutine = api.ConvertGoroutine(d.target.Selected, g)
} }
if bp.Stacktrace > 0 { 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 { if err != nil {
return err 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 { if !found {
return fmt.Errorf("could not find thread %d", state.Threads[i].ID) 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 continue
} }
s, err := proc.GoroutineScope(d.target, thread) s, err := proc.GoroutineScope(d.target.Selected, thread)
if err != nil { if err != nil {
return err return err
} }
@ -1382,14 +1430,33 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
} }
files := []string{} files := []string{}
for _, f := range d.target.BinInfo().Sources { for _, t := range d.target.Targets() {
if regex.Match([]byte(f)) { for _, f := range t.BinInfo().Sources {
files = append(files, f) if regex.Match([]byte(f)) {
files = append(files, f)
}
} }
} }
sort.Strings(files)
files = uniq(files)
return files, nil 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. // Functions returns a list of functions in the target process.
func (d *Debugger) Functions(filter string) ([]string, error) { func (d *Debugger) Functions(filter string) ([]string, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
@ -1401,11 +1468,15 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
} }
funcs := []string{} funcs := []string{}
for _, f := range d.target.BinInfo().Functions { for _, t := range d.target.Targets() {
if regex.MatchString(f.Name) { for _, f := range t.BinInfo().Functions {
funcs = append(funcs, f.Name) if regex.MatchString(f.Name) {
funcs = append(funcs, f.Name)
}
} }
} }
sort.Strings(funcs)
funcs = uniq(funcs)
return funcs, nil 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()) return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
} }
types, err := d.target.BinInfo().Types() r := []string{}
if err != nil {
return nil, err
}
r := make([]string, 0, len(types)) for _, t := range d.target.Targets() {
for _, typ := range types { types, err := t.BinInfo().Types()
if regex.Match([]byte(typ)) { if err != nil {
r = append(r, typ) 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 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) { func (d *Debugger) PackageVariables(filter string, cfg proc.LoadConfig) ([]*proc.Variable, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
p := d.target.Selected
regex, err := regexp.Compile(filter) regex, err := regexp.Compile(filter)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error()) 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 { if err != nil {
return nil, err return nil, err
} }
@ -1467,7 +1544,7 @@ func (d *Debugger) ThreadRegisters(threadID int, floatingPoint bool) (*op.DwarfR
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
thread, found := d.target.FindThread(threadID) thread, found := d.target.Selected.FindThread(threadID)
if !found { if !found {
return nil, fmt.Errorf("couldn't find thread %d", threadID) 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 { if err != nil {
return nil, err 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. // ScopeRegisters returns registers for the specified scope.
@ -1483,7 +1560,7 @@ func (d *Debugger) ScopeRegisters(goid, frame, deferredCall int, floatingPoint b
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return nil, err 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. // DwarfRegisterToString returns the name and value representation of the given register.
func (d *Debugger) DwarfRegisterToString(i int, reg *op.DwarfRegister) (string, bool, string) { 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. // 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() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }
@ -1512,7 +1589,7 @@ func (d *Debugger) FunctionArguments(goid, frame, deferredCall int, cfg proc.Loa
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }
@ -1524,7 +1601,7 @@ func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig)
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }
@ -1537,7 +1614,7 @@ func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, expr strin
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }
@ -1558,7 +1635,7 @@ func (d *Debugger) SetVariableInScope(goid, frame, deferredCall int, symbol, val
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if err != nil {
return err 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) { func (d *Debugger) Goroutines(start, count int) ([]*proc.G, int, error) {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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. // 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 { for _, g := range gs {
ok := true ok := true
for i := range filters { for i := range filters {
if !matchGoroutineFilter(d.target, g, &filters[i]) { if !matchGoroutineFilter(d.target.Selected, g, &filters[i]) {
ok = false ok = false
break break
} }
@ -1663,13 +1740,13 @@ func (d *Debugger) GroupGoroutines(gs []*proc.G, group *api.GoroutineGroupingOpt
case api.GoroutineGoLoc: case api.GoroutineGoLoc:
key = formatLoc(g.Go()) key = formatLoc(g.Go())
case api.GoroutineStartLoc: case api.GoroutineStartLoc:
key = formatLoc(g.StartLoc(d.target)) key = formatLoc(g.StartLoc(d.target.Selected))
case api.GoroutineLabel: case api.GoroutineLabel:
key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey]) key = fmt.Sprintf("%s=%s", group.GroupByKey, g.Labels()[group.GroupByKey])
case api.GoroutineRunning: case api.GoroutineRunning:
key = fmt.Sprintf("running=%v", g.Thread != nil) key = fmt.Sprintf("running=%v", g.Thread != nil)
case api.GoroutineUser: 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 { if len(groupMembers[key]) < group.MaxGroupMembers {
groupMembers[key] = append(groupMembers[key], g) groupMembers[key] = append(groupMembers[key], g)
@ -1708,13 +1785,13 @@ func (d *Debugger) Stacktrace(goroutineID, depth int, opts api.StacktraceOptions
return nil, err return nil, err
} }
g, err := proc.FindGoroutine(d.target, goroutineID) g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if g == nil { if g == nil {
return proc.ThreadStacktrace(d.target.CurrentThread(), depth) return proc.ThreadStacktrace(d.target.Selected.CurrentThread(), depth)
} else { } else {
return g.Stacktrace(depth, proc.StacktraceOptions(opts)) return g.Stacktrace(depth, proc.StacktraceOptions(opts))
} }
@ -1729,7 +1806,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
return nil, err return nil, err
} }
g, err := proc.FindGoroutine(d.target, goroutineID) g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1737,7 +1814,7 @@ func (d *Debugger) Ancestors(goroutineID, numAncestors, depth int) ([]api.Ancest
return nil, errors.New("no selected goroutine") 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 { if err != nil {
return nil, err 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 { if cfg != nil && rawlocs[i].Current.Fn != nil {
var err error 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) locals, err := scope.LocalVariables(*cfg)
if err != nil { if err != nil {
return nil, err 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 { func (d *Debugger) convertDefers(defers []*proc.Defer) []api.Defer {
r := make([]api.Defer, len(defers)) r := make([]api.Defer, len(defers))
for i := range defers { for i := range defers {
ddf, ddl, ddfn := defers[i].DeferredFunc(d.target) ddf, ddl, ddfn := defers[i].DeferredFunc(d.target.Selected)
drf, drl, drfn := d.target.BinInfo().PCToLine(defers[i].DeferPC) drf, drl, drfn := d.target.Selected.BinInfo().PCToLine(defers[i].DeferPC)
r[i] = api.Defer{ r[i] = api.Defer{
DeferredLoc: api.ConvertLocation(proc.Location{ DeferredLoc: api.ConvertLocation(proc.Location{
@ -1848,7 +1925,7 @@ func (d *Debugger) CurrentPackage() (string, error) {
if _, err := d.target.Valid(); err != nil { if _, err := d.target.Valid(); err != nil {
return "", err return "", err
} }
loc, err := d.target.CurrentThread().Location() loc, err := d.target.Selected.CurrentThread().Location()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1863,6 +1940,13 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if _, err := d.target.Valid(); err != nil {
return nil, err return nil, err
} }
@ -1872,7 +1956,7 @@ func (d *Debugger) FindLocation(goid, frame, deferredCall int, locStr string, in
return nil, err 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'. // 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() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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 { if _, err := d.target.Valid(); err != nil {
return nil, err 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) { 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(d.target, goid, frame, deferredCall) 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 { for i := range locs {
if locs[i].PC == 0 { if locs[i].PC == 0 {
continue 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].File = file
locs[i].Line = line locs[i].Line = line
locs[i].Function = api.ConvertFunction(fn) locs[i].Function = api.ConvertFunction(fn)
@ -1917,7 +2008,7 @@ func (d *Debugger) Disassemble(goroutineID int, addr1, addr2 uint64) ([]proc.Asm
} }
if addr2 == 0 { if addr2 == 0 {
fn := d.target.BinInfo().PCToFunc(addr1) fn := d.target.Selected.BinInfo().PCToFunc(addr1)
if fn == nil { if fn == nil {
return nil, fmt.Errorf("address %#x does not belong to any function", addr1) 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 addr2 = fn.End
} }
g, err := proc.FindGoroutine(d.target, goroutineID) g, err := proc.FindGoroutine(d.target.Selected, goroutineID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
curthread := d.target.CurrentThread() curthread := d.target.Selected.CurrentThread()
if g != nil && g.Thread != nil { if g != nil && g.Thread != nil {
curthread = g.Thread curthread = g.Thread
} }
regs, _ := curthread.Registers() 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 { func (d *Debugger) AsmInstructionText(inst *proc.AsmInstruction, flavour proc.AssemblyFlavour) string {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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. // 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 return nil, err
} }
thread, found := d.target.FindThread(id) thread, found := d.target.Selected.FindThread(id)
if !found { if !found {
return nil, fmt.Errorf("could not find thread %d", id) 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 { func (d *Debugger) ListDynamicLibraries() []*proc.Image {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
mem := d.target.Memory() mem := d.target.Selected.Memory()
data := make([]byte, length) data := make([]byte, length)
n, err := mem.ReadMemory(data, address) n, err := mem.ReadMemory(data, address)
if err != nil { if err != nil {
@ -2038,7 +2129,7 @@ func (d *Debugger) GetVersion(out *api.GetVersionOut) error {
} }
if !d.isRecording() && !d.IsRunning() { 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) 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 { func (d *Debugger) ListPackagesBuildInfo(includeFiles bool) []*proc.PackageBuildInfo {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() 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) // StopRecording stops a recording (if one is in progress)
@ -2072,7 +2163,7 @@ func (d *Debugger) StopRecording() error {
func (d *Debugger) StopReason() proc.StopReason { func (d *Debugger) StopReason() proc.StopReason {
d.targetMutex.Lock() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
return d.target.StopReason return d.target.Selected.StopReason
} }
// LockTarget acquires the target mutex. // LockTarget acquires the target mutex.
@ -2090,7 +2181,9 @@ func (d *Debugger) DumpStart(dest string) error {
d.targetMutex.Lock() d.targetMutex.Lock()
// targetMutex will only be unlocked when the dump is done // 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() d.targetMutex.Unlock()
return ErrCoreDumpNotSupported return ErrCoreDumpNotSupported
} }
@ -2120,7 +2213,7 @@ func (d *Debugger) DumpStart(dest string) error {
d.dumpState.Err = nil d.dumpState.Err = nil
go func() { go func() {
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
d.target.Dump(fh, 0, &d.dumpState) d.target.Selected.Dump(fh, 0, &d.dumpState)
}() }()
return nil return nil
@ -2157,11 +2250,15 @@ func (d *Debugger) DumpCancel() error {
} }
func (d *Debugger) Target() *proc.Target { func (d *Debugger) Target() *proc.Target {
return d.target.Selected
}
func (d *Debugger) TargetGroup() *proc.TargetGroup {
return d.target return d.target
} }
func (d *Debugger) BuildID() string { func (d *Debugger) BuildID() string {
return d.target.BinInfo().BuildID return d.target.Selected.BinInfo().BuildID
} }
func (d *Debugger) AttachPid() int { func (d *Debugger) AttachPid() int {
@ -2169,13 +2266,13 @@ func (d *Debugger) AttachPid() int {
} }
func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult { func (d *Debugger) GetBufferedTracepoints() []api.TracepointResult {
traces := d.target.GetBufferedTracepoints() traces := d.target.Selected.GetBufferedTracepoints()
if traces == nil { if traces == nil {
return nil return nil
} }
results := make([]api.TracepointResult, len(traces)) results := make([]api.TracepointResult, len(traces))
for i, trace := range 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].FunctionName = fn.Name
results[i].Line = l results[i].Line = l

@ -2,14 +2,9 @@ package debugger
import ( import (
"fmt" "fmt"
sys "golang.org/x/sys/unix"
) )
func attachErrorMessage(pid int, err error) error { func attachErrorMessage(pid int, err error) error {
//TODO: mention certificates? //TODO: mention certificates?
return fmt.Errorf("could not attach to pid %d: %s", pid, err) 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 ( import (
"fmt" "fmt"
sys "golang.org/x/sys/unix"
) )
func attachErrorMessage(pid int, err error) error { func attachErrorMessage(pid int, err error) error {
return fmt.Errorf("could not attach to pid %d: %s", pid, err) 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" "io/ioutil"
"os" "os"
"syscall" "syscall"
sys "golang.org/x/sys/unix"
) )
//lint:file-ignore ST1005 errors here can be capitalized //lint:file-ignore ST1005 errors here can be capitalized
@ -32,7 +30,3 @@ func attachErrorMessage(pid int, err error) error {
} }
return fallbackerr 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) 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 { func verifyBinaryFormat(exePath string) error {
f, err := os.Open(exePath) f, err := os.Open(exePath)
if err != nil { if err != nil {