Skip to content

Commit

Permalink
service/dap: supports noDebug launch requests (#2400)
Browse files Browse the repository at this point in the history
If the launch requests has noDebug attribute set, run the built
binary directly. The launch request handler will block until
the binary terminates, so the editor won't send additional requests
like breakpoint setting etc. Still disconnect or restart requests
can flow in though and they should trigger killing of the target
process if it's still running.

In order to run the binary using os/exec on windows, the target
binary has to have .exe as its extension. So, add .exe to the default
output name if it is on windows. I am not sure though yet we want
to modify the user-specified output or not yet. Considering how
go commands behave (not automatically append .exe for 'go build -o')
I think respecting what user specified is right, but the failure
(file not exist) may be mysterious.
  • Loading branch information
hyangah authored Mar 23, 2021
1 parent 1c9a105 commit 3c1b942
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 2 deletions.
76 changes: 75 additions & 1 deletion service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import (
"io"
"net"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"sync"

"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/logflags"
Expand Down Expand Up @@ -67,6 +70,11 @@ type Server struct {
variableHandles *variablesHandlesMap
// args tracks special settings for handling debug session requests.
args launchAttachArgs

mu sync.Mutex

// noDebugProcess is set for the noDebug launch process.
noDebugProcess *exec.Cmd
}

// launchAttachArgs captures arguments from launch/attach request that
Expand Down Expand Up @@ -431,6 +439,13 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
// Output path for the compiled binary in debug or test modes.
const debugBinary string = "./__debug_bin"

func cleanExeName(name string) string {
if runtime.GOOS == "windows" && filepath.Ext(name) != ".exe" {
return name + ".exe"
}
return name
}

func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
// Validate launch request mode
mode, ok := request.Arguments["mode"]
Expand All @@ -456,7 +471,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
if mode == "debug" || mode == "test" {
output, ok := request.Arguments["output"].(string)
if !ok || output == "" {
output = debugBinary
output = cleanExeName(debugBinary)
}
debugname, err := filepath.Abs(output)
if err != nil {
Expand Down Expand Up @@ -532,6 +547,17 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
s.config.Debugger.WorkingDir = wdParsed
}

noDebug, ok := request.Arguments["noDebug"]
if ok {
if v, ok := noDebug.(bool); ok && v { // noDebug == true
if err := s.runWithoutDebug(program, targetArgs, s.config.Debugger.WorkingDir); err != nil {
s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
} else { // program terminated.
s.send(&dap.TerminatedEvent{Event: *newEvent("terminated")})
}
return
}
}
var err error
if s.debugger, err = debugger.New(&s.config.Debugger, s.config.ProcessArgs); err != nil {
s.sendErrorResponse(request.Request,
Expand All @@ -546,6 +572,52 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)})
}

func (s *Server) runWithoutDebug(program string, targetArgs []string, wd string) error {
s.log.Println("Running without debug: ", program)

cmd := exec.Command(program, targetArgs...)
// TODO: send stdin/out/err as OutputEvent messages
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Dir = s.config.Debugger.WorkingDir

s.mu.Lock()
defer s.mu.Unlock()
if s.noDebugProcess != nil {
return fmt.Errorf("previous process (pid=%v) is still active", s.noDebugProcess.Process.Pid)
}
if err := cmd.Start(); err != nil {
return err
}
s.noDebugProcess = cmd
s.mu.Unlock() // allow disconnect or restart requests to call stopNoDebugProcess.

// block until the process terminates.
if err := cmd.Wait(); err != nil {
s.log.Errorf("process exited with %v", err)
}

s.mu.Lock()
if s.noDebugProcess == cmd {
s.noDebugProcess = nil
}
return nil // Program ran and terminated.
}

func (s *Server) stopNoDebugProcess() {
s.mu.Lock()
defer s.mu.Unlock()
if s.noDebugProcess == nil {
return
}

// TODO(hyangah): gracefully terminate the process and its children processes.
if err := s.noDebugProcess.Process.Kill(); err != nil {
s.log.Errorf("killing process (pid=%v) failed: %v", s.noDebugProcess.Process.Pid, err)
}
}

// TODO(polina): support "remote" mode
func isValidLaunchMode(launchMode interface{}) bool {
switch launchMode {
Expand Down Expand Up @@ -578,6 +650,8 @@ func (s *Server) onDisconnectRequest(request *dap.DisconnectRequest) {
if err != nil {
s.log.Error(err)
}
} else {
s.stopNoDebugProcess()
}
// TODO(polina): make thread-safe when handlers become asynchronous.
s.signalDisconnect()
Expand Down
44 changes: 43 additions & 1 deletion service/dap/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1693,7 +1693,7 @@ func TestWorkingDir(t *testing.T) {
"mode": "exec",
"program": fixture.Path,
"stopOnEntry": false,
"wd": wd,
"wd": wd,
})
},
// Set breakpoints
Expand Down Expand Up @@ -2366,6 +2366,48 @@ func TestLaunchRequestDefaults(t *testing.T) {
})
}

func TestLaunchRequestDefaultsNoDebug(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runNoDebugDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true,
"mode": "", /*"debug" by default*/
"program": fixture.Source,
"output": cleanExeName("__mydir")})
}, fixture.Source)
})
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runNoDebugDebugSession(t, client, "launch", func() {
// Use the default output directory.
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true,
/*"mode":"debug" by default*/
"program": fixture.Source,
"output": cleanExeName("__mydir")})
}, fixture.Source)
})
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runNoDebugDebugSession(t, client, "launch", func() {
// Use the default output directory.
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true,
"mode": "debug",
"program": fixture.Source})
// writes to default output dir __debug_bin
}, fixture.Source)
})
}

func runNoDebugDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string) {
client.InitializeRequest()
client.ExpectInitializeResponse(t)

cmdRequest()
// ! client.InitializedEvent.
// ! client.ExpectLaunchResponse
client.ExpectTerminatedEvent(t)
}

func TestLaunchTestRequest(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSession(t, client, "launch", func() {
Expand Down

0 comments on commit 3c1b942

Please sign in to comment.