proc: implement follow exec mode on Windows (#3507)

This commit is contained in:
Alessandro Arzilli 2023-10-13 16:51:11 +02:00 committed by GitHub
parent 70f21c9f86
commit 20350611ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 161 additions and 86 deletions

@ -5,14 +5,16 @@ Tests skipped by each supported backend:
* 3 not implemented * 3 not implemented
* arm64 skipped = 1 * arm64 skipped = 1
* 1 broken - global variable symbolication * 1 broken - global variable symbolication
* darwin skipped = 1 * darwin skipped = 3
* 2 follow exec not implemented on macOS
* 1 waitfor implementation is delegated to debugserver * 1 waitfor implementation is delegated to debugserver
* darwin/arm64 skipped = 2 * darwin/arm64 skipped = 2
* 2 broken - cgo stacktraces * 2 broken - cgo stacktraces
* darwin/lldb skipped = 1 * darwin/lldb skipped = 1
* 1 upstream issue * 1 upstream issue
* freebsd skipped = 7 * freebsd skipped = 9
* 2 flaky * 2 flaky
* 2 follow exec not implemented on freebsd
* 4 not implemented * 4 not implemented
* 1 not working on freebsd * 1 not working on freebsd
* linux/386/pie skipped = 2 * linux/386/pie skipped = 2

@ -1,4 +1,4 @@
//go:build !linux //go:build !linux && !windows
package native package native
@ -8,3 +8,7 @@ import "errors"
func (*nativeProcess) FollowExec(bool) error { func (*nativeProcess) FollowExec(bool) error {
return errors.New("follow exec not implemented") return errors.New("follow exec not implemented")
} }
func (*processGroup) detachChild(*nativeProcess) error {
panic("not implemented")
}

@ -247,7 +247,7 @@ func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.T
if tgt == nil { if tgt == nil {
i := len(procgrp.procs) i := len(procgrp.procs)
procgrp.procs = append(procgrp.procs, p) procgrp.procs = append(procgrp.procs, p)
procgrp.Detach(p.pid, false) procgrp.detachChild(p)
if i == len(procgrp.procs)-1 { if i == len(procgrp.procs)-1 {
procgrp.procs = procgrp.procs[:i] procgrp.procs = procgrp.procs[:i]
} }
@ -262,7 +262,7 @@ func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.T
} }
func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) {
if len(procgrp.procs) != 1 && runtime.GOOS != "linux" { if len(procgrp.procs) != 1 && runtime.GOOS != "linux" && runtime.GOOS != "windows" {
panic("not implemented") panic("not implemented")
} }
if procgrp.numValid() == 0 { if procgrp.numValid() == 0 {

@ -844,6 +844,10 @@ func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativ
return err1 return err1
} }
func (procgrp *processGroup) detachChild(dbp *nativeProcess) error {
return procgrp.Detach(dbp.pid, false)
}
func (dbp *nativeProcess) detach(kill bool) error { func (dbp *nativeProcess) detach(kill bool) error {
for threadID := range dbp.threads { for threadID := range dbp.threads {
err := ptraceDetach(threadID, 0) err := ptraceDetach(threadID, 0)

@ -36,7 +36,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
return nil, err return nil, err
} }
creationFlags := uint32(_DEBUG_ONLY_THIS_PROCESS) creationFlags := uint32(_DEBUG_PROCESS)
if flags&proc.LaunchForeground == 0 { if flags&proc.LaunchForeground == 0 {
creationFlags |= syscall.CREATE_NEW_PROCESS_GROUP creationFlags |= syscall.CREATE_NEW_PROCESS_GROUP
} }
@ -72,23 +72,20 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin
} }
func initialize(dbp *nativeProcess) (string, error) { func initialize(dbp *nativeProcess) (string, error) {
// we need a fake procgrp to call waitForDebugEvent
procgrp := &processGroup{procs: []*nativeProcess{dbp}}
// It should not actually be possible for the // It should not actually be possible for the
// call to waitForDebugEvent to fail, since Windows // call to waitForDebugEvent to fail, since Windows
// will always fire a CREATE_PROCESS_DEBUG_EVENT event // will always fire a CREATE_PROCESS_DEBUG_EVENT event
// immediately after launching under DEBUG_ONLY_THIS_PROCESS. // immediately after launching under DEBUG_ONLY_THIS_PROCESS.
// Attaching with DebugActiveProcess has similar effect. // Attaching with DebugActiveProcess has similar effect.
var err error var err error
var tid, exitCode int
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) _, err = procgrp.waitForDebugEvent(waitBlocking)
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
if tid == 0 {
dbp.postExit()
return "", proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
}
cmdline := getCmdLine(dbp.os.hProcess) cmdline := getCmdLine(dbp.os.hProcess)
@ -278,7 +275,7 @@ func (procgrp *processGroup) kill(dbp *nativeProcess) error {
_ = syscall.TerminateProcess(dbp.os.hProcess, 1) _ = syscall.TerminateProcess(dbp.os.hProcess, 1)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
dbp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions) procgrp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions)
}) })
p.Wait() p.Wait()
@ -345,9 +342,8 @@ const (
const _MS_VC_EXCEPTION = 0x406D1388 // part of VisualC protocol to set thread names const _MS_VC_EXCEPTION = 0x406D1388 // part of VisualC protocol to set thread names
func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, exitCode int, err error) { func (procgrp *processGroup) waitForDebugEvent(flags waitForDebugEventFlags) (threadID int, err error) {
var debugEvent _DEBUG_EVENT var debugEvent _DEBUG_EVENT
shouldExit := false
for { for {
continueStatus := uint32(_DBG_CONTINUE) continueStatus := uint32(_DBG_CONTINUE)
var milliseconds uint32 = 0 var milliseconds uint32 = 0
@ -357,7 +353,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
// Wait for a debug event... // Wait for a debug event...
err := _WaitForDebugEvent(&debugEvent, milliseconds) err := _WaitForDebugEvent(&debugEvent, milliseconds)
if err != nil { if err != nil {
return 0, 0, err return 0, err
} }
// ... handle each event kind ... // ... handle each event kind ...
@ -369,26 +365,52 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
if hFile != 0 && hFile != syscall.InvalidHandle { if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile) err = syscall.CloseHandle(hFile)
if err != nil { if err != nil {
return 0, 0, err return 0, err
} }
} }
var dbp *nativeProcess
if procgrp.addTarget == nil {
// This is a fake process group and waitForDebugEvent has been called
// just after attach/launch, finish configuring the root process.
dbp = procgrp.procs[0]
} else {
// Add new child process
dbp = newChildProcess(procgrp.procs[0], int(debugEvent.ProcessId))
}
dbp.os.entryPoint = uint64(debugInfo.BaseOfImage) dbp.os.entryPoint = uint64(debugInfo.BaseOfImage)
dbp.os.hProcess = debugInfo.Process dbp.os.hProcess = debugInfo.Process
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false,
flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr())
if err != nil { if err != nil {
return 0, 0, err return 0, err
}
if procgrp.addTarget != nil {
exe, err := findExePath(dbp.pid)
if err != nil {
return 0, err
}
tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, exe, proc.StopLaunched, getCmdLine(dbp.os.hProcess))
if err != nil {
return 0, err
}
if tgt == nil {
continue
}
} }
break break
case _CREATE_THREAD_DEBUG_EVENT: case _CREATE_THREAD_DEBUG_EVENT:
debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr) debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr)
dbp := procgrp.procForPid(int(debugEvent.ProcessId))
_, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false,
flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr())
if err != nil { if err != nil {
return 0, 0, err return 0, err
} }
break break
case _EXIT_THREAD_DEBUG_EVENT: case _EXIT_THREAD_DEBUG_EVENT:
dbp := procgrp.procForPid(int(debugEvent.ProcessId))
delete(dbp.threads, int(debugEvent.ThreadId)) delete(dbp.threads, int(debugEvent.ThreadId))
break break
case _OUTPUT_DEBUG_STRING_EVENT: case _OUTPUT_DEBUG_STRING_EVENT:
@ -400,7 +422,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
if hFile != 0 && hFile != syscall.InvalidHandle { if hFile != 0 && hFile != syscall.InvalidHandle {
err = syscall.CloseHandle(hFile) err = syscall.CloseHandle(hFile)
if err != nil { if err != nil {
return 0, 0, err return 0, err
} }
} }
break break
@ -415,6 +437,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
} }
exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr) exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr)
tid := int(debugEvent.ThreadId) tid := int(debugEvent.ThreadId)
dbp := procgrp.procForPid(int(debugEvent.ProcessId))
switch code := exception.ExceptionRecord.ExceptionCode; code { switch code := exception.ExceptionRecord.ExceptionCode; code {
case _EXCEPTION_BREAKPOINT: case _EXCEPTION_BREAKPOINT:
@ -444,13 +467,13 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
if th := dbp.threads[tid]; th != nil { if th := dbp.threads[tid]; th != nil {
th.os.setbp = true th.os.setbp = true
} }
return tid, 0, nil return tid, nil
} else { } else {
continueStatus = _DBG_CONTINUE continueStatus = _DBG_CONTINUE
} }
case _EXCEPTION_SINGLE_STEP: case _EXCEPTION_SINGLE_STEP:
dbp.os.breakThread = tid dbp.os.breakThread = tid
return tid, 0, nil return tid, nil
case _MS_VC_EXCEPTION: case _MS_VC_EXCEPTION:
// This exception is sent to set the thread name in VisualC, we should // This exception is sent to set the thread name in VisualC, we should
// mask it or it might crash the program. // mask it or it might crash the program.
@ -460,39 +483,37 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa
} }
case _EXIT_PROCESS_DEBUG_EVENT: case _EXIT_PROCESS_DEBUG_EVENT:
debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr) debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr)
exitCode = int(debugInfo.ExitCode) dbp := procgrp.procForPid(int(debugEvent.ProcessId))
shouldExit = true dbp.postExit()
if procgrp.numValid() == 0 {
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil {
return 0, err
}
return 0, proc.ErrProcessExited{Pid: dbp.pid, Status: int(debugInfo.ExitCode)}
}
default: default:
return 0, 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode) return 0, fmt.Errorf("unknown debug event code: %d", debugEvent.DebugEventCode)
} }
// .. and then continue unless we received an event that indicated we should break into debugger. // .. and then continue unless we received an event that indicated we should break into debugger.
err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus) err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus)
if err != nil { if err != nil {
return 0, 0, err return 0, err
}
if shouldExit {
return 0, exitCode, nil
} }
} }
} }
func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) {
dbp := procgrp.procs[0]
var err error var err error
var tid, exitCode int var tid int
dbp.execPtraceFunc(func() { procgrp.procs[0].execPtraceFunc(func() {
tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) tid, err = procgrp.waitForDebugEvent(waitBlocking)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if tid == 0 { th := procgrp.procForThread(tid).threads[tid]
dbp.postExit()
return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode}
}
th := dbp.threads[tid]
return th, nil return th, nil
} }
@ -501,38 +522,51 @@ func (dbp *nativeProcess) exitGuard(err error) error {
} }
func (procgrp *processGroup) resume() error { func (procgrp *processGroup) resume() error {
dbp := procgrp.procs[0] for _, dbp := range procgrp.procs {
for _, thread := range dbp.threads { if valid, _ := dbp.Valid(); !valid {
if thread.CurrentBreakpoint.Breakpoint != nil { continue
if err := procgrp.stepInstruction(thread); err != nil { }
return err for _, thread := range dbp.threads {
if thread.CurrentBreakpoint.Breakpoint != nil {
if err := procgrp.stepInstruction(thread); err != nil {
return err
}
thread.CurrentBreakpoint.Clear()
} }
thread.CurrentBreakpoint.Clear()
} }
} }
for _, thread := range dbp.threads { for _, dbp := range procgrp.procs {
_, err := _ResumeThread(thread.os.hThread) if valid, _ := dbp.Valid(); !valid {
if err != nil { continue
return err
} }
for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
dbp.os.running = true
} }
dbp.os.running = true
return nil return nil
} }
// stop stops all running threads threads and sets breakpoints // stop stops all running threads threads and sets breakpoints
func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) {
dbp := procgrp.procs[0] if procgrp.numValid() == 0 {
if dbp.exited { return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid}
return nil, proc.ErrProcessExited{Pid: dbp.pid}
} }
dbp.os.running = false trapproc := procgrp.procForThread(trapthread.ID)
for _, th := range dbp.threads { trapproc.os.running = false
th.os.setbp = false
for _, dbp := range procgrp.procs {
for _, th := range dbp.threads {
th.os.setbp = false
}
} }
trapthread.os.setbp = true trapthread.os.setbp = true
// While the debug event that stopped the target was being propagated // While the debug event that stopped the target was being propagated
@ -550,28 +584,21 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na
return nil, err return nil, err
} }
context := newContext() for _, dbp := range procgrp.procs {
if valid, _ := dbp.Valid(); !valid {
for _, thread := range dbp.threads { continue
thread.os.delayErr = nil
if !thread.os.dbgUiRemoteBreakIn {
// Wait before reporting the error, the thread could be removed when we
// call waitForDebugEvent in the next loop.
_, thread.os.delayErr = _SuspendThread(thread.os.hThread)
if thread.os.delayErr == nil {
// This call will block until the thread has stopped.
_ = thread.getContext(context)
}
} }
dbp.suspendAllThreads()
} }
lastEventProc := trapproc
for { for {
var err error var err error
var tid int var tid int
dbp.execPtraceFunc(func() { lastEventProc.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(lastEventProc.pid), uint32(lastEventProc.os.breakThread), _DBG_CONTINUE)
if err == nil { if err == nil {
tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads) tid, _ = procgrp.waitForDebugEvent(waitSuspendNewThreads)
} }
}) })
if err != nil { if err != nil {
@ -580,16 +607,18 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na
if tid == 0 { if tid == 0 {
break break
} }
dbp := procgrp.procForThread(tid)
err = dbp.threads[tid].SetCurrentBreakpoint(true) err = dbp.threads[tid].SetCurrentBreakpoint(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lastEventProc = dbp
} }
// Check if trapthread still exist, if the process is dying it could have // Check if trapthread still exist, if the process is dying it could have
// been removed while we were stopping the other threads. // been removed while we were stopping the other threads.
trapthreadFound := false trapthreadFound := false
for _, thread := range dbp.threads { for _, thread := range trapproc.threads {
if thread.ID == trapthread.ID { if thread.ID == trapthread.ID {
trapthreadFound = true trapthreadFound = true
} }
@ -604,7 +633,7 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na
wasDbgUiRemoteBreakIn := trapthread.os.dbgUiRemoteBreakIn wasDbgUiRemoteBreakIn := trapthread.os.dbgUiRemoteBreakIn
// trapthread exited during stop, pick another one // trapthread exited during stop, pick another one
trapthread = nil trapthread = nil
for _, thread := range dbp.threads { for _, thread := range trapproc.threads {
if thread.CurrentBreakpoint.Breakpoint != nil && thread.os.delayErr == nil { if thread.CurrentBreakpoint.Breakpoint != nil && thread.os.delayErr == nil {
trapthread = thread trapthread = thread
break break
@ -613,7 +642,7 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na
if trapthread == nil && wasDbgUiRemoteBreakIn { if trapthread == nil && wasDbgUiRemoteBreakIn {
// If this was triggered by a manual stop request we should stop // If this was triggered by a manual stop request we should stop
// regardless, pick a thread. // regardless, pick a thread.
for _, thread := range dbp.threads { for _, thread := range trapproc.threads {
return thread, nil return thread, nil
} }
} }
@ -622,6 +651,35 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na
return trapthread, nil return trapthread, nil
} }
func (dbp *nativeProcess) suspendAllThreads() {
context := newContext()
for _, thread := range dbp.threads {
thread.os.delayErr = nil
if !thread.os.dbgUiRemoteBreakIn {
// Wait before reporting the error, the thread could be removed when we
// call waitForDebugEvent in the next loop.
_, thread.os.delayErr = _SuspendThread(thread.os.hThread)
if thread.os.delayErr == nil {
// This call will block until the thread has stopped.
_ = thread.getContext(context)
}
}
}
}
func (procgrp *processGroup) detachChild(dbp *nativeProcess) error {
for _, thread := range dbp.threads {
_, err := _ResumeThread(thread.os.hThread)
if err != nil {
return err
}
}
err := _DebugActiveProcessStop(uint32(dbp.pid))
dbp.detached = true
dbp.postExit()
return err
}
func (dbp *nativeProcess) detach(kill bool) error { func (dbp *nativeProcess) detach(kill bool) error {
if !kill { if !kill {
//TODO(aarzilli): when debug.Target exist Detach should be moved to //TODO(aarzilli): when debug.Target exist Detach should be moved to
@ -775,6 +833,12 @@ func getCmdLine(hProcess syscall.Handle) string {
return string(utf16.Decode(utf16buf)) return string(utf16.Decode(utf16buf))
} }
// FollowExec enables (or disables) follow exec mode
func (dbp *nativeProcess) FollowExec(v bool) error {
dbp.followExec = v
return nil
}
func killProcess(pid int) error { func killProcess(pid int) error {
p, err := os.FindProcess(pid) p, err := os.FindProcess(pid)
if err != nil { if err != nil {

@ -95,8 +95,9 @@ const (
_OUTPUT_DEBUG_STRING_EVENT = 8 _OUTPUT_DEBUG_STRING_EVENT = 8
_RIP_EVENT = 9 _RIP_EVENT = 9
// DEBUG_ONLY_THIS_PROCESS tracks https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx // DEBUG_ONLY_THIS_PROCESS and _DEBUG_PROCESS tracks https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
_DEBUG_ONLY_THIS_PROCESS = 0x00000002 _DEBUG_ONLY_THIS_PROCESS = 0x00000002
_DEBUG_PROCESS = 0x00000001
_EXCEPTION_BREAKPOINT = 0x80000003 _EXCEPTION_BREAKPOINT = 0x80000003
_EXCEPTION_SINGLE_STEP = 0x80000004 _EXCEPTION_SINGLE_STEP = 0x80000004

@ -59,24 +59,22 @@ func (procgrp *processGroup) singleStep(t *nativeThread) error {
} }
for { for {
var tid, exitCode int var tid int
t.dbp.execPtraceFunc(func() { t.dbp.execPtraceFunc(func() {
tid, exitCode, err = t.dbp.waitForDebugEvent(waitBlocking | waitSuspendNewThreads) tid, err = procgrp.waitForDebugEvent(waitBlocking | waitSuspendNewThreads)
}) })
if err != nil { if err != nil {
return err return err
} }
if tid == 0 {
t.dbp.postExit()
return proc.ErrProcessExited{Pid: t.dbp.pid, Status: exitCode}
}
if t.dbp.os.breakThread == t.ID { ep := procgrp.procForThread(tid)
if ep.pid == t.dbp.pid && tid == t.ID {
break break
} }
t.dbp.execPtraceFunc(func() { ep.execPtraceFunc(func() {
err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE) err = _ContinueDebugEvent(uint32(ep.pid), uint32(ep.os.breakThread), _DBG_CONTINUE)
}) })
} }

@ -5956,7 +5956,8 @@ func TestStacktraceExtlinkMac(t *testing.T) {
} }
func TestFollowExec(t *testing.T) { func TestFollowExec(t *testing.T) {
skipUnlessOn(t, "follow exec only supported on linux", "linux") skipOn(t, "follow exec not implemented on freebsd", "freebsd")
skipOn(t, "follow exec not implemented on macOS", "darwin")
withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { 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[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[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)}
@ -6132,7 +6133,8 @@ func TestStepShadowConcurrentBreakpoint(t *testing.T) {
} }
func TestFollowExecRegexFilter(t *testing.T) { func TestFollowExecRegexFilter(t *testing.T) {
skipUnlessOn(t, "follow exec only supported on linux", "linux") skipOn(t, "follow exec not implemented on freebsd", "freebsd")
skipOn(t, "follow exec not implemented on macOS", "darwin")
withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { 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[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[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)}