Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

service/dap: Add support for debug and test modes #1901

Merged
merged 5 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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