diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md index ef48134c..1b3d6ffe 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -5,14 +5,16 @@ Tests skipped by each supported backend: * 3 not implemented * arm64 skipped = 1 * 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 * darwin/arm64 skipped = 2 * 2 broken - cgo stacktraces * darwin/lldb skipped = 1 * 1 upstream issue -* freebsd skipped = 7 +* freebsd skipped = 9 * 2 flaky + * 2 follow exec not implemented on freebsd * 4 not implemented * 1 not working on freebsd * linux/386/pie skipped = 2 diff --git a/pkg/proc/native/followexec_other.go b/pkg/proc/native/followexec_other.go index dd8efc3a..84eb1d25 100644 --- a/pkg/proc/native/followexec_other.go +++ b/pkg/proc/native/followexec_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package native @@ -8,3 +8,7 @@ import "errors" func (*nativeProcess) FollowExec(bool) error { return errors.New("follow exec not implemented") } + +func (*processGroup) detachChild(*nativeProcess) error { + panic("not implemented") +} diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index a73ef590..5e6d1d7a 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -247,7 +247,7 @@ func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.T if tgt == nil { i := len(procgrp.procs) procgrp.procs = append(procgrp.procs, p) - procgrp.Detach(p.pid, false) + procgrp.detachChild(p) if i == len(procgrp.procs)-1 { 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) { - if len(procgrp.procs) != 1 && runtime.GOOS != "linux" { + if len(procgrp.procs) != 1 && runtime.GOOS != "linux" && runtime.GOOS != "windows" { panic("not implemented") } if procgrp.numValid() == 0 { diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 3a30a56c..cba5c82c 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -844,6 +844,10 @@ func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativ return err1 } +func (procgrp *processGroup) detachChild(dbp *nativeProcess) error { + return procgrp.Detach(dbp.pid, false) +} + func (dbp *nativeProcess) detach(kill bool) error { for threadID := range dbp.threads { err := ptraceDetach(threadID, 0) diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 91a38700..68a3ecb6 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -36,7 +36,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin return nil, err } - creationFlags := uint32(_DEBUG_ONLY_THIS_PROCESS) + creationFlags := uint32(_DEBUG_PROCESS) if flags&proc.LaunchForeground == 0 { 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) { + // we need a fake procgrp to call waitForDebugEvent + procgrp := &processGroup{procs: []*nativeProcess{dbp}} // It should not actually be possible for the // call to waitForDebugEvent to fail, since Windows // will always fire a CREATE_PROCESS_DEBUG_EVENT event // immediately after launching under DEBUG_ONLY_THIS_PROCESS. // Attaching with DebugActiveProcess has similar effect. var err error - var tid, exitCode int dbp.execPtraceFunc(func() { - tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) + _, err = procgrp.waitForDebugEvent(waitBlocking) }) if err != nil { return "", err } - if tid == 0 { - dbp.postExit() - return "", proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} - } cmdline := getCmdLine(dbp.os.hProcess) @@ -278,7 +275,7 @@ func (procgrp *processGroup) kill(dbp *nativeProcess) error { _ = syscall.TerminateProcess(dbp.os.hProcess, 1) dbp.execPtraceFunc(func() { - dbp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions) + procgrp.waitForDebugEvent(waitBlocking | waitDontHandleExceptions) }) p.Wait() @@ -345,9 +342,8 @@ const ( 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 - shouldExit := false for { continueStatus := uint32(_DBG_CONTINUE) var milliseconds uint32 = 0 @@ -357,7 +353,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa // Wait for a debug event... err := _WaitForDebugEvent(&debugEvent, milliseconds) if err != nil { - return 0, 0, err + return 0, err } // ... handle each event kind ... @@ -369,26 +365,52 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa if hFile != 0 && hFile != syscall.InvalidHandle { err = syscall.CloseHandle(hFile) 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.hProcess = debugInfo.Process _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) 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 case _CREATE_THREAD_DEBUG_EVENT: debugInfo := (*_CREATE_THREAD_DEBUG_INFO)(unionPtr) + dbp := procgrp.procForPid(int(debugEvent.ProcessId)) _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0, debugInfo.StartAddress == dbgUiRemoteBreakin.Addr()) if err != nil { - return 0, 0, err + return 0, err } break case _EXIT_THREAD_DEBUG_EVENT: + dbp := procgrp.procForPid(int(debugEvent.ProcessId)) delete(dbp.threads, int(debugEvent.ThreadId)) break case _OUTPUT_DEBUG_STRING_EVENT: @@ -400,7 +422,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa if hFile != 0 && hFile != syscall.InvalidHandle { err = syscall.CloseHandle(hFile) if err != nil { - return 0, 0, err + return 0, err } } break @@ -415,6 +437,7 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa } exception := (*_EXCEPTION_DEBUG_INFO)(unionPtr) tid := int(debugEvent.ThreadId) + dbp := procgrp.procForPid(int(debugEvent.ProcessId)) switch code := exception.ExceptionRecord.ExceptionCode; code { case _EXCEPTION_BREAKPOINT: @@ -444,13 +467,13 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa if th := dbp.threads[tid]; th != nil { th.os.setbp = true } - return tid, 0, nil + return tid, nil } else { continueStatus = _DBG_CONTINUE } case _EXCEPTION_SINGLE_STEP: dbp.os.breakThread = tid - return tid, 0, nil + return tid, nil case _MS_VC_EXCEPTION: // This exception is sent to set the thread name in VisualC, we should // mask it or it might crash the program. @@ -460,39 +483,37 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa } case _EXIT_PROCESS_DEBUG_EVENT: debugInfo := (*_EXIT_PROCESS_DEBUG_INFO)(unionPtr) - exitCode = int(debugInfo.ExitCode) - shouldExit = true + dbp := procgrp.procForPid(int(debugEvent.ProcessId)) + 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: - 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. err = _ContinueDebugEvent(debugEvent.ProcessId, debugEvent.ThreadId, continueStatus) if err != nil { - return 0, 0, err - } - - if shouldExit { - return 0, exitCode, nil + return 0, err } } } func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { - dbp := procgrp.procs[0] var err error - var tid, exitCode int - dbp.execPtraceFunc(func() { - tid, exitCode, err = dbp.waitForDebugEvent(waitBlocking) + var tid int + procgrp.procs[0].execPtraceFunc(func() { + tid, err = procgrp.waitForDebugEvent(waitBlocking) }) if err != nil { return nil, err } - if tid == 0 { - dbp.postExit() - return nil, proc.ErrProcessExited{Pid: dbp.pid, Status: exitCode} - } - th := dbp.threads[tid] + th := procgrp.procForThread(tid).threads[tid] return th, nil } @@ -501,38 +522,51 @@ func (dbp *nativeProcess) exitGuard(err error) error { } func (procgrp *processGroup) resume() error { - dbp := procgrp.procs[0] - for _, thread := range dbp.threads { - if thread.CurrentBreakpoint.Breakpoint != nil { - if err := procgrp.stepInstruction(thread); err != nil { - return err + for _, dbp := range procgrp.procs { + if valid, _ := dbp.Valid(); !valid { + continue + } + 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 { - _, err := _ResumeThread(thread.os.hThread) - if err != nil { - return err + for _, dbp := range procgrp.procs { + if valid, _ := dbp.Valid(); !valid { + continue } + 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 } // stop stops all running threads threads and sets breakpoints 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} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} } - dbp.os.running = false - for _, th := range dbp.threads { - th.os.setbp = false + trapproc := procgrp.procForThread(trapthread.ID) + trapproc.os.running = false + + for _, dbp := range procgrp.procs { + for _, th := range dbp.threads { + th.os.setbp = false + } } + trapthread.os.setbp = true // 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 } - 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) - } + for _, dbp := range procgrp.procs { + if valid, _ := dbp.Valid(); !valid { + continue } + dbp.suspendAllThreads() } + lastEventProc := trapproc for { var err error var tid int - dbp.execPtraceFunc(func() { - err = _ContinueDebugEvent(uint32(dbp.pid), uint32(dbp.os.breakThread), _DBG_CONTINUE) + lastEventProc.execPtraceFunc(func() { + err = _ContinueDebugEvent(uint32(lastEventProc.pid), uint32(lastEventProc.os.breakThread), _DBG_CONTINUE) if err == nil { - tid, _, _ = dbp.waitForDebugEvent(waitSuspendNewThreads) + tid, _ = procgrp.waitForDebugEvent(waitSuspendNewThreads) } }) if err != nil { @@ -580,16 +607,18 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na if tid == 0 { break } + dbp := procgrp.procForThread(tid) err = dbp.threads[tid].SetCurrentBreakpoint(true) if err != nil { return nil, err } + lastEventProc = dbp } // Check if trapthread still exist, if the process is dying it could have // been removed while we were stopping the other threads. trapthreadFound := false - for _, thread := range dbp.threads { + for _, thread := range trapproc.threads { if thread.ID == trapthread.ID { trapthreadFound = true } @@ -604,7 +633,7 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na wasDbgUiRemoteBreakIn := trapthread.os.dbgUiRemoteBreakIn // trapthread exited during stop, pick another one trapthread = nil - for _, thread := range dbp.threads { + for _, thread := range trapproc.threads { if thread.CurrentBreakpoint.Breakpoint != nil && thread.os.delayErr == nil { trapthread = thread break @@ -613,7 +642,7 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na if trapthread == nil && wasDbgUiRemoteBreakIn { // If this was triggered by a manual stop request we should stop // regardless, pick a thread. - for _, thread := range dbp.threads { + for _, thread := range trapproc.threads { return thread, nil } } @@ -622,6 +651,35 @@ func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *na 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 { if !kill { //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)) } +// FollowExec enables (or disables) follow exec mode +func (dbp *nativeProcess) FollowExec(v bool) error { + dbp.followExec = v + return nil +} + func killProcess(pid int) error { p, err := os.FindProcess(pid) if err != nil { diff --git a/pkg/proc/native/syscall_windows.go b/pkg/proc/native/syscall_windows.go index 7fe6ff24..ce3b098f 100644 --- a/pkg/proc/native/syscall_windows.go +++ b/pkg/proc/native/syscall_windows.go @@ -95,8 +95,9 @@ const ( _OUTPUT_DEBUG_STRING_EVENT = 8 _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_PROCESS = 0x00000001 _EXCEPTION_BREAKPOINT = 0x80000003 _EXCEPTION_SINGLE_STEP = 0x80000004 diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index ddd2d2bf..8e75c34c 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -59,24 +59,22 @@ func (procgrp *processGroup) singleStep(t *nativeThread) error { } for { - var tid, exitCode int + var tid int t.dbp.execPtraceFunc(func() { - tid, exitCode, err = t.dbp.waitForDebugEvent(waitBlocking | waitSuspendNewThreads) + tid, err = procgrp.waitForDebugEvent(waitBlocking | waitSuspendNewThreads) }) if err != nil { 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 } - t.dbp.execPtraceFunc(func() { - err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.dbp.os.breakThread), _DBG_CONTINUE) + ep.execPtraceFunc(func() { + err = _ContinueDebugEvent(uint32(ep.pid), uint32(ep.os.breakThread), _DBG_CONTINUE) }) } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 65086f4e..2440a797 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -5956,7 +5956,8 @@ func TestStacktraceExtlinkMac(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) { 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)} @@ -6132,7 +6133,8 @@ func TestStepShadowConcurrentBreakpoint(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) { 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)}