Skip to content

Commit

Permalink
service/dap: support waitfor option for 'dap attach' only (#3656)
Browse files Browse the repository at this point in the history
Add a waitfor option for 'dap attach' that waits for a process with a
given name to appear before attaching to it.

This recovers PR #3584, originally by @muggle-nil, which was fine
except for a broken test.
  • Loading branch information
aarzilli authored Feb 7, 2024
1 parent efaf552 commit f2b3b18
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 11 deletions.
26 changes: 21 additions & 5 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1892,17 +1892,33 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) {
fmt.Sprintf("debug session already in progress at %s - use remote mode to connect to a server with an active debug session", s.address()))
return
}
if args.ProcessID == 0 {
s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach",
"The 'processId' attribute is missing in debug configuration")
if args.AttachWaitFor != "" && args.ProcessID != 0 {
s.sendShowUserErrorResponse(
request.Request,
FailedToAttach,
"Failed to attach",
"'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time")
return
} else if args.AttachWaitFor != "" {
s.config.Debugger.AttachWaitFor = args.AttachWaitFor
// Keep the default value the same as waitfor-interval.
s.config.Debugger.AttachWaitForInterval = 1
s.config.log.Debugf("Waiting for a process with a name beginning with this prefix: %s", args.AttachWaitFor)
} else if args.ProcessID != 0 {
s.config.Debugger.AttachPid = args.ProcessID
s.config.log.Debugf("Attaching to pid %d", args.ProcessID)
} else {
s.sendShowUserErrorResponse(
request.Request,
FailedToAttach,
"Failed to attach",
"The 'processId' or 'waitFor' attribute is missing in debug configuration")
return
}
s.config.Debugger.AttachPid = args.ProcessID
if args.Backend == "" {
args.Backend = "default"
}
s.config.Debugger.Backend = args.Backend
s.config.log.Debugf("attaching to pid %d", args.ProcessID)
var err error
func() {
s.mu.Lock()
Expand Down
59 changes: 54 additions & 5 deletions service/dap/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5901,6 +5901,51 @@ func TestAttachRequest(t *testing.T) {
})
}

func TestAttachWaitForRequest(t *testing.T) {
if runtime.GOOS == "freebsd" {
// The value of /proc/[pid]/cmdline might be wrong on FreeBSD: zero or the same as the parent process.
t.Skip("test skipped on freebsd")
}
runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) {
cmd := exec.Command(fixture.Path)
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
// Wait for output.
// This will give the target process time to initialize the runtime before we attach,
// so we can rely on having goroutines when they are requested on attach.
scanOut := bufio.NewScanner(stdout)
scanOut.Scan()
if scanOut.Text() != "past main" {
t.Errorf("expected loopprog.go to output \"past main\"")
}

t.Logf("The process id of program %q is %d", cmd.Path, cmd.Process.Pid)

client.InitializeRequest()
client.ExpectInitializeResponseAndCapabilities(t)
client.AttachRequest(map[string]interface{}{
"mode": "local",
"waitFor": fixture.Path,
"stopOnEntry": true,
})
client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
client.ExpectInitializedEvent(t)
client.ExpectAttachResponse(t)
client.DisconnectRequestWithKillOption(true)
client.ExpectOutputEvent(t)
client.ExpectDisconnectResponse(t)
client.ExpectTerminatedEvent(t)

runtime.KeepAlive(stdout)
})
}

// Since we are in async mode while running, we might receive thee messages after pause request
// in either order.
func expectPauseResponseAndStoppedEvent(t *testing.T, client *daptest.Client) {
Expand Down Expand Up @@ -6621,24 +6666,28 @@ func TestBadAttachRequest(t *testing.T) {

client.AttachRequest(map[string]interface{}{"mode": ""}) // empty mode defaults to "local" (not an error)
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: The 'processId' attribute is missing in debug configuration")
"Failed to attach: The 'processId' or 'waitFor' attribute is missing in debug configuration")

client.AttachRequest(map[string]interface{}{}) // no mode defaults to "local" (not an error)
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: The 'processId' attribute is missing in debug configuration")
"Failed to attach: The 'processId' or 'waitFor' attribute is missing in debug configuration")

// Bad "processId"
client.AttachRequest(map[string]interface{}{"mode": "local"})
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: The 'processId' attribute is missing in debug configuration")
"Failed to attach: The 'processId' or 'waitFor' attribute is missing in debug configuration")

client.AttachRequest(map[string]interface{}{"mode": "local", "processId": nil})
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: The 'processId' attribute is missing in debug configuration")
"Failed to attach: The 'processId' or 'waitFor' attribute is missing in debug configuration")

client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 0})
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: The 'processId' attribute is missing in debug configuration")
"Failed to attach: The 'processId' or 'waitFor' attribute is missing in debug configuration")

client.AttachRequest(map[string]interface{}{"mode": "local", "processId": 1, "waitFor": "loopprog"})
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
"Failed to attach: 'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time")

client.AttachRequest(map[string]interface{}{"mode": "local", "processId": "1"})
checkFailedToAttachWithMessage(client.ExpectVisibleErrorResponse(t),
Expand Down
6 changes: 5 additions & 1 deletion service/dap/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func (m *SubstitutePath) UnmarshalJSON(data []byte) error {
}

// AttachConfig is the collection of attach request attributes recognized by DAP implementation.
// 'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time.
type AttachConfig struct {
// Acceptable values are:
// "local": attaches to the local process with the given ProcessID.
Expand All @@ -245,9 +246,12 @@ type AttachConfig struct {
// Default is "local".
Mode string `json:"mode"`

// The numeric ID of the process to be debugged. Required and must not be 0.
// The numeric ID of the process to be debugged.
ProcessID int `json:"processId,omitempty"`

// Wait for a process with a name beginning with this prefix.
AttachWaitFor string `json:"waitFor,omitempty"`

LaunchAttachCommonConfig
}

Expand Down

0 comments on commit f2b3b18

Please sign in to comment.