go-delve_delve/pkg/debugdetect/detect_test.go
Derek Parker 57b6496c2e
pkg/debugdetect: add package for detecting debugger attachment (#4258)
* pkg/debugdetect: add package for detecting debugger attachment

Implement new pkg/debugdetect package that detects if a program is
running under a debugger on Linux, macOS, Windows, and FreeBSD.

Platform-specific detection methods:
- Linux: Parse /proc/self/status for TracerPid field
- macOS: Use sysctl with KERN_PROC_PID to check P_TRACED flag
- Windows: Call IsDebuggerPresent() Win32 API
- FreeBSD: Try sysctl first, fall back to /proc/curproc/status

Fixes #4243

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-16 10:34:01 +01:00

131 lines
3.3 KiB
Go

package debugdetect
import (
"bufio"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
protest "github.com/go-delve/delve/pkg/proc/test"
"github.com/go-delve/delve/service/rpc2"
)
func TestIntegration_NotAttached(t *testing.T) {
// Build the fixture
fixturesDir := protest.FindFixturesDir()
fixtureSrc := filepath.Join(fixturesDir, "debugdetect.go")
fixtureBin := filepath.Join(t.TempDir(), "debugdetect")
if runtime.GOOS == "windows" {
fixtureBin += ".exe"
}
cmd := exec.Command("go", "build", "-o", fixtureBin, fixtureSrc)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to build fixture: %v\n%s", err, out)
}
// Run the fixture (not under debugger)
cmd = exec.Command(fixtureBin)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("fixture failed: %v\n%s", err, out)
}
output := string(out)
if !strings.Contains(output, "NOT_ATTACHED") {
t.Errorf("expected 'NOT_ATTACHED' in output, got: %s", output)
}
}
func TestIntegration_WaitForDebugger(t *testing.T) {
// This test verifies that WaitForDebugger() blocks until a debugger
// attaches and then returns successfully by running the fixture
// under Delve.
protest.AllowRecording(t)
dlvbin := protest.GetDlvBinary(t)
fixturesDir := protest.FindFixturesDir()
fixtureSrc := filepath.Join(fixturesDir, "waitfordebugger.go")
const listenAddr = "127.0.0.1:40581"
cmd := exec.Command(dlvbin, "debug", fixtureSrc, "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr)
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
defer stdout.Close()
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Read stdout until we see the program output
scanner := bufio.NewScanner(stdout)
foundOutput := false
for scanner.Scan() {
line := scanner.Text()
t.Log(line)
if strings.Contains(line, "DEBUGGER_FOUND") {
foundOutput = true
break
}
}
// Clean up - connect and detach
client := rpc2.NewClient(listenAddr)
client.Detach(true)
cmd.Wait()
if !foundOutput {
t.Error("expected 'DEBUGGER_FOUND' in output when running under debugger")
}
}
func TestIntegration_Attached(t *testing.T) {
// This test verifies that IsDebuggerAttached() returns true when
// the process is actually running under a debugger by using
// Delve to debug the fixture source file
protest.AllowRecording(t)
dlvbin := protest.GetDlvBinary(t)
fixturesDir := protest.FindFixturesDir()
fixtureSrc := filepath.Join(fixturesDir, "debugdetect.go")
// Run the fixture under dlv debug with --headless --continue
// This will attach the debugger, compile and run the program
const listenAddr = "127.0.0.1:40580"
cmd := exec.Command(dlvbin, "debug", fixtureSrc, "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr)
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
defer stdout.Close()
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Read stdout until we see the program output
scanner := bufio.NewScanner(stdout)
foundOutput := false
for scanner.Scan() {
line := scanner.Text()
t.Log(line)
if strings.Contains(line, "ATTACHED") {
foundOutput = true
break
}
}
// Clean up - connect and detach
client := rpc2.NewClient(listenAddr)
client.Detach(true)
cmd.Wait()
if !foundOutput {
t.Error("expected 'ATTACHED' in output when running under debugger")
}
}