Skip to content

Commit

Permalink
service/dap: Add support for debug and test modes (go-delve#1901)
Browse files Browse the repository at this point in the history
* service/dap: Add support for debug and test modes

* Address code review comments

* Remove //dap comment

* OptFlags() => optfalgs()

* If mode => switch mode
  • Loading branch information
polinasok authored and abner-chenc committed Nov 23, 2020
1 parent c1a681f commit 4ebaa26
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 91 deletions.
10 changes: 10 additions & 0 deletions _fixtures/buildtest/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"os"
"testing"
)

func TestMain(m *testing.M) {
os.Exit(m.Run())
}
68 changes: 8 additions & 60 deletions cmd/dlv/cmds/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"syscall"

"github.com/go-delve/delve/pkg/config"
"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/terminal"
Expand Down Expand Up @@ -354,14 +355,6 @@ and dap modes.
return RootCommand
}

// Remove the file at path and issue a warning to stderr if this fails.
func remove(path string) {
err := os.Remove(path)
if err != nil {
fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
}
}

func dapCmd(cmd *cobra.Command, args []string) {
status := func() int {
if err := logflags.Setup(Log, LogOutput, LogDest); err != nil {
Expand Down Expand Up @@ -428,12 +421,12 @@ func debugCmd(cmd *cobra.Command, args []string) {
}

dlvArgs, targetArgs := splitArgs(cmd, args)
err = gobuild(debugname, dlvArgs)
err = gobuild.GoBuild(debugname, dlvArgs, BuildFlags)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
defer remove(debugname)
defer gobuild.Remove(debugname)
processArgs := append([]string{debugname}, targetArgs...)
return execute(0, processArgs, conf, "", executingGeneratedFile)
}()
Expand Down Expand Up @@ -484,17 +477,17 @@ func traceCmd(cmd *cobra.Command, args []string) {
return 1
}
if traceTestBinary {
if err := gotestbuild(debugname, dlvArgs); err != nil {
if err := gobuild.GoTestBuild(debugname, dlvArgs, BuildFlags); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
} else {
if err := gobuild(debugname, dlvArgs); err != nil {
if err := gobuild.GoBuild(debugname, dlvArgs, BuildFlags); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
}
defer remove(debugname)
defer gobuild.Remove(debugname)
}

processArgs = append([]string{debugname}, targetArgs...)
Expand Down Expand Up @@ -576,11 +569,11 @@ func testCmd(cmd *cobra.Command, args []string) {
}

dlvArgs, targetArgs := splitArgs(cmd, args)
err = gotestbuild(debugname, dlvArgs)
err = gobuild.GoTestBuild(debugname, dlvArgs, BuildFlags)
if err != nil {
return 1
}
defer remove(debugname)
defer gobuild.Remove(debugname)
processArgs := append([]string{debugname}, targetArgs...)

return execute(0, processArgs, conf, "", executingGeneratedTest)
Expand Down Expand Up @@ -795,48 +788,3 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile

return connect(listener.Addr().String(), clientConn, conf, kind)
}

func optflags(args []string) []string {
// after go1.9 building with -gcflags='-N -l' and -a simultaneously works.
// after go1.10 specifying -a is unnecessary because of the new caching strategy, but we should pass -gcflags=all=-N -l to have it applied to all packages
// see https://github.com/golang/go/commit/5993251c015dfa1e905bdf44bdb41572387edf90

ver, _ := goversion.Installed()
switch {
case ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}):
args = append(args, "-gcflags", "all=-N -l")
case ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}):
args = append(args, "-gcflags", "-N -l", "-a")
default:
args = append(args, "-gcflags", "-N -l")
}
return args
}

func gobuild(debugname string, pkgs []string) error {
args := []string{"-o", debugname}
args = optflags(args)
if BuildFlags != "" {
args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
}
args = append(args, pkgs...)
return gocommand("build", args...)
}

func gotestbuild(debugname string, pkgs []string) error {
args := []string{"-c", "-o", debugname}
args = optflags(args)
if BuildFlags != "" {
args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
}
args = append(args, pkgs...)
return gocommand("test", args...)
}

func gocommand(command string, args ...string) error {
allargs := []string{command}
allargs = append(allargs, args...)
goBuild := exec.Command("go", allargs...)
goBuild.Stderr = os.Stderr
return goBuild.Run()
}
72 changes: 72 additions & 0 deletions pkg/gobuild/gobuild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Package gobuild provides utilities for building programs and tests
// for the debugging session.
package gobuild

import (
"fmt"
"os"
"os/exec"

"github.com/go-delve/delve/pkg/config"
"github.com/go-delve/delve/pkg/goversion"
)

// Remove the file at path and issue a warning to stderr if this fails.
// This can be used to remove the temporary binary generated for the session.
func Remove(path string) {
err := os.Remove(path)
if err != nil {
fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
}
}

// optflags generates default build flags to turn off optimization and inlining.
func optflags(args []string) []string {
// after go1.9 building with -gcflags='-N -l' and -a simultaneously works.
// after go1.10 specifying -a is unnecessary because of the new caching strategy,
// but we should pass -gcflags=all=-N -l to have it applied to all packages
// see https://github.com/golang/go/commit/5993251c015dfa1e905bdf44bdb41572387edf90

ver, _ := goversion.Installed()
switch {
case ver.Major < 0 || ver.AfterOrEqual(goversion.GoVersion{1, 10, -1, 0, 0, ""}):
args = append(args, "-gcflags", "all=-N -l")
case ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}):
args = append(args, "-gcflags", "-N -l", "-a")
default:
args = append(args, "-gcflags", "-N -l")
}
return args
}

// GoBuild builds non-test files in 'pkgs' with the specified 'buildflags'
// and writes the output at 'debugname'.
func GoBuild(debugname string, pkgs []string, buildflags string) error {
args := []string{"-o", debugname}
args = optflags(args)
if buildflags != "" {
args = append(args, config.SplitQuotedFields(buildflags, '\'')...)
}
args = append(args, pkgs...)
return gocommand("build", args...)
}

// GoBuild builds test files 'pkgs' with the specified 'buildflags'
// and writes the output at 'debugname'.
func GoTestBuild(debugname string, pkgs []string, buildflags string) error {
args := []string{"-c", "-o", debugname}
args = optflags(args)
if buildflags != "" {
args = append(args, config.SplitQuotedFields(buildflags, '\'')...)
}
args = append(args, pkgs...)
return gocommand("test", args...)
}

func gocommand(command string, args ...string) error {
allargs := []string{command}
allargs = append(allargs, args...)
goBuild := exec.Command("go", allargs...)
goBuild.Stderr = os.Stderr
return goBuild.Run()
}
15 changes: 12 additions & 3 deletions service/dap/daptest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,27 @@ func (c *Client) InitializeRequest() {
c.send(request)
}

// LaunchRequest sends a 'launch' request.
func (c *Client) LaunchRequest(program string, stopOnEntry bool) {
// LaunchRequest sends a 'launch' request with the specified args.
func (c *Client) LaunchRequest(mode string, program string, stopOnEntry bool) {
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
request.Arguments = map[string]interface{}{
"request": "launch",
"mode": "exec",
"mode": mode,
"program": program,
"stopOnEntry": stopOnEntry,
}
c.send(request)
}

// LaunchRequestWithArgs takes a map of untyped implementation-specific
// arguments to send a 'launch' request. This version can be used to
// test for values of unexpected types or unspecified values.
func (c *Client) LaunchRequestWithArgs(arguments map[string]interface{}) {
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
request.Arguments = arguments
c.send(request)
}

// DisconnectRequest sends a 'disconnect' request.
func (c *Client) DisconnectRequest() {
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
Expand Down
62 changes: 53 additions & 9 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"net"
"path/filepath"

"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/service"
Expand Down Expand Up @@ -52,6 +53,8 @@ type Server struct {
log *logrus.Entry
// stopOnEntry is set to automatically stop the debugee after start.
stopOnEntry bool
// binaryToRemove is the compiled binary to be removed on disconnect.
binaryToRemove string
}

// NewServer creates a new DAP Server. It takes an opened Listener
Expand Down Expand Up @@ -115,6 +118,9 @@ func (s *Server) signalDisconnect() {
close(s.config.DisconnectChan)
s.config.DisconnectChan = nil
}
if s.binaryToRemove != "" {
gobuild.Remove(s.binaryToRemove)
}
}

// Run launches a new goroutine where it accepts a client connection
Expand Down Expand Up @@ -286,34 +292,72 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
s.send(response)
}

// Output path for the compiled binary in debug or test modes.
const debugBinary string = "./__debug_bin"

func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
// TODO(polina): Respond with an error if debug session is in progress?
program, ok := request.Arguments["program"]

program, ok := request.Arguments["program"].(string)
if !ok || program == "" {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch",
"The program attribute is missing in debug configuration.")
return
}
s.config.ProcessArgs = []string{program.(string)}
s.config.WorkingDir = filepath.Dir(program.(string))
// TODO: support program args

stop, ok := request.Arguments["stopOnEntry"]
s.stopOnEntry = (ok && stop == true)

mode, ok := request.Arguments["mode"]
if !ok || mode == "" {
mode = "debug"
}
// TODO(polina): support "debug", "test" and "remote" modes
if mode != "exec" {

if mode == "debug" || mode == "test" {
output, ok := request.Arguments["output"].(string)
if !ok || output == "" {
output = debugBinary
}
debugname, err := filepath.Abs(output)
if err != nil {
s.sendInternalErrorResponse(request.Seq, err.Error())
return
}

buildFlags, ok := request.Arguments["buildFlags"].(string)
if !ok {
buildFlags = ""
}

switch mode {
case "debug":
err = gobuild.GoBuild(debugname, []string{program}, buildFlags)
case "test":
err = gobuild.GoTestBuild(debugname, []string{program}, buildFlags)
}
if err != nil {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch",
fmt.Sprintf("Build error: %s", err.Error()))
return
}
program = debugname
s.binaryToRemove = debugname
}

// TODO(polina): support "remote" mode
if mode != "exec" && mode != "debug" && mode != "test" {
s.sendErrorResponse(request.Request,
FailedToContinue, "Failed to launch",
fmt.Sprintf("Unsupported 'mode' value %q in debug configuration.", mode))
return
}

stop, ok := request.Arguments["stopOnEntry"]
s.stopOnEntry = (ok && stop == true)

// TODO(polina): support target args
s.config.ProcessArgs = []string{program}
s.config.WorkingDir = filepath.Dir(program)

config := &debugger.Config{
WorkingDir: s.config.WorkingDir,
AttachPid: 0,
Expand Down
Loading

0 comments on commit 4ebaa26

Please sign in to comment.