diff --git a/Documentation/usage/dlv_dap.md b/Documentation/usage/dlv_dap.md index 50a73a93..63d89847 100644 --- a/Documentation/usage/dlv_dap.md +++ b/Documentation/usage/dlv_dap.md @@ -7,13 +7,15 @@ [EXPERIMENTAL] Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP). The server is always headless and requires a DAP client like vscode to connect and request a binary -to be launched or process to be attached to. The following modes are supported: +to be launched or process to be attached to. The following modes can be specified via client's launch config: - launch + exec (executes precompiled binary, like 'dlv exec') - launch + debug (builds and launches, like 'dlv debug') - launch + test (builds and tests, like 'dlv test') - launch + replay (replays an rr trace, like 'dlv replay') - launch + core (replays a core dump file, like 'dlv core') - attach + local (attaches to a running process, like 'dlv attach') +Program and output binary paths will be interpreted relative to dlv's working directory. + The server does not yet accept multiple client connections (--accept-multiclient). While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if execution is resumed at the start of the debug session. diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 1a7d7e8a..c74cbb91 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -180,13 +180,15 @@ option to let the process continue or kill it. Long: `[EXPERIMENTAL] Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP). The server is always headless and requires a DAP client like vscode to connect and request a binary -to be launched or process to be attached to. The following modes are supported: +to be launched or process to be attached to. The following modes can be specified via client's launch config: - launch + exec (executes precompiled binary, like 'dlv exec') - launch + debug (builds and launches, like 'dlv debug') - launch + test (builds and tests, like 'dlv test') - launch + replay (replays an rr trace, like 'dlv replay') - launch + core (replays a core dump file, like 'dlv core') - attach + local (attaches to a running process, like 'dlv attach') +Program and output binary paths will be interpreted relative to dlv's working directory. + The server does not yet accept multiple client connections (--accept-multiclient). While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if execution is resumed at the start of the debug session.`, diff --git a/service/dap/server.go b/service/dap/server.go index 78d2c764..6fb982a9 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -864,8 +864,8 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { var cmd string var out []byte - // Log debug binary build - s.log.Debugf("building binary '%s' from '%s' with flags '%v'", debugbinary, program, buildFlags) + wd, _ := os.Getwd() + s.log.Debugf("building program '%s' in '%s' with flags '%v'", program, wd, buildFlags) switch mode { case "debug": cmd, out, err = gobuild.GoBuildCombinedOutput(debugbinary, []string{program}, buildFlags) @@ -936,7 +936,7 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) { s.config.Debugger.WorkingDir = wdParsed } - s.log.Debugf("running program in %s\n", s.config.Debugger.WorkingDir) + s.log.Debugf("running binary '%s' in '%s'", program, s.config.Debugger.WorkingDir) if noDebug, ok := request.Arguments["noDebug"].(bool); ok && noDebug { s.mu.Lock() cmd, err := s.newNoDebugProcess(program, targetArgs, s.config.Debugger.WorkingDir) diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 1b142066..6df35c11 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -3978,8 +3978,9 @@ type onBreakpoint struct { // so the test author has full control of its arguments. // Note that he rest of the test sequence assumes that // stopOnEntry is false. +// source - source file path, needed to set breakpoints, "" if none to be set. // breakpoints - list of lines, where breakpoints are to be set -// onBreakpoints - list of test sequences to execute at each of the set breakpoints. +// onBPs - list of test sequences to execute at each of the set breakpoints. func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string, breakpoints []int, onBPs []onBreakpoint) { client.InitializeRequest() client.ExpectInitializeResponseAndCapabilities(t) @@ -3994,8 +3995,10 @@ func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cm panic("expected launch or attach command") } - client.SetBreakpointsRequest(source, breakpoints) - client.ExpectSetBreakpointsResponse(t) + if source != "" { + client.SetBreakpointsRequest(source, breakpoints) + client.ExpectSetBreakpointsResponse(t) + } // Skip no-op setExceptionBreakpoints @@ -4045,8 +4048,8 @@ func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, cmd string, cm // runDebugSession is a helper for executing the standard init and shutdown // sequences for a program that does not stop on entry // while specifying unique launch criteria via parameters. -func runDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func(), source string) { - runDebugSessionWithBPs(t, client, cmd, cmdRequest, source, nil, nil) +func runDebugSession(t *testing.T, client *daptest.Client, cmd string, cmdRequest func()) { + runDebugSessionWithBPs(t, client, cmd, cmdRequest, "", nil, nil) } func TestLaunchDebugRequest(t *testing.T) { @@ -4061,7 +4064,7 @@ func TestLaunchDebugRequest(t *testing.T) { runDebugSession(t, client, "launch", func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source, "output": tmpBin}) - }, fixture.Source) + }) }) // Wait for the test to finish to capture all stderr time.Sleep(100 * time.Millisecond) @@ -4098,13 +4101,13 @@ func TestLaunchRequestDefaults(t *testing.T) { runDebugSession(t, client, "launch", func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin"}) - }, fixture.Source) + }) }) runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, "launch", func() { client.LaunchRequestWithArgs(map[string]interface{}{ /*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin"}) - }, fixture.Source) + }) }) runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { runDebugSession(t, client, "launch", func() { @@ -4112,7 +4115,7 @@ func TestLaunchRequestDefaults(t *testing.T) { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source}) // writes to default output dir __debug_bin - }, fixture.Source) + }) }) // if noDebug is not a bool, behave as if it is the default value (false). @@ -4120,7 +4123,7 @@ func TestLaunchRequestDefaults(t *testing.T) { runDebugSession(t, client, "launch", func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source, "noDebug": "true"}) - }, fixture.Source) + }) }) } @@ -4219,16 +4222,48 @@ func TestNoDebug_AcceptNoRequestsButDisconnect(t *testing.T) { }) } -func TestLaunchTestRequest(t *testing.T) { +func TestLaunchRequestWithRelativeBuildPath(t *testing.T) { + client := startDapServer(t) + defer client.Close() // will trigger Stop() + + fixdir := protest.FindFixturesDir() + if filepath.IsAbs(fixdir) { + t.Fatal("this test requires relative program path") + } + program := filepath.Join(protest.FindFixturesDir(), "buildtest") + + // Use different working dir for target than dlv. + // Program path will be interpreted relative to dlv's. + dlvwd, _ := os.Getwd() + runDebugSession(t, client, "launch", func() { + client.LaunchRequestWithArgs(map[string]interface{}{ + "mode": "debug", "program": program, "cwd": filepath.Dir(dlvwd)}) + }) +} + +func TestLaunchRequestWithRelativeExecPath(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { + symlink := "./__thisexe" + err := os.Symlink(fixture.Path, symlink) + defer os.Remove(symlink) + if err != nil { + t.Fatal("unable to create relative symlink:", err) + } runDebugSession(t, client, "launch", func() { - // We reuse the harness that builds, but ignore the built binary, - // only relying on the source to be built in response to LaunchRequest. - fixtures := protest.FindFixturesDir() - testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) client.LaunchRequestWithArgs(map[string]interface{}{ - "mode": "test", "program": testdir, "output": "__mytestdir"}) - }, fixture.Source) + "mode": "exec", "program": symlink}) + }) + }) +} + +func TestLaunchTestRequest(t *testing.T) { + client := startDapServer(t) + defer client.Close() // will trigger Stop() + runDebugSession(t, client, "launch", func() { + fixtures := protest.FindFixturesDir() + testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) + client.LaunchRequestWithArgs(map[string]interface{}{ + "mode": "test", "program": testdir, "output": "__mytestdir"}) }) } @@ -4242,7 +4277,7 @@ func TestLaunchRequestWithArgs(t *testing.T) { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "exec", "program": fixture.Path, "args": []string{"test", "pass flag"}}) - }, fixture.Source) + }) }) } @@ -4258,7 +4293,7 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "debug", "program": fixture.Source, "output": "__mybin", "buildFlags": "-ldflags '-X main.Hello=World'"}) - }, fixture.Source) + }) }) }