diff --git a/_fixtures/spawn.go b/_fixtures/spawn.go new file mode 100644 index 00000000..75f5c418 --- /dev/null +++ b/_fixtures/spawn.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "time" +) + +func traceme1() { + fmt.Printf("parent starting\n") +} + +func traceme2(n string) { + fmt.Printf("hello from %s\n", n) +} + +func traceme3() { + fmt.Printf("done\n") +} + +func main() { + exe, _ := os.Executable() + switch os.Args[1] { + case "spawn": + traceme1() + n, _ := strconv.Atoi(os.Args[2]) + cmds := []*exec.Cmd{} + for i := 0; i < n; i++ { + cmd := exec.Command(exe, "child", fmt.Sprintf("C%d", i)) + cmd.Stdout = os.Stdout + cmd.Start() + cmds = append(cmds, cmd) + } + + for _, cmd := range cmds { + cmd.Wait() + } + + time.Sleep(1 * time.Second) + traceme3() + + case "child": + traceme2(os.Args[2]) + + } +} diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 148d3405..1407627c 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -204,7 +204,7 @@ var ErrUnrecognizedFormat = errors.New("unrecognized core format") // OpenCore will open the core file and return a Process struct. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, error) { +func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.TargetGroup, error) { var p *process var currentThread proc.Thread var err error @@ -222,14 +222,13 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e return nil, ErrNoThreads } - return proc.NewTarget(p, p.pid, currentThread, proc.NewTargetConfig{ - Path: exePath, + grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, DisableAsyncPreempt: false, - StopReason: proc.StopAttached, CanDump: false, - ContinueOnce: continueOnce, }) + _, err = addTarget(p, p.pid, currentThread, exePath, proc.StopAttached) + return grp, err } // BinInfo will return the binary info. @@ -310,6 +309,11 @@ func (p *process) WriteMemory(addr uint64, data []byte) (int, error) { return 0, ErrWriteCore } +// FollowExec enables (or disables) follow exec mode +func (p *process) FollowExec(bool) error { + return nil +} + // ProcessMemory returns the memory of this thread's process. func (t *thread) ProcessMemory() proc.MemoryReadWriter { return t.p @@ -419,7 +423,7 @@ func (p *process) ClearInternalBreakpoints() error { return nil } -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { +func (*process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { return nil, proc.StopUnknown, ErrContinueCore } diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 3ee4a3e1..4daabd9c 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -198,7 +198,7 @@ func TestSplicedReader(t *testing.T) { } } -func withCoreFile(t *testing.T, name, args string) *proc.Target { +func withCoreFile(t *testing.T, name, args string) *proc.TargetGroup { // This is all very fragile and won't work on hosts with non-default core patterns. // Might be better to check in the binary and core? tempDir := t.TempDir() @@ -251,8 +251,8 @@ func TestCore(t *testing.T) { if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { t.Skip("disabled on linux, Github Actions, with PIE buildmode") } - p := withCoreFile(t, "panic", "") - grp := proc.NewGroup(p) + grp := withCoreFile(t, "panic", "") + p := grp.Selected recorded, _ := grp.Recorded() if !recorded { @@ -324,7 +324,8 @@ func TestCoreFpRegisters(t *testing.T) { t.Skip("not supported in go1.10 and later") } - p := withCoreFile(t, "fputest/", "panic") + grp := withCoreFile(t, "fputest/", "panic") + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { @@ -407,7 +408,8 @@ func TestCoreWithEmptyString(t *testing.T) { if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { t.Skip("disabled on linux, Github Actions, with PIE buildmode") } - p := withCoreFile(t, "coreemptystring", "") + grp := withCoreFile(t, "coreemptystring", "") + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -452,10 +454,11 @@ func TestMinidump(t *testing.T) { fix := test.BuildFixture("sleep", buildFlags) mdmpPath := procdump(t, fix.Path) - p, err := OpenCore(mdmpPath, fix.Path, []string{}) + grp, err := OpenCore(mdmpPath, fix.Path, []string{}) if err != nil { t.Fatalf("OpenCore: %v", err) } + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 8f99c999..2bb87199 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -278,7 +278,7 @@ func newProcess(process *os.Process) *gdbProcess { } // Listen waits for a connection from the stub. -func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { acceptChan := make(chan net.Conn) go func() { @@ -300,7 +300,7 @@ func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugIn } // Dial attempts to connect to the stub. -func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { for { conn, err := net.Dial("tcp", addr) if err == nil { @@ -321,7 +321,7 @@ func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []str // program and the PID of the target process, both are optional, however // some stubs do not provide ways to determine path and pid automatically // and Connect will be unable to function without knowing them. -func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { p.conn.conn = conn p.conn.pid = pid err := p.conn.handshake(p.regnames) @@ -450,7 +450,7 @@ func getLdEnvVars() []string { // LLDBLaunch starts an instance of lldb-server and connects to it, asking // it to launch the specified target program with the specified arguments // (cmd) on the specified directory wd. -func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { if runtime.GOOS == "windows" { return nil, ErrUnsupportedOS } @@ -567,11 +567,11 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ p := newProcess(process.Process) p.conn.isDebugserver = isDebugserver - var tgt *proc.Target + var grp *proc.TargetGroup if listener != nil { - tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched) + grp, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched) } else { - tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched) + grp, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched) } if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) { // Make the target process the controlling process of the tty if it is a foreground process. @@ -580,7 +580,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err) } } - return tgt, err + return grp, err } // LLDBAttach starts an instance of lldb-server and connects to it, asking @@ -588,7 +588,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ // Path is path to the target's executable, path only needs to be specified // for some stubs that do not provide an automated way of determining it // (for example debugserver). -func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, error) { +func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.TargetGroup, error) { if runtime.GOOS == "windows" { return nil, ErrUnsupportedOS } @@ -633,13 +633,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err p := newProcess(process.Process) p.conn.isDebugserver = isDebugserver - var tgt *proc.Target + var grp *proc.TargetGroup if listener != nil { - tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached) + grp, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached) } else { - tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached) + grp, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached) } - return tgt, err + return grp, err } // EntryPoint will return the process entry point address, useful for @@ -673,7 +673,7 @@ func (p *gdbProcess) EntryPoint() (uint64, error) { // initialize uses qProcessInfo to load the inferior's PID and // executable path. This command is not supported by all stubs and not all // stubs will report both the PID and executable path. -func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { var err error if path == "" { // If we are attaching to a running process and the user didn't specify @@ -724,19 +724,18 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason return nil, err } } - tgt, err := proc.NewTarget(p, p.conn.pid, p.currentThread, proc.NewTargetConfig{ - Path: path, + grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, DisableAsyncPreempt: runtime.GOOS == "darwin", StopReason: stopReason, CanDump: runtime.GOOS == "darwin", - ContinueOnce: continueOnce, }) + _, err = addTarget(p, p.conn.pid, p.currentThread, path, stopReason) if err != nil { p.Detach(true) return nil, err } - return tgt, nil + return grp, nil } func queryProcessInfo(p *gdbProcess, pid int) (int, string, error) { @@ -821,11 +820,7 @@ const ( debugServerTargetExcBreakpoint = 0x96 ) -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { - if len(procs) != 1 { - panic("not implemented") - } - p := procs[0].(*gdbProcess) +func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { if p.exited { return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid} } @@ -1307,6 +1302,11 @@ func (p *gdbProcess) EraseBreakpoint(bp *proc.Breakpoint) error { return p.conn.clearBreakpoint(bp.Addr, watchTypeToBreakpointType(bp.WatchType), kind) } +// FollowExec enables (or disables) follow exec mode +func (p *gdbProcess) FollowExec(bool) error { + return errors.New("follow exec not supported") +} + type threadUpdater struct { p *gdbProcess seen map[int]bool diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index e133b12b..52a32080 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -124,7 +124,7 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir // Replay starts an instance of rr in replay mode, with the specified trace // directory, and connects to it. -func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) { +func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.TargetGroup, error) { if err := checkRRAvailable(); err != nil { return nil, err } @@ -279,7 +279,7 @@ func rrParseGdbCommand(line string) rrInit { } // RecordAndReplay acts like calling Record and then Replay. -func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) { +func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.TargetGroup, string, error) { tracedir, err := Record(cmd, wd, quiet, redirects) if tracedir == "" { return nil, "", err diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index a83e249f..9c96280f 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -30,14 +30,12 @@ func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup, t.Skip("test skipped, rr not found") } t.Log("recording") - p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{}) + grp, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{}) if err != nil { t.Fatal("Launch():", err) } t.Logf("replaying %q", tracedir) - grp := proc.NewGroup(p) - defer grp.Detach(true) fn(grp, fixture) diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 4027b40e..d2a0ef35 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -7,6 +7,11 @@ import ( "github.com/go-delve/delve/pkg/proc/internal/ebpf" ) +// ProcessGroup is a group of processes that are resumed at the same time. +type ProcessGroup interface { + ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error) +} + // Process represents the target of the debugger. This // target could be a system process, core file, etc. // @@ -57,6 +62,9 @@ type ProcessInternal interface { // StartCallInjection notifies the backend that we are about to inject a function call. StartCallInjection() (func(), error) + + // FollowExec enables (or disables) follow exec mode + FollowExec(bool) error } // RecordingManipulation is an interface for manipulating process recordings. diff --git a/pkg/proc/native/followexec_other.go b/pkg/proc/native/followexec_other.go new file mode 100644 index 00000000..2520ab0d --- /dev/null +++ b/pkg/proc/native/followexec_other.go @@ -0,0 +1,11 @@ +//go:build !linux +// +build !linux + +package native + +import "errors" + +// FollowExec enables (or disables) follow exec mode +func (*nativeProcess) FollowExec(bool) error { + return errors.New("follow exec not implemented") +} diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go index 3aae7e46..d0f7b19a 100644 --- a/pkg/proc/native/nonative_darwin.go +++ b/pkg/proc/native/nonative_darwin.go @@ -16,12 +16,12 @@ import ( var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") // Launch returns ErrNativeBackendDisabled. -func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) { +func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) { return nil, ErrNativeBackendDisabled } // Attach returns ErrNativeBackendDisabled. -func Attach(_ int, _ []string) (*proc.Target, error) { +func Attach(_ int, _ []string) (*proc.TargetGroup, error) { return nil, ErrNativeBackendDisabled } @@ -57,11 +57,11 @@ func (dbp *nativeProcess) resume() error { panic(ErrNativeBackendDisabled) } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { panic(ErrNativeBackendDisabled) } -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (*processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { panic(ErrNativeBackendDisabled) } diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index b1c906a4..c3d6c864 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -24,11 +24,11 @@ type nativeProcess struct { // Thread used to read and write memory memthread *nativeThread - os *osProcessDetails - firstStart bool - ptraceChan chan func() - ptraceDoneChan chan interface{} - childProcess bool // this process was launched, not attached to + os *osProcessDetails + firstStart bool + ptraceThread *ptraceThread + childProcess bool // this process was launched, not attached to + followExec bool // automatically attach to new processes // Controlling terminal file descriptor for // this process. @@ -45,19 +45,30 @@ type nativeProcess struct { // `handlePtraceFuncs`. func newProcess(pid int) *nativeProcess { dbp := &nativeProcess{ - pid: pid, - threads: make(map[int]*nativeThread), - breakpoints: proc.NewBreakpointMap(), - firstStart: true, - os: new(osProcessDetails), - ptraceChan: make(chan func()), - ptraceDoneChan: make(chan interface{}), - bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), + pid: pid, + threads: make(map[int]*nativeThread), + breakpoints: proc.NewBreakpointMap(), + firstStart: true, + os: new(osProcessDetails), + ptraceThread: newPtraceThread(), + bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), } - go dbp.handlePtraceFuncs() return dbp } +// newChildProcess is like newProcess but uses the same ptrace thread as dbp. +func newChildProcess(dbp *nativeProcess, pid int) *nativeProcess { + return &nativeProcess{ + pid: pid, + threads: make(map[int]*nativeThread), + breakpoints: proc.NewBreakpointMap(), + firstStart: true, + os: new(osProcessDetails), + ptraceThread: dbp.ptraceThread.acquire(), + bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), + } +} + // BinInfo will return the binary info struct associated with this process. func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo { return dbp.bi @@ -172,23 +183,58 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error { return dbp.memthread.clearSoftwareBreakpoint(bp) } -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { - if len(procs) != 1 { +type processGroup struct { + procs []*nativeProcess + addTarget proc.AddTargetFunc +} + +func (procgrp *processGroup) numValid() int { + n := 0 + for _, p := range procgrp.procs { + if ok, _ := p.Valid(); ok { + n++ + } + } + return n +} + +func (procgrp *processGroup) procForThread(tid int) *nativeProcess { + for _, p := range procgrp.procs { + if p.threads[tid] != nil { + return p + } + } + return nil +} + +func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason) (*proc.Target, error) { + tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason) + if err != nil { + return nil, err + } + procgrp.procs = append(procgrp.procs, p) + return tgt, nil +} + +func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { + if len(procgrp.procs) != 1 && runtime.GOOS != "linux" { panic("not implemented") } - dbp := procs[0].(*nativeProcess) - if dbp.exited { - return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid} + if procgrp.numValid() == 0 { + return nil, proc.StopExited, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} } for { - - if err := dbp.resume(); err != nil { - return nil, proc.StopUnknown, err - } - - for _, th := range dbp.threads { - th.CurrentBreakpoint.Clear() + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + if err := dbp.resume(); err != nil { + return nil, proc.StopUnknown, err + } + for _, th := range dbp.threads { + th.CurrentBreakpoint.Clear() + } } if cctx.ResumeChan != nil { @@ -196,16 +242,29 @@ func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) cctx.ResumeChan = nil } - trapthread, err := dbp.trapWait(-1) + trapthread, err := trapWait(procgrp, -1) if err != nil { return nil, proc.StopUnknown, err } - trapthread, err = dbp.stop(cctx, trapthread) + trapthread, err = procgrp.stop(cctx, trapthread) if err != nil { return nil, proc.StopUnknown, err } if trapthread != nil { + dbp := procgrp.procForThread(trapthread.ID) dbp.memthread = trapthread + // refresh memthread for every other process + for _, p2 := range procgrp.procs { + if p2.exited || p2 == dbp { + continue + } + for _, th := range p2.threads { + p2.memthread = th + if th.SoftExc() { + break + } + } + } return trapthread, proc.StopUnknown, nil } } @@ -226,21 +285,26 @@ func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakp return nil, false } -// initialize will ensure that all relevant information is loaded -// so the process is ready to be debugged. -func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.Target, error) { +func (dbp *nativeProcess) initializeBasic() error { if err := initialize(dbp); err != nil { - return nil, err + return err } if err := dbp.updateThreadList(); err != nil { - return nil, err + return err } + return nil +} + +// initialize will ensure that all relevant information is loaded +// so the process is ready to be debugged. +func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) { + dbp.initializeBasic() stopReason := proc.StopLaunched if !dbp.childProcess { stopReason = proc.StopAttached } - tgt, err := proc.NewTarget(dbp, dbp.pid, dbp.memthread, proc.NewTargetConfig{ - Path: path, + procgrp := &processGroup{} + grp, addTarget := proc.NewGroup(procgrp, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, // We disable asyncpreempt for the following reasons: @@ -253,20 +317,21 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc // See: https://go-review.googlesource.com/c/go/+/208126 DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), - StopReason: stopReason, - CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"), - ContinueOnce: continueOnce, + StopReason: stopReason, + CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"), }) + procgrp.addTarget = addTarget + tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason) if err != nil { return nil, err } if dbp.bi.Arch.Name == "arm64" { dbp.iscgo = tgt.IsCgo() } - return tgt, nil + return grp, nil } -func (dbp *nativeProcess) handlePtraceFuncs() { +func (pt *ptraceThread) handlePtraceFuncs() { // We must ensure here that we are running on the same thread during // while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects // all commands after PTRACE_ATTACH to come from the same thread. @@ -279,21 +344,20 @@ func (dbp *nativeProcess) handlePtraceFuncs() { defer runtime.UnlockOSThread() } - for fn := range dbp.ptraceChan { + for fn := range pt.ptraceChan { fn() - dbp.ptraceDoneChan <- nil + pt.ptraceDoneChan <- nil } } func (dbp *nativeProcess) execPtraceFunc(fn func()) { - dbp.ptraceChan <- fn - <-dbp.ptraceDoneChan + dbp.ptraceThread.ptraceChan <- fn + <-dbp.ptraceThread.ptraceDoneChan } func (dbp *nativeProcess) postExit() { dbp.exited = true - close(dbp.ptraceChan) - close(dbp.ptraceDoneChan) + dbp.ptraceThread.release() dbp.bi.Close() if dbp.ctty != nil { dbp.ctty.Close() @@ -349,3 +413,32 @@ func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr return stdin, stdout, stderr, closefn, nil } + +type ptraceThread struct { + ptraceRefCnt int + ptraceChan chan func() + ptraceDoneChan chan interface{} +} + +func newPtraceThread() *ptraceThread { + pt := &ptraceThread{ + ptraceChan: make(chan func()), + ptraceDoneChan: make(chan interface{}), + ptraceRefCnt: 1, + } + go pt.handlePtraceFuncs() + return pt +} + +func (pt *ptraceThread) acquire() *ptraceThread { + pt.ptraceRefCnt++ + return pt +} + +func (pt *ptraceThread) release() { + pt.ptraceRefCnt-- + if pt.ptraceRefCnt == 0 { + close(pt.ptraceChan) + close(pt.ptraceDoneChan) + } +} diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index fa0f7558..c8eca1da 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -42,7 +42,7 @@ func (os *osProcessDetails) Close() {} // custom fork/exec process in order to take advantage of // PT_SIGEXC on Darwin which will turn Unix signals into // Mach exceptions. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err @@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin if err != nil { return nil, err } - if _, err := dbp.stop(nil, nil); err != nil { + if _, err := dbp.stop(nil); err != nil { return nil, err } @@ -137,7 +137,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin } // Attach to an existing process with the given PID. -func Attach(pid int, _ []string) (*proc.Target, error) { +func Attach(pid int, _ []string) (*proc.TargetGroup, error) { if err := macutil.CheckRosetta(); err != nil { return nil, err } @@ -291,6 +291,10 @@ func findExecutable(path string, pid int) string { return path } +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return procgrp.procs[0].trapWait(pid) +} + func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { for { task := dbp.os.task @@ -429,7 +433,11 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + return procgrp.procs[0].stop(trapthread) +} + +func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index 44fbc6b6..40581e5a 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -55,7 +55,7 @@ func (os *osProcessDetails) Close() {} // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { var ( process *exec.Cmd err error @@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str // Attach to an existing process with the given PID. Once attached, if // the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { +func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) { dbp := newProcess(pid) var err error @@ -227,8 +227,8 @@ func findExecutable(path string, pid int) string { return path } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, trapWaitNormal) +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal) } type trapWaitMode uint8 @@ -403,7 +403,11 @@ func (dbp *nativeProcess) resume() error { // Used by ContinueOnce // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + return procgrp.procs[0].stop(trapthread) +} + +func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 29a08b50..4686ebe6 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -62,7 +62,7 @@ func (os *osProcessDetails) Close() { // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { var ( process *exec.Cmd err error @@ -141,7 +141,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str // Attach to an existing process with the given PID. Once attached, if // the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { +func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) { dbp := newProcess(pid) var err error @@ -237,6 +237,11 @@ func (dbp *nativeProcess) requestManualStop() (err error) { return sys.Kill(dbp.pid, sys.SIGTRAP) } +const ( + ptraceOptionsNormal = syscall.PTRACE_O_TRACECLONE + ptraceOptionsFollowExec = syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEVFORK | syscall.PTRACE_O_TRACEEXEC +) + // Attach to a newly created thread, and store that thread in our list of // known threads. func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) { @@ -244,6 +249,11 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) return thread, nil } + ptraceOptions := ptraceOptionsNormal + if dbp.followExec { + ptraceOptions = ptraceOptionsFollowExec + } + var err error if attach { dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) }) @@ -263,12 +273,12 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) } } - dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) + dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) if err == syscall.ESRCH { if _, _, err = dbp.waitFast(tid); err != nil { return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) } - dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) + dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) if err == syscall.ESRCH { return nil, err } @@ -318,8 +328,8 @@ func findExecutable(path string, pid int) string { return path } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, 0) +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return trapWaitInternal(procgrp, pid, 0) } type trapWaitOptions uint8 @@ -330,14 +340,21 @@ const ( trapWaitDontCallExitGuard ) -func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*nativeThread, error) { +func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) { + var waitdbp *nativeProcess = nil + if len(procgrp.procs) == 1 { + // Note that waitdbp is only used to call (*nativeProcess).wait which will + // behave correctly if waitdbp == nil. + waitdbp = procgrp.procs[0] + } + halt := options&trapWaitHalt != 0 for { wopt := 0 if options&trapWaitNohang != 0 { wopt = sys.WNOHANG } - wpid, status, err := dbp.wait(pid, wopt) + wpid, status, err := waitdbp.wait(pid, wopt) if err != nil { return nil, fmt.Errorf("wait err %s %d", err, pid) } @@ -347,14 +364,27 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n } continue } - th, ok := dbp.threads[wpid] - if ok { - th.Status = (*waitStatus)(status) + dbp := procgrp.procForThread(wpid) + var th *nativeThread + if dbp != nil { + var ok bool + th, ok = dbp.threads[wpid] + if ok { + th.Status = (*waitStatus)(status) + } + } else { + dbp = procgrp.procs[0] } if status.Exited() { if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + } + if halt { + return nil, nil + } + continue } delete(dbp.threads, wpid) continue @@ -363,15 +393,24 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n // Signaled means the thread was terminated due to a signal. if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} + } + if halt { + return nil, nil + } + continue } // does this ever happen? delete(dbp.threads, wpid) continue } - if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { + if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_CLONE || status.TrapCause() == sys.PTRACE_EVENT_VFORK) { // A traced thread has cloned a new thread, grab the pid and // add it to our list of traced threads. + // If TrapCause() is sys.PTRACE_EVENT_VFORK this is actually a new + // process, but treat it as a normal thread until exec happens, so that + // we can initialize the new process normally. var cloned uint dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) }) if err != nil { @@ -410,6 +449,34 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n } continue } + if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_EXEC) { + // A thread called exec and we now have a new process. Retrieve the + // thread ID of the exec'ing thread with PtraceGetEventMsg to remove it + // and create a new nativeProcess object to track the new process. + var tid uint + dbp.execPtraceFunc(func() { tid, err = sys.PtraceGetEventMsg(wpid) }) + if err == nil { + delete(dbp.threads, int(tid)) + } + dbp = newChildProcess(procgrp.procs[0], wpid) + dbp.followExec = true + dbp.initializeBasic() + _, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched) + if err != nil { + _ = dbp.Detach(false) + return nil, err + } + if halt { + return nil, nil + } + // TODO(aarzilli): if we want to give users the ability to stop the target + // group on exec here is where we should return + err = dbp.threads[dbp.pid].Continue() + if err != nil { + return nil, err + } + continue + } if th == nil { // Sometimes we get an unknown thread, ignore it? continue @@ -439,7 +506,10 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n // do the same thing we do if a thread quit if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + } + continue } delete(dbp.threads, wpid) } @@ -476,7 +546,7 @@ func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) { func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { var s sys.WaitStatus - if (pid != dbp.pid) || (options != 0) { + if (dbp == nil) || (pid != dbp.pid) || (options != 0) { wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) return wpid, &s, err } @@ -506,12 +576,12 @@ func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { } } -func (dbp *nativeProcess) exitGuard(err error) error { +func exitGuard(dbp *nativeProcess, procgrp *processGroup, err error) error { if err != sys.ESRCH { return err } if status(dbp.pid, dbp.os.comm) == statusZombie { - _, err := dbp.trapWaitInternal(-1, trapWaitDontCallExitGuard) + _, err := trapWaitInternal(procgrp, -1, trapWaitDontCallExitGuard) return err } @@ -538,21 +608,27 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { - if dbp.exited { - return nil, proc.ErrProcessExited{Pid: dbp.pid} +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} } - for _, th := range dbp.threads { - th.os.setbp = false + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + th.os.setbp = false + } } trapthread.os.setbp = true // check if any other thread simultaneously received a SIGTRAP for { - th, err := dbp.trapWaitInternal(-1, trapWaitNohang) + th, err := trapWaitInternal(procgrp, -1, trapWaitNohang) if err != nil { - return nil, dbp.exitGuard(err) + p := procgrp.procForThread(th.ID) + return nil, exitGuard(p, procgrp, err) } if th == nil { break @@ -560,10 +636,20 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ } // stop all threads that are still running - for _, th := range dbp.threads { - if th.os.running { - if err := th.stop(); err != nil { - return nil, dbp.exitGuard(err) + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.running { + if err := th.stop(); err != nil { + if err == sys.ESRCH { + // thread exited + delete(dbp.threads, th.ID) + } else { + return nil, exitGuard(dbp, procgrp, err) + } + } } } } @@ -571,26 +657,60 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ // wait for all threads to stop for { allstopped := true - for _, th := range dbp.threads { - if th.os.running { - allstopped = false - break + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.running { + allstopped = false + break + } } } if allstopped { break } - _, err := dbp.trapWaitInternal(-1, trapWaitHalt) + _, err := trapWaitInternal(procgrp, -1, trapWaitHalt) if err != nil { return nil, err } } - if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { - return nil, err + switchTrapthread := false + + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + err := stop1(cctx, dbp, trapthread, &switchTrapthread) + if err != nil { + return nil, err + } } - switchTrapthread := false + if switchTrapthread { + trapthreadID := trapthread.ID + trapthread = nil + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.setbp && th.ThreadID() != trapthreadID { + return th, nil + } + } + } + } + + return trapthread, nil +} + +func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativeThread, switchTrapthread *bool) error { + if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { + return err + } // set breakpoints on SIGTRAP threads var err1 error @@ -649,27 +769,13 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ // Will switch to a different thread for trapthread because we don't // want pkg/proc to believe that this thread was stopped by a // hardcoded breakpoint. - switchTrapthread = true + *switchTrapthread = true } } } } } - if err1 != nil { - return nil, err1 - } - - if switchTrapthread { - trapthreadID := trapthread.ID - trapthread = nil - for _, th := range dbp.threads { - if th.os.setbp && th.ThreadID() != trapthreadID { - return th, nil - } - } - } - - return trapthread, nil + return err1 } func (dbp *nativeProcess) detach(kill bool) error { @@ -788,6 +894,25 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) } +// FollowExec enables (or disables) follow exec mode +func (dbp *nativeProcess) FollowExec(v bool) error { + dbp.followExec = v + ptraceOptions := ptraceOptionsNormal + if dbp.followExec { + ptraceOptions = ptraceOptionsFollowExec + } + var err error + dbp.execPtraceFunc(func() { + for tid := range dbp.threads { + err = syscall.PtraceSetOptions(tid, ptraceOptions) + if err != nil { + return + } + } + }) + return err +} + func killProcess(pid int) error { return sys.Kill(pid, sys.SIGINT) } diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index d37249ed..705fe6e5 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -23,7 +23,7 @@ type osProcessDetails struct { func (os *osProcessDetails) Close() {} // Launch creates and begins debugging a new process. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.TargetGroup, error) { argv0Go := cmd[0] env := proc.DisableAsyncPreemptEnv() @@ -140,7 +140,7 @@ func findExePath(pid int) (string, error) { var debugPrivilegeRequested = false // Attach to an existing process with the given PID. -func Attach(pid int, _ []string) (*proc.Target, error) { +func Attach(pid int, _ []string) (*proc.TargetGroup, error) { var aperr error if !debugPrivilegeRequested { debugPrivilegeRequested = true @@ -427,7 +427,8 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa } } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + dbp := procgrp.procs[0] var err error var tid, exitCode int dbp.execPtraceFunc(func() { @@ -474,7 +475,8 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + dbp := procgrp.procs[0] if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go index 4c25cc7b..5d5572ab 100644 --- a/pkg/proc/native/threads_linux.go +++ b/pkg/proc/native/threads_linux.go @@ -22,6 +22,9 @@ type osSpecificDetails struct { func (t *nativeThread) stop() (err error) { err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP) if err != nil { + if err == sys.ESRCH { + return + } err = fmt.Errorf("stop err %s on thread %d", err, t.ID) return } diff --git a/pkg/proc/proc_export_test.go b/pkg/proc/proc_export_test.go deleted file mode 100644 index 45b67e5f..00000000 --- a/pkg/proc/proc_export_test.go +++ /dev/null @@ -1,42 +0,0 @@ -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() -} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 5a27329b..013d1c83 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -88,28 +88,28 @@ func skipUnlessOn(t testing.TB, reason string, conditions ...string) { } } -func withTestProcess(name string, t testing.TB, fn func(p *proc.Target, fixture protest.Fixture)) { +func withTestProcess(name string, t testing.TB, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) { withTestProcessArgs(name, t, ".", []string{}, 0, fn) } -func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, fixture protest.Fixture)) { +func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) { if buildMode == "pie" { buildFlags |= protest.BuildModePIE } fixture := protest.BuildFixture(name, buildFlags) - var p *proc.Target + var grp *proc.TargetGroup var err error var tracedir string switch testBackend { case "native": - p, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) + grp, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) case "lldb": - p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) + grp, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) case "rr": protest.MustHaveRecordingAllowed(t) t.Log("recording") - p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) + grp, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) t.Logf("replaying %q", tracedir) default: t.Fatal("unknown backend") @@ -119,10 +119,10 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu } defer func() { - p.Detach(true) + grp.Detach(true) }() - fn(p, fixture) + fn(grp.Selected, grp, fixture) } func getRegisters(p *proc.Target, t *testing.T) proc.Registers { @@ -174,8 +174,8 @@ func assertLineNumber(p *proc.Target, t *testing.T, lineno int, descr string) (s func TestExit(t *testing.T) { protest.AllowRecording(t) - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", err) @@ -191,10 +191,10 @@ func TestExit(t *testing.T) { func TestExitAfterContinue(t *testing.T) { protest.AllowRecording(t) - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "First Continue()") - err := p.Continue() + assertNoError(grp.Continue(), t, "First Continue()") + err := grp.Continue() pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", pe) @@ -272,8 +272,7 @@ func findFileLocation(p *proc.Target, t *testing.T, file string, lineno int) uin func TestHalt(t *testing.T) { stopChan := make(chan interface{}, 1) - withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.loop") assertNoError(grp.Continue(), t, "Continue") resumeChan := make(chan struct{}, 1) @@ -294,9 +293,9 @@ func TestHalt(t *testing.T) { func TestStep(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.helloworld") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") regs := getRegisters(p, t) rip := regs.PC() @@ -313,9 +312,9 @@ func TestStep(t *testing.T) { func TestBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.helloworld") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -334,10 +333,10 @@ func TestBreakpoint(t *testing.T) { func TestBreakpointInSeparateGoRoutine(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testthreads", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testthreads", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.anotherthread") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -351,7 +350,7 @@ func TestBreakpointInSeparateGoRoutine(t *testing.T) { } func TestBreakpointWithNonExistantFunction(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { _, err := p.SetBreakpoint(0, 0, proc.UserBreakpoint, nil) if err == nil { t.Fatal("Should not be able to break at non existent function") @@ -360,7 +359,7 @@ func TestBreakpointWithNonExistantFunction(t *testing.T) { } func TestClearBreakpointBreakpoint(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sleepytime") err := p.ClearBreakpoint(bp.Addr) @@ -434,7 +433,7 @@ func testseq2(t *testing.T, program string, initialLocation string, testcases [] func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *testing.T, program string, initialLocation string, testcases []seqTest) { protest.AllowRecording(t) - withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { var bp *proc.Breakpoint if initialLocation != "" { bp = setFunctionBreakpoint(p, t, initialLocation) @@ -456,22 +455,22 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te if traceTestseq2 { t.Log("next") } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") case contStep: if traceTestseq2 { t.Log("step") } - assertNoError(p.Step(), t, "Step() returned an error") + assertNoError(grp.Step(), t, "Step() returned an error") case contStepout: if traceTestseq2 { t.Log("stepout") } - assertNoError(p.StepOut(), t, "StepOut() returned an error") + assertNoError(grp.StepOut(), t, "StepOut() returned an error") case contContinue: if traceTestseq2 { t.Log("continue") } - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") if i == 0 { if traceTestseq2 { t.Log("clearing initial breakpoint") @@ -483,29 +482,29 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te if traceTestseq2 { t.Log("reverse-next") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.Next(), t, "reverse Next() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Next(), t, "reverse Next() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contReverseStep: if traceTestseq2 { t.Log("reverse-step") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.Step(), t, "reverse Step() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Step(), t, "reverse Step() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contReverseStepout: if traceTestseq2 { t.Log("reverse-stepout") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.StepOut(), t, "reverse StepOut() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.StepOut(), t, "reverse StepOut() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contContinueToBreakpoint: bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int)) if traceTestseq2 { t.Log("continue") } - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint() returned an error") } @@ -592,9 +591,9 @@ func TestNextConcurrent(t *testing.T) { {10, 11}, } protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") f, ln := currentLineNumber(p, t) initV := evalVariable(p, t, "n") initVval, _ := constant.Int64Val(initV.Value) @@ -609,7 +608,7 @@ func TestNextConcurrent(t *testing.T) { if ln != tc.begin { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location") v := evalVariable(p, t, "n") vval, _ := constant.Int64Val(v.Value) @@ -628,9 +627,9 @@ func TestNextConcurrentVariant2(t *testing.T) { {10, 11}, } protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") f, ln := currentLineNumber(p, t) initV := evalVariable(p, t, "n") initVval, _ := constant.Int64Val(initV.Value) @@ -644,7 +643,7 @@ func TestNextConcurrentVariant2(t *testing.T) { if ln != tc.begin { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") var vval int64 for { v := evalVariable(p, t, "n") @@ -661,7 +660,7 @@ func TestNextConcurrentVariant2(t *testing.T) { if vval == initVval { t.Fatal("Initial breakpoint triggered twice for the same goroutine") } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") } } f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location") @@ -706,7 +705,7 @@ func TestNextNetHTTP(t *testing.T) { {11, 12}, {12, 13}, } - withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { go func() { // Wait for program to start listening. for { @@ -719,7 +718,7 @@ func TestNextNetHTTP(t *testing.T) { } http.Get("http://127.0.0.1:9191") }() - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { t.Fatal(err) } f, ln := currentLineNumber(p, t) @@ -728,7 +727,7 @@ func TestNextNetHTTP(t *testing.T) { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to correct next location") } @@ -736,8 +735,8 @@ func TestNextNetHTTP(t *testing.T) { } func TestRuntimeBreakpoint(t *testing.T) { - withTestProcess("testruntimebreakpoint", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testruntimebreakpoint", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() if err != nil { t.Fatal(err) } @@ -764,9 +763,9 @@ func returnAddress(thread proc.Thread) (uint64, error) { func TestFindReturnAddress(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 24) - err := p.Continue() + err := grp.Continue() if err != nil { t.Fatal(err) } @@ -783,10 +782,10 @@ func TestFindReturnAddress(t *testing.T) { func TestFindReturnAddressTopOfStackFn(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testreturnaddress", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testreturnaddress", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fnName := "runtime.rt0_go" setFunctionBreakpoint(p, t, fnName) - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { t.Fatal(err) } if _, err := returnAddress(p.CurrentThread()); err == nil { @@ -797,14 +796,14 @@ func TestFindReturnAddressTopOfStackFn(t *testing.T) { func TestSwitchThread(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // With invalid thread id err := p.SwitchThread(-1) if err == nil { t.Fatal("Expected error for invalid thread id") } setFunctionBreakpoint(p, t, "main.main") - err = p.Continue() + err = grp.Continue() if err != nil { t.Fatal(err) } @@ -842,10 +841,10 @@ func TestCGONext(t *testing.T) { skipOn(t, "broken - see https://github.com/go-delve/delve/issues/3158", "darwin", "amd64") protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") }) } @@ -853,7 +852,7 @@ func TestCGOBreakpointLocation(t *testing.T) { protest.MustHaveCgo(t) protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "C.foo") if !strings.Contains(bp.File, "cgotest.go") { t.Fatalf("incorrect breakpoint location, expected cgotest.go got %s", bp.File) @@ -881,11 +880,11 @@ func TestStacktrace(t *testing.T) { {{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}}, } protest.AllowRecording(t) - withTestProcess("stacktraceprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stacktraceprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.stacktraceme") for i := range stacks { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") @@ -906,13 +905,13 @@ func TestStacktrace(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } func TestStacktrace2(t *testing.T) { - withTestProcess("retstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("retstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") @@ -923,7 +922,7 @@ func TestStacktrace2(t *testing.T) { t.Fatalf("Stack error at main.f()\n%v\n", locations) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") locations, err = proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.g"}, {17, "main.main"}}, locations, false) { @@ -977,10 +976,10 @@ func TestStacktraceGoroutine(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -1030,14 +1029,14 @@ func TestStacktraceGoroutine(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } func TestKill(t *testing.T) { skipOn(t, "N/A", "lldb") // k command presumably works but leaves the process around? - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { - if err := p.Detach(true); err != nil { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + if err := grp.Detach(true); err != nil { t.Fatal(err) } if valid, _ := p.Valid(); valid { @@ -1056,10 +1055,10 @@ func TestKill(t *testing.T) { }) } -func testGSupportFunc(name string, t *testing.T, p *proc.Target, fixture protest.Fixture) { +func testGSupportFunc(name string, t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, name+": Continue()") + assertNoError(grp.Continue(), t, name+": Continue()") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, name+": GetG()") @@ -1074,8 +1073,8 @@ func testGSupportFunc(name string, t *testing.T, p *proc.Target, fixture protest } func TestGetG(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { - testGSupportFunc("nocgo", t, p, fixture) + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testGSupportFunc("nocgo", t, p, grp, fixture) }) // On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973 @@ -1085,21 +1084,21 @@ func TestGetG(t *testing.T) { protest.MustHaveCgo(t) protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { - testGSupportFunc("cgo", t, p, fixture) + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testGSupportFunc("cgo", t, p, grp, fixture) }) } func TestContinueMulti(t *testing.T) { protest.AllowRecording(t) - withTestProcess("integrationprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("integrationprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp1 := setFunctionBreakpoint(p, t, "main.main") bp2 := setFunctionBreakpoint(p, t, "main.sayhi") mainCount := 0 sayhiCount := 0 for { - err := p.Continue() + err := grp.Continue() if valid, _ := p.Valid(); !valid { break } @@ -1130,8 +1129,8 @@ func TestBreakpointOnFunctionEntry(t *testing.T) { func TestProcessReceivesSIGCHLD(t *testing.T) { protest.AllowRecording(t) - withTestProcess("sigchldprog", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("sigchldprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() _, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %v", err) @@ -1140,9 +1139,9 @@ func TestProcessReceivesSIGCHLD(t *testing.T) { } func TestIssue239(t *testing.T) { - withTestProcess("is sue239", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("is sue239", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 17) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") }) } @@ -1235,8 +1234,8 @@ func TestVariableEvaluation(t *testing.T) { {"ba", reflect.Slice, nil, 200, 200, 64}, } - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, tc := range testcases { v := evalVariable(p, t, tc.name) @@ -1289,9 +1288,9 @@ func TestFrameEvaluation(t *testing.T) { if runtime.GOOS == "windows" { lenient = true } - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine()) @@ -1344,7 +1343,7 @@ func TestFrameEvaluation(t *testing.T) { } // Testing evaluation on frames - assertNoError(p.Continue(), t, "Continue() 2") + assertNoError(grp.Continue(), t, "Continue() 2") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") @@ -1372,8 +1371,8 @@ func TestThreadFrameEvaluation(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { t.SkipNow() } - withTestProcess("testdeadlock", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp { @@ -1390,8 +1389,8 @@ func TestThreadFrameEvaluation(t *testing.T) { } func TestPointerSetting(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") pval := func(n int64) { variable := evalVariable(p, t, "p1") @@ -1418,15 +1417,15 @@ func TestPointerSetting(t *testing.T) { } func TestVariableFunctionScoping(t *testing.T) { - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") evalVariable(p, t, "a1") evalVariable(p, t, "a2") // Move scopes, a1 exists here by a2 does not - err = p.Continue() + err = grp.Continue() assertNoError(err, t, "Continue() returned an error") evalVariable(p, t, "a1") @@ -1440,8 +1439,8 @@ func TestVariableFunctionScoping(t *testing.T) { func TestRecursiveStructure(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "aas") t.Logf("v: %v\n", v) }) @@ -1450,8 +1449,8 @@ func TestRecursiveStructure(t *testing.T) { func TestIssue316(t *testing.T) { // A pointer loop that includes one interface should not send dlv into an infinite loop protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") evalVariable(p, t, "iface5") }) } @@ -1459,8 +1458,8 @@ func TestIssue316(t *testing.T) { func TestIssue325(t *testing.T) { // nil pointer dereference when evaluating interfaces to function pointers protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") iface2fn1v := evalVariable(p, t, "iface2fn1") t.Logf("iface2fn1: %v\n", iface2fn1v) @@ -1471,11 +1470,11 @@ func TestIssue325(t *testing.T) { func TestBreakpointCounts(t *testing.T) { protest.AllowRecording(t) - withTestProcess("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 12) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1501,10 +1500,10 @@ func TestBreakpointCounts(t *testing.T) { } func TestHardcodedBreakpointCounts(t *testing.T) { - withTestProcess("hcbpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("hcbpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { counts := map[int64]int{} for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1540,8 +1539,8 @@ func TestHardcodedBreakpointCounts(t *testing.T) { func BenchmarkArray(b *testing.B) { // each bencharr struct is 128 bytes, bencharr is 64 elements long b.SetBytes(int64(64 * 128)) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "bencharr") @@ -1557,11 +1556,11 @@ func TestBreakpointCountsWithDetection(t *testing.T) { } m := map[int64]int64{} protest.AllowRecording(t) - withTestProcess("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 12) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1613,8 +1612,8 @@ func BenchmarkArrayPointer(b *testing.B) { // each bencharr struct is 128 bytes, benchparr is an array of 64 pointers to bencharr // each read will read 64 bencharr structs plus the 64 pointers of benchparr b.SetBytes(int64(64*128 + 64*8)) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "bencharr") @@ -1627,8 +1626,8 @@ func BenchmarkMap(b *testing.B) { // each string key has an average of 9 character // reading strings and the map structure imposes a overhead that we ignore here b.SetBytes(int64(41 * (2*8 + 9))) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "m1") @@ -1637,8 +1636,8 @@ func BenchmarkMap(b *testing.B) { } func BenchmarkGoroutinesInfo(b *testing.B) { - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { p.ClearCaches() @@ -1651,11 +1650,11 @@ func BenchmarkGoroutinesInfo(b *testing.B) { func TestIssue262(t *testing.T) { // Continue does not work when the current breakpoint is set on a NOP instruction protest.AllowRecording(t) - withTestProcess("issue262", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue262", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) - assertNoError(p.Continue(), t, "Continue()") - err := p.Continue() + assertNoError(grp.Continue(), t, "Continue()") + err := grp.Continue() if err == nil { t.Fatalf("No error on second continue") } @@ -1671,16 +1670,16 @@ func TestIssue305(t *testing.T) { // the internal breakpoints aren't cleared preventing further use of // 'next' command protest.AllowRecording(t) - withTestProcess("issue305", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue305", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 5) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next() 1") - assertNoError(p.Next(), t, "Next() 2") - assertNoError(p.Next(), t, "Next() 3") - assertNoError(p.Next(), t, "Next() 4") - assertNoError(p.Next(), t, "Next() 5") + assertNoError(grp.Next(), t, "Next() 1") + assertNoError(grp.Next(), t, "Next() 2") + assertNoError(grp.Next(), t, "Next() 3") + assertNoError(grp.Next(), t, "Next() 4") + assertNoError(grp.Next(), t, "Next() 5") }) } @@ -1688,8 +1687,8 @@ func TestPointerLoops(t *testing.T) { // Pointer loops through map entries, pointers and slices // Regression test for issue #341 protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, expr := range []string{"mapinf", "ptrinf", "sliceinf"} { t.Logf("requesting %s", expr) v := evalVariable(p, t, expr) @@ -1699,8 +1698,8 @@ func TestPointerLoops(t *testing.T) { } func BenchmarkLocalVariables(b *testing.B) { - withTestProcess("testvariables", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue() returned an error") + withTestProcess("testvariables", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue() returned an error") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, b, "Scope()") b.ResetTimer() @@ -1713,7 +1712,7 @@ func BenchmarkLocalVariables(b *testing.B) { func TestCondBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -1721,7 +1720,7 @@ func TestCondBreakpoint(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") nvar := evalVariable(p, t, "n") @@ -1734,7 +1733,7 @@ func TestCondBreakpoint(t *testing.T) { func TestCondBreakpointError(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -1742,7 +1741,7 @@ func TestCondBreakpointError(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - err := p.Continue() + err := grp.Continue() if err == nil { t.Fatalf("No error on first Continue()") } @@ -1757,7 +1756,7 @@ func TestCondBreakpointError(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - err = p.Continue() + err = grp.Continue() if err != nil { if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on second Continue(): %v", err) @@ -1774,14 +1773,14 @@ func TestCondBreakpointError(t *testing.T) { } func TestHitCondBreakpointEQ(t *testing.T) { - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token Val int }{token.EQL, 3} - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1789,7 +1788,7 @@ func TestHitCondBreakpointEQ(t *testing.T) { t.Fatalf("Stopped on wrong hitcount %d\n", i) } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on Continue(): %v", err) } @@ -1798,7 +1797,7 @@ func TestHitCondBreakpointEQ(t *testing.T) { func TestHitCondBreakpointGEQ(t *testing.T) { protest.AllowRecording(t) - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token @@ -1806,7 +1805,7 @@ func TestHitCondBreakpointGEQ(t *testing.T) { }{token.GEQ, 3} for it := 3; it <= 10; it++ { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1815,13 +1814,13 @@ func TestHitCondBreakpointGEQ(t *testing.T) { } } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") }) } func TestHitCondBreakpointREM(t *testing.T) { protest.AllowRecording(t) - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token @@ -1829,7 +1828,7 @@ func TestHitCondBreakpointREM(t *testing.T) { }{token.REM, 2} for it := 2; it <= 10; it += 2 { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1838,7 +1837,7 @@ func TestHitCondBreakpointREM(t *testing.T) { } } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on Continue(): %v", err) } @@ -1848,8 +1847,8 @@ func TestHitCondBreakpointREM(t *testing.T) { func TestIssue356(t *testing.T) { // slice with a typedef does not get printed correctly protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") mmvar := evalVariable(p, t, "mainMenu") if mmvar.Kind != reflect.Slice { t.Fatalf("Wrong kind for mainMenu: %v\n", mmvar.Kind) @@ -1858,11 +1857,11 @@ func TestIssue356(t *testing.T) { } func TestStepIntoFunction(t *testing.T) { - withTestProcess("teststep", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // Continue until breakpoint - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") // Step into function - assertNoError(p.Step(), t, "Step() returned an error") + assertNoError(grp.Step(), t, "Step() returned an error") // We should now be inside the function. loc, err := p.CurrentThread().Location() if err != nil { @@ -1883,10 +1882,10 @@ func TestStepIntoFunction(t *testing.T) { func TestIssue332_Part1(t *testing.T) { // Next shouldn't step inside a function call protest.AllowRecording(t) - withTestProcess("issue332", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 8) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "first Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "first Next()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { @@ -1907,13 +1906,13 @@ func TestIssue332_Part2(t *testing.T) { // which leads to 'next' and 'stack' failing with error "could not find FDE for PC: " // because the incorrect FDE data leads to reading the wrong stack address as the return address protest.AllowRecording(t) - withTestProcess("issue332", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 8) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") // step until we enter changeMe for { - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { @@ -1935,10 +1934,10 @@ func TestIssue332_Part2(t *testing.T) { t.Fatalf("Step did not skip the prologue: current pc: %x, first instruction after prologue: %x", pc, pcAfterPrologue) } - assertNoError(p.Next(), t, "first Next()") - assertNoError(p.Next(), t, "second Next()") - assertNoError(p.Next(), t, "third Next()") - err = p.Continue() + assertNoError(grp.Next(), t, "first Next()") + assertNoError(grp.Next(), t, "second Next()") + assertNoError(grp.Next(), t, "third Next()") + err = grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { assertNoError(err, t, "final Continue()") } @@ -1949,9 +1948,9 @@ func TestIssue414(t *testing.T) { skipOn(t, "broken", "linux", "386", "pie") // test occasionally hangs on linux/386/pie // Stepping until the program exits protest.AllowRecording(t) - withTestProcess("math", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("math", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") for { pc := currentPC(p, t) f, ln := currentLineNumber(p, t) @@ -1961,9 +1960,9 @@ func TestIssue414(t *testing.T) { // of main.main just use Next. // See: https://github.com/go-delve/delve/pull/2082 if f == fixture.Source { - err = p.Step() + err = grp.Step() } else { - err = p.Next() + err = grp.Next() } if err != nil { if _, exited := err.(proc.ErrProcessExited); exited { @@ -1985,8 +1984,8 @@ func TestPackageVariables(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "Scope()") @@ -2014,15 +2013,15 @@ func TestIssue149(t *testing.T) { return } // setting breakpoint on break statement - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { findFileLocation(p, t, fixture.Source, 8) }) } func TestPanicBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic { t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) @@ -2031,8 +2030,8 @@ func TestPanicBreakpoint(t *testing.T) { } func TestCmdLineArgs(t *testing.T) { - expectSuccess := func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + expectSuccess := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() bp := p.CurrentThread().Breakpoint() if bp.Breakpoint != nil && bp.Logical.Name == proc.UnrecoveredPanic { t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp) @@ -2047,8 +2046,8 @@ func TestCmdLineArgs(t *testing.T) { } } - expectPanic := func(p *proc.Target, fixture protest.Fixture) { - p.Continue() + expectPanic := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.Continue() bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic { t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) @@ -2072,8 +2071,7 @@ func TestCmdLineArgs(t *testing.T) { func TestIssue462(t *testing.T) { skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error - withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { go func() { // Wait for program to start listening. for { @@ -2096,13 +2094,13 @@ func TestIssue462(t *testing.T) { func TestNextParked(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") // continue until a parked goroutine exists var parkedg *proc.G for parkedg == nil { - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return @@ -2136,7 +2134,7 @@ func TestNextParked(t *testing.T) { assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()") p.ClearBreakpoint(bp.Addr) - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") if p.SelectedGoroutine().ID != parkedg.ID { t.Fatalf("Next did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID) @@ -2146,14 +2144,14 @@ func TestNextParked(t *testing.T) { func TestStepParked(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") // continue until a parked goroutine exists var parkedg *proc.G LookForParkedG: for { - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return @@ -2183,7 +2181,7 @@ func TestStepParked(t *testing.T) { assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()") p.ClearBreakpoint(bp.Addr) - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") if p.SelectedGoroutine().ID != parkedg.ID { t.Fatalf("Step did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID) @@ -2215,7 +2213,7 @@ func TestUnsupportedArch(t *testing.T) { } defer os.Remove(outfile) - var p *proc.Target + var p *proc.TargetGroup switch testBackend { case "native": @@ -2243,17 +2241,17 @@ func TestIssue573(t *testing.T) { // calls to runtime.duffzero and runtime.duffcopy jump directly into the middle // of the function and the internal breakpoint set by StepInto may be missed. protest.AllowRecording(t) - withTestProcess("issue573", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue573", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.foo") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step() #1") - assertNoError(p.Step(), t, "Step() #2") // Bug exits here. - assertNoError(p.Step(), t, "Step() #3") // Third step ought to be possible; program ought not have exited. + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step() #1") + assertNoError(grp.Step(), t, "Step() #2") // Bug exits here. + assertNoError(grp.Step(), t, "Step() #3") // Third step ought to be possible; program ought not have exited. }) } func TestTestvariables2Prologue(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { addrEntry := p.BinInfo().LookupFunc["main.main"].Entry addrPrologue := findFunctionLocation(p, t, "main.main") if addrEntry == addrPrologue { @@ -2366,24 +2364,24 @@ func TestIssue561(t *testing.T) { // Step fails to make progress when PC is at a CALL instruction // where a breakpoint is also set. protest.AllowRecording(t) - withTestProcess("issue561", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue561", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 10) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 5, "wrong line number after Step,") }) } func TestGoroutineLables(t *testing.T) { - withTestProcess("goroutineLabels", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("goroutineLabels", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") if len(g.Labels()) != 0 { t.Fatalf("No labels expected") } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") g, err = proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") labels := g.Labels() @@ -2403,10 +2401,10 @@ func TestStepOut(t *testing.T) { func TestStepConcurrentDirect(t *testing.T) { skipOn(t, "broken - step concurrent", "windows", "arm64") protest.AllowRecording(t) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 37) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint()") @@ -2455,7 +2453,7 @@ func TestStepConcurrentDirect(t *testing.T) { if i == 0 { count++ } - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") } if count != 100 { @@ -2466,7 +2464,7 @@ func TestStepConcurrentDirect(t *testing.T) { func TestStepConcurrentPtr(t *testing.T) { protest.AllowRecording(t) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 24) for _, b := range p.Breakpoints().M { @@ -2480,7 +2478,7 @@ func TestStepConcurrentPtr(t *testing.T) { kvals := map[int64]int64{} count := 0 for { - err := p.Continue() + err := grp.Continue() _, exited := err.(proc.ErrProcessExited) if exited { break @@ -2508,12 +2506,12 @@ func TestStepConcurrentPtr(t *testing.T) { } kvals[gid] = k - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") for p.Breakpoints().HasSteppingBreakpoints() { if p.SelectedGoroutine().ID == gid { t.Fatalf("step did not step into function call (but internal breakpoints still active?) (%d %d)", gid, p.SelectedGoroutine().ID) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") } if p.SelectedGoroutine().ID != gid { @@ -2538,14 +2536,14 @@ func TestStepConcurrentPtr(t *testing.T) { func TestStepOutBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 13) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) // StepOut should be interrupted by a breakpoint on the same goroutine. setFileBreakpoint(p, t, fixture.Source, 14) - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") assertLineNumber(p, t, 14, "wrong line number") if p.Breakpoints().HasSteppingBreakpoints() { t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine") @@ -2555,14 +2553,14 @@ func TestStepOutBreakpoint(t *testing.T) { func TestNextBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 34) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) // Next should be interrupted by a breakpoint on the same goroutine. setFileBreakpoint(p, t, fixture.Source, 14) - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 14, "wrong line number") if p.Breakpoints().HasSteppingBreakpoints() { t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine") @@ -2572,8 +2570,7 @@ func TestNextBreakpoint(t *testing.T) { func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { grp.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints bp := setFileBreakpoint(p, t, fixture.Source, 34) assertNoError(grp.Continue(), t, "Continue()") @@ -2599,14 +2596,14 @@ func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) { func TestStepOutDefer(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextdefer", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextdefer", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) assertLineNumber(p, t, 9, "wrong line number") - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") f, l, _ := p.BinInfo().PCToLine(currentPC(p, t)) if f == fixture.Source || l == 6 { @@ -2631,13 +2628,13 @@ func TestStepInstructionOnBreakpoint(t *testing.T) { // StepInstruction should step one instruction forward when // PC is on a 1 byte instruction with a software breakpoint. protest.AllowRecording(t) - withTestProcess("break/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, filepath.ToSlash(filepath.Join(fixture.BuildDir, "break_amd64.s")), 4) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") pc := getRegisters(p, t).PC() - assertNoError(p.StepInstruction(), t, "StepInstruction()") + assertNoError(grp.StepInstruction(), t, "StepInstruction()") if pc == getRegisters(p, t).PC() { t.Fatal("Could not step a single instruction") } @@ -2646,10 +2643,10 @@ func TestStepInstructionOnBreakpoint(t *testing.T) { func TestStepOnCallPtrInstr(t *testing.T) { protest.AllowRecording(t) - withTestProcess("teststepprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 10) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") found := false @@ -2667,14 +2664,14 @@ func TestStepOnCallPtrInstr(t *testing.T) { found = true break } - assertNoError(p.StepInstruction(), t, "StepInstruction()") + assertNoError(grp.StepInstruction(), t, "StepInstruction()") } if !found { t.Fatal("Could not find CALL instruction") } - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) && !protest.RegabiSupported() { assertLineNumber(p, t, 6, "Step continued to wrong line,") @@ -2697,8 +2694,8 @@ func TestIssue594(t *testing.T) { // In particular the target should be able to cause a nil pointer // dereference panic and recover from it. protest.AllowRecording(t) - withTestProcess("issue594", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue594", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") var f string var ln int if testBackend == "rr" { @@ -2730,9 +2727,9 @@ func TestWorkDir(t *testing.T) { wd = "/private/tmp" } protest.AllowRecording(t) - withTestProcessArgs("workdir", t, wd, []string{}, 0, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("workdir", t, wd, []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 14) - p.Continue() + grp.Continue() v := evalVariable(p, t, "pwd") str := constant.StringVal(v.Value) if wd != str { @@ -2752,8 +2749,8 @@ func TestNegativeIntEvaluation(t *testing.T) { {"ni32", "int32", int64(-5)}, } protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { v := evalVariable(p, t, tc.name) if typ := v.RealType.String(); typ != tc.typ { @@ -2769,13 +2766,13 @@ func TestNegativeIntEvaluation(t *testing.T) { func TestIssue683(t *testing.T) { // Step panics when source file can not be found protest.AllowRecording(t) - withTestProcess("issue683", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue683", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") for i := 0; i < 20; i++ { // eventually an error about the source file not being found will be // returned, the important thing is that we shouldn't panic - err := p.Step() + err := grp.Step() if err != nil { break } @@ -2785,21 +2782,21 @@ func TestIssue683(t *testing.T) { func TestIssue664(t *testing.T) { protest.AllowRecording(t) - withTestProcess("issue664", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue664", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 4) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 5, "Did not continue to correct location,") }) } // Benchmarks (*Process).Continue + (*Scope).FunctionArguments func BenchmarkTrace(b *testing.B) { - withTestProcess("traceperf", b, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("traceperf", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, b, "main.PerfCheck") b.ResetTimer() for i := 0; i < b.N; i++ { - assertNoError(p.Continue(), b, "Continue()") + assertNoError(grp.Continue(), b, "Continue()") s, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, b, "Scope()") _, err = s.FunctionArguments(proc.LoadConfig{false, 0, 64, 0, 3, 0}) @@ -2815,9 +2812,9 @@ func TestNextInDeferReturn(t *testing.T) { // field being nil. // We need to deal with this without panicing. protest.AllowRecording(t) - withTestProcess("defercall", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("defercall", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "runtime.deferreturn") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") // Set a breakpoint on the deferred function so that the following loop // can not step out of the runtime.deferreturn and all the way to the @@ -2830,7 +2827,7 @@ func TestNextInDeferReturn(t *testing.T) { if loc.Fn != nil && loc.Fn.Name == "main.sampleFunction" { break } - assertNoError(p.Next(), t, fmt.Sprintf("Next() %d", i)) + assertNoError(grp.Next(), t, fmt.Sprintf("Next() %d", i)) } }) } @@ -2879,7 +2876,7 @@ func TestAttachDetach(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -2902,7 +2899,7 @@ func TestAttachDetach(t *testing.T) { }() assertNoError(p.Continue(), t, "Continue") - assertLineNumber(p, t, 11, "Did not continue to correct location,") + assertLineNumber(p.Selected, t, 11, "Did not continue to correct location,") assertNoError(p.Detach(false), t, "Detach") @@ -2923,8 +2920,8 @@ func TestAttachDetach(t *testing.T) { func TestVarSum(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") sumvar := evalVariable(p, t, "s1[0] + s1[1]") sumvarstr := constant.StringVal(sumvar.Value) if sumvarstr != "onetwo" { @@ -2938,8 +2935,8 @@ func TestVarSum(t *testing.T) { func TestPackageWithPathVar(t *testing.T) { protest.AllowRecording(t) - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") evalVariable(p, t, "pkg.SomeVar") evalVariable(p, t, "pkg.SomeVar.X") }) @@ -2948,8 +2945,8 @@ func TestPackageWithPathVar(t *testing.T) { func TestEnvironment(t *testing.T) { protest.AllowRecording(t) os.Setenv("SOMEVAR", "bah") - withTestProcess("testenv", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testenv", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "x") vv := constant.StringVal(v.Value) t.Logf("v = %q", vv) @@ -2975,24 +2972,24 @@ func TestRecursiveNext(t *testing.T) { } testseq("increment", contNext, testcases, "main.Increment", t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.Increment") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint") - assertNoError(p.Next(), t, "Next 1") - assertNoError(p.Next(), t, "Next 2") - assertNoError(p.Next(), t, "Next 3") + assertNoError(grp.Next(), t, "Next 1") + assertNoError(grp.Next(), t, "Next 2") + assertNoError(grp.Next(), t, "Next 3") frameoff0 := getFrameOff(p, t) - assertNoError(p.Step(), t, "Step") + assertNoError(grp.Step(), t, "Step") frameoff1 := getFrameOff(p, t) if frameoff0 == frameoff1 { t.Fatalf("did not step into function?") } assertLineNumber(p, t, 6, "program did not continue to expected location,") - assertNoError(p.Next(), t, "Next 4") + assertNoError(grp.Next(), t, "Next 4") assertLineNumber(p, t, 7, "program did not continue to expected location,") - assertNoError(p.StepOut(), t, "StepOut") + assertNoError(grp.StepOut(), t, "StepOut") assertLineNumber(p, t, 11, "program did not continue to expected location,") frameoff2 := getFrameOff(p, t) if frameoff0 != frameoff2 { @@ -3014,8 +3011,8 @@ func TestIssue877(t *testing.T) { } const envval = "/usr/local/lib" os.Setenv("DYLD_LIBRARY_PATH", envval) - withTestProcess("issue877", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue877", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "dyldenv") vv := constant.StringVal(v.Value) t.Logf("v = %q", vv) @@ -3030,8 +3027,8 @@ func TestIssue893(t *testing.T) { // executable, acceptable behaviors are: (a) no error, (b) no source at PC // error, (c) program runs to completion protest.AllowRecording(t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Next() + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Next() if err == nil { return } @@ -3050,17 +3047,17 @@ func TestIssue893(t *testing.T) { func TestStepInstructionNoGoroutine(t *testing.T) { protest.AllowRecording(t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // Call StepInstruction immediately after launching the program, it should // work even though no goroutine is selected. - assertNoError(p.StepInstruction(), t, "StepInstruction") + assertNoError(grp.StepInstruction(), t, "StepInstruction") }) } func TestIssue871(t *testing.T) { protest.AllowRecording(t) - withTestProcess("issue871", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue871", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") var scope *proc.EvalScope var err error @@ -3107,8 +3104,8 @@ func TestShadowedFlag(t *testing.T) { if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { return } - withTestProcess("testshadow", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("testshadow", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") locals, err := scope.LocalVariables(normalLoadConfig) @@ -3181,7 +3178,7 @@ func TestAttachStripped(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -3211,7 +3208,7 @@ func TestAttachStripped(t *testing.T) { func TestIssue844(t *testing.T) { // Conditional breakpoints should not prevent next from working if their // condition isn't met. - withTestProcess("nextcond", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nextcond", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) condbp := setFileBreakpoint(p, t, fixture.Source, 10) condbp.UserBreaklet().Cond = &ast.BinaryExpr{ @@ -3219,8 +3216,8 @@ func TestIssue844(t *testing.T) { X: &ast.Ident{Name: "n"}, Y: &ast.BasicLit{Kind: token.INT, Value: "11"}, } - assertNoError(p.Continue(), t, "Continue") - assertNoError(p.Next(), t, "Next") + assertNoError(grp.Continue(), t, "Continue") + assertNoError(grp.Next(), t, "Next") assertLineNumber(p, t, 10, "continued to wrong location,") }) } @@ -3362,11 +3359,11 @@ func TestCgoStacktrace(t *testing.T) { frameOffs := map[string]int64{} framePointerOffs := map[string]int64{} - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for itidx, tc := range testCases { t.Logf("iteration step %d", itidx) - assertNoError(p.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) + assertNoError(grp.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, fmt.Sprintf("GetG at iteration step %d", itidx)) @@ -3446,7 +3443,7 @@ func TestCgoSources(t *testing.T) { protest.MustHaveCgo(t) - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { sources := p.BinInfo().Sources for _, needle := range []string{"main.go", "hello.c"} { found := false @@ -3465,10 +3462,10 @@ func TestCgoSources(t *testing.T) { func TestSystemstackStacktrace(t *testing.T) { // check that we can follow a stack switch initiated by runtime.systemstack() - withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "runtime.startpanic_m") - assertNoError(p.Continue(), t, "first continue") - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "second continue") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") frames, err := g.Stacktrace(100, 0) @@ -3487,9 +3484,9 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { // - try to look at stacktraces of other goroutines // If one of the other goroutines is resizing its own stack the stack // command won't work for it. - withTestProcess("binarytrees", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("binarytrees", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "first continue") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") @@ -3497,7 +3494,7 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { setFunctionBreakpoint(p, t, "runtime.newstack") for { - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "second continue") g, err = proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") if g.ID == mainGoroutineID { @@ -3520,9 +3517,9 @@ func TestIssue1034(t *testing.T) { // The external linker on macOS produces an abbrev for DW_TAG_subprogram // without the "has children" flag, we should support this. - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") frames, err := p.SelectedGoroutine().Stacktrace(10, 0) assertNoError(err, t, "Stacktrace") scope := proc.FrameToScope(p, p.Memory(), nil, frames[2:]...) @@ -3540,9 +3537,9 @@ func TestIssue1008(t *testing.T) { // The external linker on macOS inserts "end of sequence" extended opcodes // in debug_line. which we should support correctly. - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") loc, err := p.CurrentThread().Location() assertNoError(err, t, "CurrentThread().Location()") t.Logf("location %v\n", loc) @@ -3585,41 +3582,41 @@ func TestDeclLine(t *testing.T) { t.Skip("go 1.9 and prior versions do not emit DW_AT_decl_line") } - withTestProcess("decllinetest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("decllinetest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 8) setFileBreakpoint(p, t, fixture.Source, 9) setFileBreakpoint(p, t, fixture.Source, 10) setFileBreakpoint(p, t, fixture.Source, 11) setFileBreakpoint(p, t, fixture.Source, 14) - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { testDeclLineCount(t, p, 8, []string{}) } else { testDeclLineCount(t, p, 8, []string{"a"}) } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") testDeclLineCount(t, p, 9, []string{"a"}) - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { testDeclLineCount(t, p, 10, []string{"a"}) } else { testDeclLineCount(t, p, 10, []string{"a", "b"}) } - assertNoError(p.Continue(), t, "Continue 4") + assertNoError(grp.Continue(), t, "Continue 4") testDeclLineCount(t, p, 11, []string{"a", "b"}) - assertNoError(p.Continue(), t, "Continue 5") + assertNoError(grp.Continue(), t, "Continue 5") testDeclLineCount(t, p, 14, []string{"a", "b"}) }) } func TestIssue1137(t *testing.T) { - withTestProcess("dotpackagesiface", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("dotpackagesiface", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "iface") assertNoError(v.Unreadable, t, "iface unreadable") v2 := evalVariable(p, t, "iface2") @@ -3644,16 +3641,16 @@ func TestIssue1101(t *testing.T) { // close proximity to main.main calling os.Exit() and causing the death of // the thread group leader. - withTestProcess("issue1101", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1101", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.f") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next() 1") - assertNoError(p.Next(), t, "Next() 2") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next() 1") + assertNoError(grp.Next(), t, "Next() 2") lastCmd := "Next() 3" - exitErr := p.Next() + exitErr := grp.Next() if exitErr == nil { lastCmd = "final Continue()" - exitErr = p.Continue() + exitErr = grp.Continue() } if pexit, exited := exitErr.(proc.ErrProcessExited); exited { if pexit.Status != 2 && testBackend != "lldb" && (runtime.GOOS != "linux" || runtime.GOARCH != "386") { @@ -3673,8 +3670,7 @@ func TestIssue1101(t *testing.T) { } func TestIssue1145(t *testing.T) { - withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 18) assertNoError(grp.Continue(), t, "Continue()") resumeChan := make(chan struct{}, 1) @@ -3693,8 +3689,7 @@ func TestIssue1145(t *testing.T) { } func TestHaltKeepsSteppingBreakpoints(t *testing.T) { - withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { grp.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints setFileBreakpoint(p, t, fixture.Source, 18) assertNoError(grp.Continue(), t, "Continue()") @@ -3723,7 +3718,7 @@ func TestDisassembleGlobalVars(t *testing.T) { if runtime.GOARCH == "386" && runtime.GOOS == "linux" && buildMode == "pie" { t.Skip("On 386 linux when pie, symLookup can't look up global variables") } - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { mainfn := p.BinInfo().LookupFunc["main.main"] regs, _ := p.CurrentThread().Registers() text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) @@ -3764,7 +3759,7 @@ func TestAllPCsForFileLines(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { l2pcs := p.BinInfo().AllPCsForFileLines(fixture.Source, []int{7, 20}) if len(l2pcs) != 2 { t.Fatalf("expected two map entries for %s:{%d,%d} (got %d: %v)", fixture.Source, 7, 20, len(l2pcs), l2pcs) @@ -3828,7 +3823,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { }, } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pcs, err := proc.FindFileLocation(p, fixture.Source, 7) assertNoError(err, t, "LineToPC") if len(pcs) < 2 { @@ -3841,7 +3836,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { } // first inlined call - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 20) assertNoError(err, t, "ThreadStacktrace") t.Logf("Stacktrace:\n") @@ -3868,7 +3863,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { } // second inlined call - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") frames, err = proc.ThreadStacktrace(p.CurrentThread(), 20) assertNoError(err, t, "ThreadStacktrace (2)") t.Logf("Stacktrace 2:\n") @@ -3965,7 +3960,7 @@ func TestInlineFunctionList(t *testing.T) { // TODO(qmuntal): seems to be an upstream issue, investigate. t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { var found bool for _, fn := range p.BinInfo().Functions { if strings.Contains(fn.Name, "inlineThis") { @@ -3985,7 +3980,7 @@ func TestInlineBreakpoint(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pcs, err := proc.FindFileLocation(p, fixture.Source, 17) if err != nil { t.Fatal(err) @@ -4013,7 +4008,7 @@ func TestDoubleInlineBreakpoint(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("doubleinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("doubleinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fns, err := p.BinInfo().FindFunction("main.(*Rectangle).Height") if err != nil { t.Fatal(err) @@ -4032,8 +4027,8 @@ func TestIssue951(t *testing.T) { t.Skip("scopes not implemented in <=go1.8") } - withTestProcess("issue951", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue951", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") args, err := scope.FunctionArguments(normalLoadConfig) @@ -4065,9 +4060,9 @@ func TestDWZCompression(t *testing.T) { t.Skip("dwz not installed") } - withTestProcessArgs("dwzcompression", t, ".", []string{}, protest.EnableDWZCompression, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("dwzcompression", t, ".", []string{}, protest.EnableDWZCompression, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "C.fortytwo") - assertNoError(p.Continue(), t, "first Continue()") + assertNoError(grp.Continue(), t, "first Continue()") val := evalVariable(p, t, "stdin") if val.RealType == nil { t.Errorf("Can't find type for \"stdin\" global variable") @@ -4077,9 +4072,9 @@ func TestDWZCompression(t *testing.T) { func TestMapLoadConfigWithReslice(t *testing.T) { // Check that load configuration is respected for resliced maps. - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { zolotovLoadCfg := proc.LoadConfig{FollowPointers: true, MaxStructFields: -1, MaxVariableRecurse: 3, MaxStringLen: 10, MaxArrayValues: 10} - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") m1, err := scope.EvalExpression("m1", zolotovLoadCfg) @@ -4105,10 +4100,10 @@ func TestStepOutReturn(t *testing.T) { if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { t.Skip("return variables aren't marked on 1.9 or earlier") } - withTestProcess("stepoutret", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stepoutret", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stepout") - assertNoError(p.Continue(), t, "Continue") - assertNoError(p.StepOut(), t, "StepOut") + assertNoError(grp.Continue(), t, "Continue") + assertNoError(grp.StepOut(), t, "StepOut") ret := p.CurrentThread().Common().ReturnValues(normalLoadConfig) if len(ret) != 2 { t.Fatalf("wrong number of return values %v", ret) @@ -4153,7 +4148,7 @@ func TestStepOutReturn(t *testing.T) { } func TestOptimizationCheck(t *testing.T) { - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fn := p.BinInfo().LookupFunc["main.main"] if fn.Optimized() { t.Fatalf("main.main is optimized") @@ -4161,7 +4156,7 @@ func TestOptimizationCheck(t *testing.T) { }) if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { - withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fn := p.BinInfo().LookupFunc["main.main"] if !fn.Optimized() { t.Fatalf("main.main is not optimized") @@ -4173,17 +4168,17 @@ func TestOptimizationCheck(t *testing.T) { func TestIssue1264(t *testing.T) { // It should be possible to set a breakpoint condition that consists only // of evaluating a single boolean variable. - withTestProcess("issue1264", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1264", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 8) bp.UserBreaklet().Cond = &ast.Ident{Name: "equalsTwo"} - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 8, "after continue") }) } func TestReadDefer(t *testing.T) { - withTestProcess("deferstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") frames, err := p.SelectedGoroutine().Stacktrace(10, proc.StacktraceReadDefers) assertNoError(err, t, "Stacktrace") @@ -4245,10 +4240,10 @@ func TestNextUnknownInstr(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { t.Skip("versions of Go before 1.10 can't assemble the instruction VPUNPCKLWD") } - withTestProcess("nodisasm/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nodisasm/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.asmFunc") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") }) } @@ -4274,8 +4269,8 @@ func TestReadDeferArgs(t *testing.T) { {2, 2, 1, -1}, } - withTestProcess("deferstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, test := range tests { scope, err := proc.ConvertEvalScope(p, -1, test.frame, test.deferCall) @@ -4315,8 +4310,7 @@ func TestReadDeferArgs(t *testing.T) { func TestIssue1374(t *testing.T) { // Continue did not work when stopped at a breakpoint immediately after calling CallFunction. protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue1374", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 7) assertNoError(grp.Continue(), t, "First Continue") assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),") @@ -4335,8 +4329,8 @@ func TestIssue1432(t *testing.T) { // the struct's type and then accessing a member field will still: // - perform auto-dereferencing on struct member access // - yield a Variable that's ultimately assignable (i.e. has an address) - withTestProcess("issue1432", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue1432", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") svar := evalVariable(p, t, "s") t.Logf("%#x", svar.Addr) @@ -4349,9 +4343,9 @@ func TestIssue1432(t *testing.T) { } func TestGoroutinesInfoLimit(t *testing.T) { - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 37) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gcount := 0 nextg := 0 @@ -4378,9 +4372,9 @@ func TestGoroutinesInfoLimit(t *testing.T) { } func TestIssue1469(t *testing.T) { - withTestProcess("issue1469", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1469", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 13) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gid2thread := make(map[int64][]proc.Thread) for _, thread := range p.ThreadList() { @@ -4415,8 +4409,8 @@ func TestDeadlockBreakpoint(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { deadlockBp = proc.UnrecoveredPanic } - withTestProcess("testdeadlock", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp { @@ -4437,12 +4431,12 @@ func findSource(source string, sources []string) bool { func TestListImages(t *testing.T) { pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/") - withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { if !findSource(fixture.Source, p.BinInfo().Sources) { t.Fatalf("could not find %s in sources: %q\n", fixture.Source, p.BinInfo().Sources) } - assertNoError(p.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "first continue") f, l := currentLineNumber(p, t) plugin1Found := false t.Logf("Libraries before %s:%d:", f, l) @@ -4459,7 +4453,7 @@ func TestListImages(t *testing.T) { // Source files for the base program must be available even after a plugin is loaded. Issue #2074. t.Fatalf("could not find %s in sources (after loading plugin): %q\n", fixture.Source, p.BinInfo().Sources) } - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "second continue") f, l = currentLineNumber(p, t) plugin1Found, plugin2Found := false, false t.Logf("Libraries after %s:%d:", f, l) @@ -4488,9 +4482,9 @@ func TestAncestors(t *testing.T) { savedGodebug := os.Getenv("GODEBUG") os.Setenv("GODEBUG", "tracebackancestors=100") defer os.Setenv("GODEBUG", savedGodebug) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.testgoroutine") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") as, err := proc.Ancestors(p, p.SelectedGoroutine(), 1000) assertNoError(err, t, "Ancestors") t.Logf("ancestors: %#v\n", as) @@ -4546,8 +4540,7 @@ func testCallConcurrentCheckReturns(p *proc.Target, t *testing.T, gid1, gid2 int func TestCallConcurrent(t *testing.T) { protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 24) assertNoError(grp.Continue(), t, "Continue()") //_, err := p.ClearBreakpoint(bp.Addr) @@ -4605,8 +4598,8 @@ func TestPluginStepping(t *testing.T) { func TestIssue1601(t *testing.T) { protest.MustHaveCgo(t) // Tests that recursive types involving C qualifiers and typedefs are parsed correctly - withTestProcess("issue1601", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue1601", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") evalVariable(p, t, "C.globalq") }) } @@ -4614,7 +4607,7 @@ func TestIssue1601(t *testing.T) { func TestIssue1615(t *testing.T) { // A breakpoint condition that tests for string equality with a constant string shouldn't fail with 'string too long for comparison' error - withTestProcess("issue1615", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1615", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 19) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -4622,7 +4615,7 @@ func TestIssue1615(t *testing.T) { Y: &ast.BasicLit{Kind: token.STRING, Value: `"projects/my-gcp-project-id-string/locations/us-central1/queues/my-task-queue-name"`}, } - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") assertLineNumber(p, t, 19, "") }) } @@ -4634,8 +4627,8 @@ func TestCgoStacktrace2(t *testing.T) { protest.MustHaveCgo(t) // If a panic happens during cgo execution the stacktrace should show the C // function that caused the problem. - withTestProcess("cgosigsegvstack", t, func(p *proc.Target, fixture protest.Fixture) { - p.Continue() + withTestProcess("cgosigsegvstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.Continue() frames, err := proc.ThreadStacktrace(p.CurrentThread(), 100) assertNoError(err, t, "Stacktrace()") logStacktrace(t, p, frames) @@ -4648,14 +4641,14 @@ func TestCgoStacktrace2(t *testing.T) { func TestIssue1656(t *testing.T) { skipUnlessOn(t, "amd64 only", "amd64") - withTestProcess("issue1656/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1656/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.s")), 5) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("step1\n") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 8, "wrong line number after first step") t.Logf("step2\n") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 9, "wrong line number after second step") }) } @@ -4666,15 +4659,15 @@ func TestBreakpointConfusionOnResume(t *testing.T) { // stopped at. // This test checks for a regression introduced when fixing Issue #1656 skipUnlessOn(t, "amd64 only", "amd64") - withTestProcess("nopbreakpoint/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nopbreakpoint/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { maindots := filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.s")) maindotgo := filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.go")) setFileBreakpoint(p, t, maindots, 5) // line immediately after the NOP - assertNoError(p.Continue(), t, "First Continue") + assertNoError(grp.Continue(), t, "First Continue") assertLineNumber(p, t, 5, "not on main.s:5") setFileBreakpoint(p, t, maindots, 4) // sets a breakpoint on the NOP line, which will be one byte before the breakpoint we currently are stopped at. setFileBreakpoint(p, t, maindotgo, 18) // set one extra breakpoint so that we can recover execution and check the global variable g - assertNoError(p.Continue(), t, "Second Continue") + assertNoError(grp.Continue(), t, "Second Continue") gvar := evalVariable(p, t, "g") if n, _ := constant.Int64Val(gvar.Value); n != 1 { t.Fatalf("wrong value of global variable 'g': %v (expected 1)", gvar.Value) @@ -4683,8 +4676,8 @@ func TestBreakpointConfusionOnResume(t *testing.T) { } func TestIssue1736(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") ch1BufVar := evalVariable(p, t, "*(ch1.buf)") q := fmt.Sprintf("*(*%q)(%d)", ch1BufVar.DwarfType.Common().Name, ch1BufVar.Addr) t.Logf("%s", q) @@ -4698,13 +4691,13 @@ func TestIssue1736(t *testing.T) { func TestIssue1817(t *testing.T) { // Setting a breakpoint on a line that doesn't have any PC addresses marked // is_stmt should work. - withTestProcess("issue1817", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1817", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 16) }) } func TestListPackagesBuildInfo(t *testing.T) { - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pkgs := p.BinInfo().ListPackagesBuildInfo(true) t.Logf("returned %d", len(pkgs)) if len(pkgs) < 10 { @@ -4736,17 +4729,17 @@ func TestIssue1795(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) { t.Skip("Test not relevant to Go < 1.13") } - withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 12, "wrong line number after Continue,") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 13, "wrong line number after Next,") }) - withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "regexp.(*Regexp).doExecute") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 12, "wrong line number after Continue (1),") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "ThreadStacktrace()") logStacktrace(t, p, frames) @@ -4767,14 +4760,14 @@ func TestIssue1795(t *testing.T) { func BenchmarkConditionalBreakpoints(b *testing.B) { b.N = 1 - withTestProcess("issue1549", b, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1549", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, b, fixture.Source, 12) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, X: &ast.Ident{Name: "value"}, Y: &ast.BasicLit{Kind: token.INT, Value: "-1"}, } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { b.Fatalf("Unexpected error on Continue(): %v", err) } @@ -4934,8 +4927,7 @@ func TestIssue1925(t *testing.T) { // 'call' procedure should clean the G cache like every other function // altering the state of the target process. protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { assertNoError(grp.Continue(), t, "Continue()") assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call") t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc) @@ -4998,10 +4990,10 @@ func TestRefreshCurThreadSelGAfterContinueOnceError(t *testing.T) { skipUnlessOn(t, "N/A", "darwin", "lldb") - withTestProcess("issue2078", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue2078", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 4) - assertNoError(p.Continue(), t, "Continue() (first)") - if p.Continue() == nil { + assertNoError(grp.Continue(), t, "Continue() (first)") + if grp.Continue() == nil { t.Fatalf("Second continue did not return an error") } g := p.SelectedGoroutine() @@ -5014,14 +5006,14 @@ func TestRefreshCurThreadSelGAfterContinueOnceError(t *testing.T) { func TestStepoutOneliner(t *testing.T) { // The heuristic detecting autogenerated wrappers when stepping out should // not skip oneliner functions. - withTestProcess("issue2086", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue2086", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 15, "after first continue") - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.T.m" { t.Fatalf("wrong function after stepout %#v", fn) } - assertNoError(p.StepOut(), t, "second StepOut()") + assertNoError(grp.StepOut(), t, "second StepOut()") if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.main" { t.Fatalf("wrong fnuction after second stepout %#v", fn) } @@ -5030,8 +5022,7 @@ func TestStepoutOneliner(t *testing.T) { func TestRequestManualStopWhileStopped(t *testing.T) { // Requesting a manual stop while stopped shouldn't cause problems (issue #2138). - withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue2138", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { resumed := make(chan struct{}) setFileBreakpoint(p, t, fixture.Source, 8) assertNoError(grp.Continue(), t, "Continue() 1") @@ -5064,8 +5055,8 @@ func TestRequestManualStopWhileStopped(t *testing.T) { func TestStepOutPreservesGoroutine(t *testing.T) { // Checks that StepOut preserves the currently selected goroutine. rand.Seed(time.Now().Unix()) - withTestProcess("issue2113", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue2113", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") logState := func() { g := p.SelectedGoroutine() @@ -5115,7 +5106,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) { logState() - err = p.StepOut() + err = grp.StepOut() if err != nil { _, isexited := err.(proc.ErrProcessExited) if !isexited { @@ -5223,7 +5214,7 @@ func TestDump(t *testing.T) { } c, err := core.OpenCore(corePath, exePath, nil) assertNoError(err, t, "OpenCore()") - return c + return c.Selected } testDump := func(p, c *proc.Target) { @@ -5302,8 +5293,8 @@ func TestDump(t *testing.T) { } } - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") corePath := filepath.Join(fixture.BuildDir, "coredump") corePathPlatIndep := filepath.Join(fixture.BuildDir, "coredump-indep") @@ -5329,7 +5320,7 @@ func TestCompositeMemoryWrite(t *testing.T) { t.Skip("only valid on amd64") } skipOn(t, "not implemented", "freebsd") - withTestProcess("fputest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("fputest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { getregs := func() (pc, rax, xmm1 uint64) { regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -5361,7 +5352,7 @@ func TestCompositeMemoryWrite(t *testing.T) { return binary.LittleEndian.Uint64(buf) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") oldPc, oldRax, oldXmm1 := getregs() t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1) @@ -5401,8 +5392,8 @@ func TestVariablesWithExternalLinking(t *testing.T) { // See: // https://github.com/golang/go/issues/25841 // https://github.com/go-delve/delve/issues/2346 - withTestProcessArgs("testvariables2", t, ".", []string{}, protest.BuildModeExternalLinker, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcessArgs("testvariables2", t, ".", []string{}, protest.BuildModeExternalLinker, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") str1Var := evalVariable(p, t, "str1") if str1Var.Unreadable != nil { t.Fatalf("variable str1 is unreadable: %v", str1Var.Unreadable) @@ -5428,11 +5419,11 @@ func TestWatchpointsBasic(t *testing.T) { position5 = 40 } - withTestProcess("databpeasy", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpeasy", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") setFileBreakpoint(p, t, fixture.Source, 21) // Position 2 breakpoint setFileBreakpoint(p, t, fixture.Source, 27) // Position 4 breakpoint - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 13, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5441,7 +5432,7 @@ func TestWatchpointsBasic(t *testing.T) { bp, err := p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only)") - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, position1, "Continue 1") // Position 1 if curbp := p.CurrentThread().Breakpoint().Breakpoint; curbp == nil || (curbp.LogicalID() != bp.LogicalID()) { @@ -5450,25 +5441,25 @@ func TestWatchpointsBasic(t *testing.T) { assertNoError(p.ClearBreakpoint(bp.Addr), t, "ClearBreakpoint") - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") assertLineNumber(p, t, 21, "Continue 2") // Position 2 _, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite|proc.WatchRead, nil) assertNoError(err, t, "SetDataBreakpoint(read-write)") - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") assertLineNumber(p, t, 22, "Continue 3") // Position 3 p.ClearBreakpoint(bp.Addr) - assertNoError(p.Continue(), t, "Continue 4") + assertNoError(grp.Continue(), t, "Continue 4") assertLineNumber(p, t, 27, "Continue 4") // Position 4 t.Logf("setting final breakpoint") _, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only, again)") - assertNoError(p.Continue(), t, "Continue 5") + assertNoError(grp.Continue(), t, "Continue 5") assertLineNumber(p, t, position5, "Continue 5") // Position 5 }) } @@ -5479,9 +5470,9 @@ func TestWatchpointCounts(t *testing.T) { skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows") protest.AllowRecording(t) - withTestProcess("databpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") @@ -5490,7 +5481,7 @@ func TestWatchpointCounts(t *testing.T) { assertNoError(err, t, "SetWatchpoint(write-only)") for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -5517,8 +5508,7 @@ func TestWatchpointCounts(t *testing.T) { func TestManualStopWhileStopped(t *testing.T) { // Checks that RequestManualStop sent to a stopped thread does not cause the target process to die. - withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { asyncCont := func(done chan struct{}) { defer close(done) err := grp.Continue() @@ -5566,9 +5556,9 @@ func TestManualStopWhileStopped(t *testing.T) { func TestDwrapStartLocation(t *testing.T) { // Tests that the start location of a goroutine is unwrapped in Go 1.17 and later. - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") found := false @@ -5601,11 +5591,11 @@ func TestWatchpointStack(t *testing.T) { position1 = 16 } - withTestProcess("databpstack", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint clearlen := len(p.Breakpoints().M) - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 11, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5615,7 +5605,7 @@ func TestWatchpointStack(t *testing.T) { assertNoError(err, t, "SetDataBreakpoint(write-only)") watchbpnum := 3 - if recorded, _ := p.Recorded(); recorded { + if recorded, _ := grp.Recorded(); recorded { watchbpnum = 4 } @@ -5647,10 +5637,10 @@ func TestWatchpointStack(t *testing.T) { t.Errorf("wrong number of breakpoints after setting watchpoint: %d", len(p.Breakpoints().M)-clearlen) } - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, position1, "Continue 1") // Position 1 - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 24, "Continue 2") // Position 2 (watchpoint gone out of scope) @@ -5677,11 +5667,11 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { skipUnlessOn(t, "only for recorded targets", "rr") protest.AllowRecording(t) - withTestProcess("databpstack", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint clearlen := len(p.Breakpoints().M) - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 11, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5690,20 +5680,20 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { _, err = p.SetWatchpoint(0, scope, "w", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only)") - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, 17, "Continue 1") // Position 1 - p.ChangeDirection(proc.Backward) + grp.ChangeDirection(proc.Backward) - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 16, "Continue 2") // Position 1 again (because of inverted movement) - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 11, "Continue 3") // Position 0 (breakpoint 1 hit) - assertNoError(p.Continue(), t, "Continue 4") + assertNoError(grp.Continue(), t, "Continue 4") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 23, "Continue 4") // Position 2 (watchpoint gone out of scope) @@ -5725,9 +5715,9 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { func TestSetOnFunctions(t *testing.T) { // The set command between function variables should fail with an error // Issue #2691 - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") err = scope.SetVariable("main.func1", "main.func2") @@ -5742,9 +5732,9 @@ func TestSetYMMRegister(t *testing.T) { // Checks that setting a XMM register works. This checks that the // workaround for a bug in debugserver works. // See issue #2767. - withTestProcess("setymmreg/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("setymmreg/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.asmFunc") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") getReg := func(pos string) *op.DwarfRegister { regs := getRegisters(p, t) @@ -5792,14 +5782,14 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { t.Fatalf("assembly file for %s not provided", runtime.GOARCH) } - withTestProcess("asmnilptr/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("asmnilptr/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { f := filepath.Join(fixture.BuildDir, asmfile) f = strings.ReplaceAll(f, "\\", "/") setFileBreakpoint(p, t, f, 5) t.Logf("first continue") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("second continue") - err := p.Continue() + err := grp.Continue() if runtime.GOOS == "darwin" && err != nil && err.Error() == "bad access" { // this is also ok return @@ -5819,10 +5809,10 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { func TestStepIntoAutogeneratedSkip(t *testing.T) { // Tests that autogenerated functions are skipped with the new naming // scheme for autogenerated functions (issue #2948). - withTestProcess("stepintobug", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stepintobug", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step") assertLineNumber(p, t, 12, "After step") }) } @@ -5836,11 +5826,9 @@ func TestCallInjectionFlagCorruption(t *testing.T) { skipUnlessOn(t, "not relevant", "amd64") protest.MustSupportFunctionCalls(t, testBackend) - withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { mainfn := p.BinInfo().LookupFunc["main.main"] - grp := proc.NewGroup(p) - // Find JNZ instruction on line :14 var addr uint64 text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) @@ -5935,9 +5923,9 @@ func TestStacktraceExtlinkMac(t *testing.T) { // Tests stacktrace for programs built using external linker. // See issue #3194 skipUnlessOn(t, "darwin only", "darwin") - withTestProcess("issue3194", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue3194", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10) assertNoError(err, t, "ThreadStacktrace") logStacktrace(t, p, frames) @@ -5946,3 +5934,109 @@ func TestStacktraceExtlinkMac(t *testing.T) { } }) } + +func TestFollowExec(t *testing.T) { + skipUnlessOn(t, "follow exec only supported on linux", "linux") + withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int64]uint64)} + grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)} + grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int64]uint64)} + + assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[1]), t, "EnableBreakpoint(main.traceme1)") + assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[3]), t, "EnableBreakpoint(main.traceme3)") + + assertNoError(grp.FollowExec(true, ""), t, "FollowExec") + + first := true + finished := false + pids := map[int]int{} + ns := map[string]int{} + + for { + t.Log("Continuing") + err := grp.Continue() + if err != nil { + _, isexited := err.(proc.ErrProcessExited) + if isexited { + break + } + assertNoError(err, t, "Continue") + } + + if first { + first = false + if grp.Selected != p { + t.Fatalf("first breakpoint hit was not on the parent process") + } + if grp.Selected.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 1 { + t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint) + } + loc, err := grp.Selected.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme1" { + t.Fatalf("wrong stop location %#v", loc) + } + } else if grp.Selected == p { + if finished { + t.Fatalf("breakpoint hit after the last one in the parent process") + } + if p.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 3 { + t.Fatalf("wrong breakpoint %#v", p.CurrentThread().Breakpoint().Breakpoint) + } + loc, err := p.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme3" { + t.Fatalf("wrong stop location %#v", loc) + } + finished = true + } else { + if finished { + t.Fatalf("breakpoint hit after the last one in a child process") + } + it := proc.ValidTargets{Group: grp} + for it.Next() { + tgt := it.Target + if !tgt.CurrentThread().Breakpoint().Active { + continue + } + if tgt.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 2 { + t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint) + } + pids[tgt.Pid()]++ + loc, err := tgt.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme2" { + t.Fatalf("wrong stop location %#v", loc) + } + nvar := evalVariable(tgt, t, "n") + if nvar.Unreadable != nil { + t.Fatalf("unreadable variable 'n' on target %d: %v", tgt.Pid(), nvar.Unreadable) + } + t.Logf("variable 'n' on target %d: %#v (%v)", tgt.Pid(), nvar, nvar.Value) + ns[constant.StringVal(nvar.Value)]++ + } + } + } + + if len(ns) != 3 { + t.Errorf("bad contents of ns: %#v", ns) + } + for _, v := range ns { + if v != 1 { + t.Errorf("bad contents of ns: %#v", ns) + } + } + if ns["C0"] != 1 || ns["C1"] != 1 || ns["C2"] != 1 { + t.Errorf("bad contents of ns: %#v", ns) + } + + if len(pids) != 3 { + t.Errorf("bad contents of pids: %#v", pids) + } + for _, v := range pids { + if v != 1 { + t.Errorf("bad contents of pids: %#v", pids) + } + } + }) +} diff --git a/pkg/proc/proc_unix_test.go b/pkg/proc/proc_unix_test.go index fa9ac0ae..0404995b 100644 --- a/pkg/proc/proc_unix_test.go +++ b/pkg/proc/proc_unix_test.go @@ -36,8 +36,7 @@ func TestIssue419(t *testing.T) { errChan := make(chan error, 2) // SIGINT directed at the inferior should be passed along not swallowed by delve - withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue419", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") assertNoError(grp.Continue(), t, "Continue()") resumeChan := make(chan struct{}, 1) diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go index 80031690..8ca4665f 100644 --- a/pkg/proc/scope_test.go +++ b/pkg/proc/scope_test.go @@ -23,8 +23,8 @@ func TestScopeWithEscapedVariable(t *testing.T) { return } - withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("scopeescapevareval", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") // On the breakpoint there are two 'a' variables in scope, the one that // isn't shadowed is a variable that escapes to the heap and figures in @@ -72,7 +72,7 @@ func TestScope(t *testing.T) { scopeChecks := getScopeChecks(scopetestPath, t) - withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("scopetest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for i := range scopeChecks { setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line) } @@ -80,7 +80,7 @@ func TestScope(t *testing.T) { t.Logf("%d breakpoints set", len(scopeChecks)) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 5646f013..90403449 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -38,9 +38,8 @@ const ( type Target struct { Process - proc ProcessInternal - recman RecordingManipulationInternal - continueOnce ContinueOnceFunc + proc ProcessInternal + recman RecordingManipulationInternal pid int @@ -49,9 +48,6 @@ type Target struct { // case only one will be reported. StopReason StopReason - // CanDump is true if core dumping is supported. - CanDump bool - // currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected. currentThread Thread @@ -148,18 +144,6 @@ const ( StopWatchpoint // The target process hit one or more watchpoints ) -type ContinueOnceFunc func([]ProcessInternal, *ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error) - -// NewTargetConfig contains the configuration for a new Target object, -type NewTargetConfig struct { - Path string // path of the main executable - DebugInfoDirs []string // Directories to search for split debug info - DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled - StopReason StopReason // Initial stop reason - CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap) - ContinueOnce ContinueOnceFunc -} - // DisableAsyncPreemptEnv returns a process environment (like os.Environ) // where asyncpreemptoff is set to 1. func DisableAsyncPreemptEnv() []string { @@ -174,15 +158,15 @@ func DisableAsyncPreemptEnv() []string { return env } -// NewTarget returns an initialized Target object. +// newTarget returns an initialized Target object. // The p argument can optionally implement the RecordingManipulation interface. -func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetConfig) (*Target, error) { +func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path string) (*Target, error) { entryPoint, err := p.EntryPoint() if err != nil { return nil, err } - err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs) + err = p.BinInfo().LoadBinaryInfo(path, entryPoint, grp.cfg.DebugInfoDirs) if err != nil { return nil, err } @@ -196,11 +180,8 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo Process: p, proc: p, fncallForG: make(map[int64]*callInjection), - StopReason: cfg.StopReason, currentThread: currentThread, - CanDump: cfg.CanDump, pid: pid, - continueOnce: cfg.ContinueOnce, } if recman, ok := p.(RecordingManipulationInternal); ok { @@ -219,7 +200,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo t.gcache.init(p.BinInfo()) t.fakeMemoryRegistryMap = make(map[string]*compositeMemory) - if cfg.DisableAsyncPreempt { + if grp.cfg.DisableAsyncPreempt { setAsyncPreemptOff(t, 1) } diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 0e5b68fb..6f45d588 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -48,48 +48,55 @@ func (grp *TargetGroup) Next() (err error) { // processes. It will continue until it hits a breakpoint // or is otherwise stopped. 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 grp.numValid() == 0 { + _, err := grp.targets[0].Valid() return err } - for _, thread := range dbp.ThreadList() { - thread.Common().CallReturn = false - thread.Common().returnValues = nil + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + for _, thread := range dbp.ThreadList() { + thread.Common().CallReturn = false + thread.Common().returnValues = nil + } + dbp.Breakpoints().WatchOutOfScope = nil + dbp.clearHardcodedBreakpoints() } - dbp.Breakpoints().WatchOutOfScope = nil - dbp.clearHardcodedBreakpoints() grp.cctx.CheckAndClearManualStopRequest() defer func() { // Make sure we clear internal breakpoints if we simultaneously receive a // manual stop request and hit a breakpoint. if grp.cctx.CheckAndClearManualStopRequest() { - dbp.StopReason = StopManual - dbp.clearHardcodedBreakpoints() - if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { - dbp.ClearSteppingBreakpoints() - } + grp.finishManualStop() } }() for { if grp.cctx.CheckAndClearManualStopRequest() { - dbp.StopReason = StopManual - dbp.clearHardcodedBreakpoints() - if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { - dbp.ClearSteppingBreakpoints() - } + grp.finishManualStop() return nil } - dbp.ClearCaches() - trapthread, stopReason, contOnceErr := grp.continueOnce([]ProcessInternal{grp.targets[0].proc}, grp.cctx) - dbp.StopReason = stopReason + for _, dbp := range grp.targets { + dbp.ClearCaches() + } + trapthread, stopReason, contOnceErr := grp.procgrp.ContinueOnce(grp.cctx) + var traptgt *Target + if trapthread != nil { + traptgt = grp.TargetForThread(trapthread.ThreadID()) + if traptgt == nil { + return fmt.Errorf("could not find target for thread %d", trapthread.ThreadID()) + } + } else { + traptgt = grp.targets[0] + } + traptgt.StopReason = stopReason - threads := dbp.ThreadList() - for _, thread := range threads { - if thread.Breakpoint().Breakpoint != nil { - thread.Breakpoint().Breakpoint.checkCondition(dbp, thread, thread.Breakpoint()) + it := ValidTargets{Group: grp} + for it.Next() { + for _, thread := range it.ThreadList() { + if thread.Breakpoint().Breakpoint != nil { + thread.Breakpoint().Breakpoint.checkCondition(it.Target, thread, thread.Breakpoint()) + } } } @@ -98,31 +105,56 @@ func (grp *TargetGroup) Continue() error { // Issue #2078. // Errors are ignored because depending on why ContinueOnce failed this // might very well not work. - if valid, _ := dbp.Valid(); valid { - if trapthread != nil { - _ = dbp.SwitchThread(trapthread.ThreadID()) - } else if curth := dbp.CurrentThread(); curth != nil { - dbp.selectedGoroutine, _ = GetG(curth) - } - } + _ = grp.setCurrentThreads(traptgt, trapthread) if pe, ok := contOnceErr.(ErrProcessExited); ok { - dbp.exitStatus = pe.Status + traptgt.exitStatus = pe.Status } return contOnceErr } - if dbp.StopReason == StopLaunched { - dbp.ClearSteppingBreakpoints() + if stopReason == StopLaunched { + it.Reset() + for it.Next() { + it.Target.ClearSteppingBreakpoints() + } } - callInjectionDone, callErr := callInjectionProtocol(dbp, threads) - hcbpErr := dbp.handleHardcodedBreakpoints(trapthread, threads) + var callInjectionDone bool + var callErr error + var hcbpErr error + it.Reset() + for it.Next() { + dbp := it.Target + threads := dbp.ThreadList() + callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads) + callInjectionDone = callInjectionDone || callInjectionDoneThis + if callInjectionDoneThis { + dbp.StopReason = StopCallReturned + } + if callErrThis != nil && callErr == nil { + callErr = callErrThis + } + hcbpErrThis := dbp.handleHardcodedBreakpoints(trapthread, threads) + if hcbpErrThis != nil && hcbpErr == nil { + hcbpErr = hcbpErrThis + } + } // callErr and hcbpErr check delayed until after pickCurrentThread, which // must always happen, otherwise the debugger could be left in an // inconsistent state. - if err := pickCurrentThread(dbp, trapthread, threads); err != nil { - return err + it = ValidTargets{Group: grp} + for it.Next() { + var th Thread = nil + if it.Target == traptgt { + th = trapthread + } + err := pickCurrentThread(it.Target, th) + if err != nil { + return err + } } + grp.pickCurrentTarget(traptgt) + dbp := grp.Selected if callErr != nil { return callErr @@ -138,7 +170,7 @@ func (grp *TargetGroup) Continue() error { case curbp.Active && curbp.Stepping: if curbp.SteppingInto { // See description of proc.(*Process).next for the meaning of StepBreakpoints - if err := conditionErrors(threads); err != nil { + if err := conditionErrors(grp); err != nil { return err } if grp.GetDirection() == Forward { @@ -168,7 +200,7 @@ func (grp *TargetGroup) Continue() error { return err } dbp.StopReason = StopNextFinished - return conditionErrors(threads) + return conditionErrors(grp) } case curbp.Active: onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints()) @@ -191,19 +223,58 @@ func (grp *TargetGroup) Continue() error { if curbp.Breakpoint.WatchType != 0 { dbp.StopReason = StopWatchpoint } - return conditionErrors(threads) + return conditionErrors(grp) default: // not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat } if callInjectionDone { // a call injection was finished, don't let a breakpoint with a failed // condition or a step breakpoint shadow this. - dbp.StopReason = StopCallReturned - return conditionErrors(threads) + return conditionErrors(grp) } } } +func (grp *TargetGroup) finishManualStop() { + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + dbp.StopReason = StopManual + dbp.clearHardcodedBreakpoints() + if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { + dbp.ClearSteppingBreakpoints() + } + } +} + +// setCurrentThreads switches traptgt to trapthread, then for each target in +// the group if its current thread exists it refreshes the current +// goroutine, otherwise it switches it to a randomly selected thread. +func (grp *TargetGroup) setCurrentThreads(traptgt *Target, trapthread Thread) error { + var err error + if traptgt != nil && trapthread != nil { + err = traptgt.SwitchThread(trapthread.ThreadID()) + } + for _, tgt := range grp.targets { + if isvalid, _ := tgt.Valid(); !isvalid { + continue + } + if _, ok := tgt.FindThread(tgt.currentThread.ThreadID()); ok { + tgt.selectedGoroutine, _ = GetG(tgt.currentThread) + } else { + threads := tgt.ThreadList() + if len(threads) > 0 { + err1 := tgt.SwitchThread(threads[0].ThreadID()) + if err1 != nil && err == nil { + err = err1 + } + } + } + } + return err +} + func isTraceOrTraceReturn(bp *Breakpoint) bool { if bp.Logical == nil { return false @@ -211,14 +282,19 @@ func isTraceOrTraceReturn(bp *Breakpoint) bool { return bp.Logical.Tracepoint || bp.Logical.TraceReturn } -func conditionErrors(threads []Thread) error { +func conditionErrors(grp *TargetGroup) error { var condErr error - for _, th := range threads { - if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil { - if condErr == nil { - condErr = bp.CondError - } else { - return fmt.Errorf("multiple errors evaluating conditions") + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + for _, th := range dbp.ThreadList() { + if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil { + if condErr == nil { + condErr = bp.CondError + } else { + return fmt.Errorf("multiple errors evaluating conditions") + } } } } @@ -226,24 +302,88 @@ func conditionErrors(threads []Thread) error { } // pick a new dbp.currentThread, with the following priority: +// // - a thread with an active stepping breakpoint // - a thread with an active breakpoint, prioritizing trapthread -// - trapthread -func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error { +// - trapthread if it is not nil +// - the previous current thread if it still exists +// - a randomly selected thread +func pickCurrentThread(dbp *Target, trapthread Thread) error { + threads := dbp.ThreadList() for _, th := range threads { if bp := th.Breakpoint(); bp.Active && bp.Stepping { return dbp.SwitchThread(th.ThreadID()) } } - if bp := trapthread.Breakpoint(); bp.Active { - return dbp.SwitchThread(trapthread.ThreadID()) + if trapthread != nil { + if bp := trapthread.Breakpoint(); bp.Active { + return dbp.SwitchThread(trapthread.ThreadID()) + } } for _, th := range threads { if bp := th.Breakpoint(); bp.Active { return dbp.SwitchThread(th.ThreadID()) } } - return dbp.SwitchThread(trapthread.ThreadID()) + if trapthread != nil { + return dbp.SwitchThread(trapthread.ThreadID()) + } + if _, ok := dbp.FindThread(dbp.currentThread.ThreadID()); ok { + dbp.selectedGoroutine, _ = GetG(dbp.currentThread) + return nil + } + if len(threads) > 0 { + return dbp.SwitchThread(threads[0].ThreadID()) + } + return nil +} + +// pickCurrentTarget picks a new current target, with the following property: +// +// - a target with an active stepping breakpoint +// - a target with StopReason == StopCallReturned +// - a target with an active breakpoint, prioritizing traptgt +// - traptgt +func (grp *TargetGroup) pickCurrentTarget(traptgt *Target) { + if len(grp.targets) == 1 { + grp.Selected = grp.targets[0] + return + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + bp := dbp.currentThread.Breakpoint() + if bp.Active && bp.Stepping { + grp.Selected = dbp + return + } + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + if dbp.StopReason == StopCallReturned { + grp.Selected = dbp + return + } + } + + if traptgt.currentThread.Breakpoint().Active { + grp.Selected = traptgt + return + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + bp := dbp.currentThread.Breakpoint() + if bp.Active { + grp.Selected = dbp + return + } + } + grp.Selected = traptgt } func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) { diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go index a1f6acb2..5385b93d 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -2,8 +2,11 @@ package proc import ( "bytes" + "errors" "fmt" "strings" + + "github.com/go-delve/delve/pkg/logflags" ) // TargetGroup represents a group of target processes being debugged that @@ -12,66 +15,114 @@ import ( // enabled and the backend supports it, otherwise the group will always // contain a single target process. type TargetGroup struct { - targets []*Target - Selected *Target + procgrp ProcessGroup + + targets []*Target + Selected *Target + followExecEnabled bool RecordingManipulation recman RecordingManipulationInternal + // StopReason describes the reason why the selected target process is stopped. + // A process could be stopped for multiple simultaneous reasons, in which + // case only one will be reported. + StopReason StopReason + // 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 + cctx *ContinueOnceContext + cfg NewTargetGroupConfig + CanDump bool } +// NewTargetGroupConfig contains the configuration for a new TargetGroup object, +type NewTargetGroupConfig struct { + DebugInfoDirs []string // Directories to search for split debug info + DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled + StopReason StopReason // Initial stop reason + CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap) +} + +type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason) (*Target, error) + // NewGroup creates a TargetGroup containing the specified Target. -func NewGroup(t *Target) *TargetGroup { - if t.partOfGroup { - panic("internal error: target is already part of a group") - } - t.partOfGroup = true - if t.Breakpoints().Logical == nil { - t.Breakpoints().Logical = make(map[int]*LogicalBreakpoint) - } - return &TargetGroup{ - RecordingManipulation: t.recman, - targets: []*Target{t}, - Selected: t, - cctx: &ContinueOnceContext{}, - recman: t.recman, - LogicalBreakpoints: t.Breakpoints().Logical, - continueOnce: t.continueOnce, +func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) { + grp := &TargetGroup{ + procgrp: procgrp, + cctx: &ContinueOnceContext{}, + LogicalBreakpoints: make(map[int]*LogicalBreakpoint), + StopReason: cfg.StopReason, + cfg: cfg, + CanDump: cfg.CanDump, } + return grp, grp.addTarget } -// NewGroupRestart creates a new group of targets containing t and -// sets breakpoints and other attributes from oldgrp. +// Restart copies breakpoints and follow exec status from oldgrp into grp. // Breakpoints that can not be set will be discarded, if discard is not nil // it will be called for each discarded breakpoint. -func NewGroupRestart(t *Target, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) *TargetGroup { - grp := NewGroup(t) - grp.LogicalBreakpoints = oldgrp.LogicalBreakpoints - t.Breakpoints().Logical = grp.LogicalBreakpoints - for _, bp := range grp.LogicalBreakpoints { - if bp.LogicalID < 0 || !bp.Enabled { +func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) { + for _, bp := range oldgrp.LogicalBreakpoints { + if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok { continue } + grp.LogicalBreakpoints[bp.LogicalID] = bp bp.TotalHitCount = 0 bp.HitCount = make(map[int64]uint64) bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart - err := grp.EnableBreakpoint(bp) - if err != nil { - if discard != nil { - discard(bp, err) + if bp.Enabled { + err := grp.EnableBreakpoint(bp) + if err != nil { + if discard != nil { + discard(bp, err) + } + delete(grp.LogicalBreakpoints, bp.LogicalID) } - delete(grp.LogicalBreakpoints, bp.LogicalID) } } - return grp + if oldgrp.followExecEnabled { + grp.FollowExec(true, "") + } +} + +func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason) (*Target, error) { + t, err := grp.newTarget(p, pid, currentThread, path) + if err != nil { + return nil, err + } + t.StopReason = stopReason + //TODO(aarzilli): check if the target's command line matches the regex + if t.partOfGroup { + panic("internal error: target is already part of group") + } + t.partOfGroup = true + if grp.RecordingManipulation == nil { + grp.RecordingManipulation = t.recman + grp.recman = t.recman + } + if grp.Selected == nil { + grp.Selected = t + } + t.Breakpoints().Logical = grp.LogicalBreakpoints + logger := logflags.DebuggerLogger() + for _, lbp := range grp.LogicalBreakpoints { + if lbp.LogicalID < 0 { + continue + } + err := enableBreakpointOnTarget(t, lbp) + if err != nil { + logger.Debugf("could not enable breakpoint %d on new target %d: %v", lbp.LogicalID, t.Pid(), err) + } else { + logger.Debugf("breakpoint %d enabled on new target %d: %v", lbp.LogicalID, t.Pid(), err) + } + } + grp.targets = append(grp.targets, t) + return t, nil } // Targets returns a slice of all targets in the group, including the @@ -95,10 +146,22 @@ func (grp *TargetGroup) Valid() (bool, error) { return false, err0 } +func (grp *TargetGroup) numValid() int { + r := 0 + for _, t := range grp.targets { + ok, _ := t.Valid() + if ok { + r++ + } + } + return r +} + // Detach detaches all targets in the group. func (grp *TargetGroup) Detach(kill bool) error { var errs []string - for _, t := range grp.targets { + for i := len(grp.targets) - 1; i >= 0; i-- { + t := grp.targets[i] isvalid, _ := t.Valid() if !isvalid { continue @@ -144,12 +207,10 @@ func (grp *TargetGroup) ThreadList() []Thread { } // TargetForThread returns the target containing the given thread. -func (grp *TargetGroup) TargetForThread(thread Thread) *Target { +func (grp *TargetGroup) TargetForThread(tid int) *Target { for _, t := range grp.targets { - for _, th := range t.ThreadList() { - if th == thread { - return t - } + if _, ok := t.FindThread(tid); ok { + return t } } return nil @@ -274,6 +335,26 @@ func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error { return nil } +// FollowExec enables or disables follow exec mode. When follow exec mode is +// enabled new processes spawned by the target process are automatically +// added to the target group. +// If regex is not the empty string only processes whose command line +// matches regex will be added to the target group. +func (grp *TargetGroup) FollowExec(v bool, regex string) error { + if regex != "" { + return errors.New("regex not implemented") + } + it := ValidTargets{Group: grp} + for it.Next() { + err := it.proc.FollowExec(v) + if err != nil { + return err + } + } + grp.followExecEnabled = v + return nil +} + // ValidTargets iterates through all valid targets in Group. type ValidTargets struct { *Target @@ -295,3 +376,9 @@ func (it *ValidTargets) Next() bool { it.Target = nil return false } + +// Reset returns the iterator to the start of the group. +func (it *ValidTargets) Reset() { + it.Target = nil + it.start = 0 +} diff --git a/pkg/proc/variable_test.go b/pkg/proc/variable_test.go index 848cb51a..747616c3 100644 --- a/pkg/proc/variable_test.go +++ b/pkg/proc/variable_test.go @@ -10,9 +10,9 @@ import ( func TestGoroutineCreationLocation(t *testing.T) { protest.AllowRecording(t) - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.agoroutine") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -39,6 +39,6 @@ func TestGoroutineCreationLocation(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 53d3fdb2..9cd993a1 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -140,8 +140,8 @@ func TestVariableEvaluation2(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -198,8 +198,8 @@ func TestSetVariable(t *testing.T) { {"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"}, } - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { if tc.name == "iface1" && tc.expr == "parr" { @@ -267,8 +267,8 @@ func TestVariableEvaluationShort(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -323,8 +323,8 @@ func TestMultilineVariableEvaluation(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -399,8 +399,8 @@ func TestLocalVariables(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -436,7 +436,7 @@ func TestLocalVariables(t *testing.T) { func TestEmbeddedStruct(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { testcases := []varTest{ {"b.val", true, "-314", "-314", "int", nil}, {"b.A.val", true, "-314", "-314", "int", nil}, @@ -460,7 +460,7 @@ func TestEmbeddedStruct(t *testing.T) { {"w4.F", false, ``, ``, "", errors.New("w4 has no member F")}, {"w5.F", false, ``, ``, "", errors.New("w5 has no member F")}, } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ver, _ := goversion.Parse(runtime.Version()) if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { @@ -491,8 +491,8 @@ func TestEmbeddedStruct(t *testing.T) { } func TestComplexSetting(t *testing.T) { - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") h := func(setExpr, value string) { @@ -842,8 +842,8 @@ func TestEvalExpression(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for i, tc := range testcases { t.Run(strconv.Itoa(i), func(t *testing.T) { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) @@ -872,8 +872,8 @@ func TestEvalExpression(t *testing.T) { func TestEvalAddrAndCast(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") c1addr, err := evalVariableWithCfg(p, "&c1", pnormalLoadConfig) assertNoError(err, t, "EvalExpression(&c1)") c1addrstr := api.ConvertVar(c1addr).SinglelineString() @@ -899,8 +899,8 @@ func TestEvalAddrAndCast(t *testing.T) { func TestMapEvaluation(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") m1v, err := evalVariableWithCfg(p, "m1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") m1 := api.ConvertVar(m1v) @@ -941,8 +941,8 @@ func TestMapEvaluation(t *testing.T) { func TestUnsafePointer(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") up1v, err := evalVariableWithCfg(p, "up1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable(up1)") up1 := api.ConvertVar(up1v) @@ -979,8 +979,8 @@ func TestIssue426(t *testing.T) { // Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces // differs from the serialization used by the linker to produce DWARF type information protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, testcase := range testcases { v, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) @@ -1065,8 +1065,8 @@ func TestPackageRenames(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") testPackageRenamesHelper(t, p, testcases) testPackageRenamesHelper(t, p, testcases1_9) @@ -1101,8 +1101,8 @@ func TestConstants(t *testing.T) { // Not supported on 1.9 or earlier t.Skip("constants added in go 1.10") } - withTestProcess("consts", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("consts", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") for _, testcase := range testcases { variable, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) @@ -1112,9 +1112,9 @@ func TestConstants(t *testing.T) { } func TestIssue1075(t *testing.T) { - withTestProcess("clientdo", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("clientdo", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "net/http.(*Client).Do") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") for i := 0; i < 10; i++ { scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, fmt.Sprintf("GoroutineScope (%d)", i)) @@ -1260,9 +1260,8 @@ func TestCallFunction(t *testing.T) { {`floatsum(1, 2)`, []string{":float64:3"}, nil}, } - withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) - testCallFunctionSetBreakpoint(t, p, fixture) + withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testCallFunctionSetBreakpoint(t, p, grp, fixture) assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { @@ -1303,7 +1302,7 @@ func TestCallFunction(t *testing.T) { }) } -func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest.Fixture) { +func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { buf, err := ioutil.ReadFile(fixture.Source) assertNoError(err, t, "ReadFile") for i, line := range strings.Split(string(buf), "\n") { @@ -1390,8 +1389,8 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te func TestIssue1531(t *testing.T) { // Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0. - withTestProcess("issue1531", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue1531", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") hasKeys := func(mv *proc.Variable, keys ...string) { n := 0 @@ -1453,9 +1452,9 @@ func assertCurrentLocationFunction(p *proc.Target, t *testing.T, fnname string) func TestPluginVariables(t *testing.T) { pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/") - withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 41) - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") bp := setFunctionBreakpoint(p, t, "github.com/go-delve/delve/_fixtures/plugin2.TypesTest") t.Logf("bp.Addr = %#x", bp.Addr) @@ -1465,7 +1464,7 @@ func TestPluginVariables(t *testing.T) { t.Logf("%#x %s\n", image.StaticBase, image.Path) } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") // test that PackageVariables returns variables from the executable and plugins scope, err := evalScope(p) @@ -1495,15 +1494,15 @@ func TestPluginVariables(t *testing.T) { // test that the concrete type -> interface{} conversion works across plugins (mostly tests proc.dwarfToRuntimeType) assertNoError(setVariable(p, "plugin2.A", "main.ExeGlobal"), t, "setVariable(plugin2.A = main.ExeGlobal)") - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") assertCurrentLocationFunction(p, t, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil") vstr, err := evalVariableWithCfg(p, "str", pnormalLoadConfig) assertNoError(err, t, "Eval(str)") assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil}) - assertNoError(p.StepOut(), t, "StepOut") - assertNoError(p.StepOut(), t, "StepOut") - assertNoError(p.Next(), t, "Next") + assertNoError(grp.StepOut(), t, "StepOut") + assertNoError(grp.StepOut(), t, "StepOut") + assertNoError(grp.Next(), t, "Next") // read interface variable, inside executable code, with a concrete type defined in a plugin vb, err := evalVariableWithCfg(p, "b", pnormalLoadConfig) @@ -1534,8 +1533,8 @@ func TestCgoEval(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariablescgo/", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariablescgo/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, tc := range testcases { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) if err != nil && err.Error() == "evaluating methods not supported on this version of Go" { @@ -1581,9 +1580,9 @@ func TestEvalExpressionGenerics(t *testing.T) { }, } - withTestProcess("testvariables_generic", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables_generic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for i, tcs := range testcases { - assertNoError(p.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i)) + assertNoError(grp.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i)) for _, tc := range tcs { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) if tc.err == nil { @@ -1604,8 +1603,8 @@ func TestEvalExpressionGenerics(t *testing.T) { // Test the behavior when reading dangling pointers produced by unsafe code. func TestBadUnsafePtr(t *testing.T) { - withTestProcess("testunsafepointers", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testunsafepointers", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") // danglingPtrPtr is a pointer with value 0x42, which is an unreadable // address. diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index e6b3c2cc..d7209576 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -79,6 +79,7 @@ func (ft *FakeTerminal) ExecStarlark(starlarkProgram string) (outstr string, err } func (ft *FakeTerminal) MustExec(cmdstr string) string { + ft.t.Helper() outstr, err := ft.Exec(cmdstr) if err != nil { ft.t.Errorf("output of %q: %q", cmdstr, outstr) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 66dbf285..64b15564 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -161,30 +161,28 @@ func New(config *Config, processArgs []string) (*Debugger, error) { if len(d.processArgs) > 0 { path = d.processArgs[0] } - p, err := d.Attach(d.config.AttachPid, path) + var err error + d.target, err = d.Attach(d.config.AttachPid, path) if err != nil { err = go11DecodeErrorCheck(err) err = noDebugErrorWarning(err) return nil, attachErrorMessage(d.config.AttachPid, err) } - d.target = proc.NewGroup(p) case d.config.CoreFile != "": - var p *proc.Target var err error switch d.config.Backend { case "rr": d.log.Infof("opening trace %s", d.config.CoreFile) - p, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories) + d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories) default: d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0]) - p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories) + d.target, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories) } if err != nil { err = go11DecodeErrorCheck(err) return nil, err } - d.target = proc.NewGroup(p) if err := d.checkGoVersion(); err != nil { d.target.Detach(true) return nil, err @@ -192,7 +190,8 @@ func New(config *Config, processArgs []string) (*Debugger, error) { default: d.log.Infof("launching process with args: %v", d.processArgs) - p, err := d.Launch(d.processArgs, d.config.WorkingDir) + var err error + d.target, err = d.Launch(d.processArgs, d.config.WorkingDir) if err != nil { if _, ok := err.(*proc.ErrUnsupportedArch); !ok { err = go11DecodeErrorCheck(err) @@ -201,10 +200,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) { } return nil, err } - if p != nil { - // if p == nil and err == nil then we are doing a recording, don't touch d.target - d.target = proc.NewGroup(p) - } if err := d.checkGoVersion(); err != nil { d.target.Detach(true) return nil, err @@ -245,7 +240,7 @@ func (d *Debugger) TargetGoVersion() string { } // Launch will start a process with the given args and working directory. -func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) { +func (d *Debugger) Launch(processArgs []string, wd string) (*proc.TargetGroup, error) { fullpath, err := verifyBinaryFormat(processArgs[0]) if err != nil { return nil, err @@ -285,7 +280,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) go func() { defer d.targetMutex.Unlock() - p, err := d.recordingRun(run) + grp, err := d.recordingRun(run) if err != nil { d.log.Errorf("could not record target: %v", err) // this is ugly but we can't respond to any client requests at this @@ -293,7 +288,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) os.Exit(1) } d.recordingDone() - d.target = proc.NewGroup(p) + d.target = grp if err := d.checkGoVersion(); err != nil { d.log.Error(err) err := d.target.Detach(true) @@ -332,7 +327,7 @@ func (d *Debugger) isRecording() bool { return d.stopRecording != nil } -func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error) { +func (d *Debugger) recordingRun(run func() (string, error)) (*proc.TargetGroup, error) { tracedir, err := run() if err != nil && tracedir == "" { return nil, err @@ -342,7 +337,7 @@ func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error } // Attach will attach to the process specified by 'pid'. -func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) { +func (d *Debugger) Attach(pid int, path string) (*proc.TargetGroup, error) { switch d.config.Backend { case "native": return native.Attach(pid, d.config.DebugInfoDirectories) @@ -360,7 +355,7 @@ func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) { var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install Xcode's command line tools or lldb-server") -func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error) { +func betterGdbserialLaunchError(p *proc.TargetGroup, err error) (*proc.TargetGroup, error) { if runtime.GOOS != "darwin" { return p, err } @@ -482,7 +477,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] d.processArgs = append([]string{d.processArgs[0]}, newArgs...) d.config.Redirects = newRedirects } - var p *proc.Target + var grp *proc.TargetGroup var err error if rebuild { @@ -510,19 +505,20 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } d.recordingStart(stop) - p, err = d.recordingRun(run) + grp, err = d.recordingRun(run) d.recordingDone() } else { - p, err = d.Launch(d.processArgs, d.config.WorkingDir) + grp, err = d.Launch(d.processArgs, d.config.WorkingDir) } if err != nil { return nil, fmt.Errorf("could not launch process: %s", err) } discarded := []api.DiscardedBreakpoint{} - d.target = proc.NewGroupRestart(p, d.target, func(oldBp *proc.LogicalBreakpoint, err error) { + proc.Restart(grp, d.target, func(oldBp *proc.LogicalBreakpoint, err error) { discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()}) }) + d.target = grp return discarded, nil } @@ -1321,7 +1317,7 @@ func (d *Debugger) collectBreakpointInformation(apiThread *api.Thread, thread pr bpi := &api.BreakpointInfo{} apiThread.BreakpointInfo = bpi - tgt := d.target.TargetForThread(thread) + tgt := d.target.TargetForThread(thread.ThreadID()) if bp.Goroutine { g, err := proc.GetG(thread) @@ -2141,7 +2137,7 @@ func (d *Debugger) DumpStart(dest string) error { //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 { + if !d.target.CanDump { d.targetMutex.Unlock() return ErrCoreDumpNotSupported }