2020-02-21 17:05:30 +00:00
// Package dap implements VSCode's Debug Adaptor Protocol (DAP).
// This allows delve to communicate with frontends using DAP
// without a separate adaptor. The frontend will run the debugger
// (which now doubles as an adaptor) in server mode listening on
// a port and communicating over TCP. This is work in progress,
// so for now Delve in dap mode only supports synchronous
// request-response communication, blocking while processing each request.
// For DAP details see https://microsoft.github.io/debug-adapter-protocol.
2020-02-15 19:52:53 +00:00
package dap
import (
"bufio"
2021-05-17 16:25:41 +00:00
"bytes"
2020-02-15 19:52:53 +00:00
"encoding/json"
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
"errors"
2020-02-15 19:52:53 +00:00
"fmt"
2020-07-07 13:21:18 +00:00
"go/constant"
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
"go/parser"
2020-02-15 19:52:53 +00:00
"io"
"net"
2020-03-09 17:14:34 +00:00
"os"
2021-03-23 19:10:21 +00:00
"os/exec"
2020-02-15 19:52:53 +00:00
"path/filepath"
2020-08-11 15:34:27 +00:00
"reflect"
2020-11-12 23:24:31 +00:00
"regexp"
2021-03-23 19:10:21 +00:00
"runtime"
2021-04-29 09:15:32 +00:00
"runtime/debug"
2020-09-15 20:14:55 +00:00
"strings"
2021-03-23 19:10:21 +00:00
"sync"
2020-02-15 19:52:53 +00:00
2020-03-04 17:22:51 +00:00
"github.com/go-delve/delve/pkg/gobuild"
2021-06-28 15:39:34 +00:00
"github.com/go-delve/delve/pkg/goversion"
2021-04-15 23:35:37 +00:00
"github.com/go-delve/delve/pkg/locspec"
2020-02-15 19:52:53 +00:00
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/service"
"github.com/go-delve/delve/service/api"
"github.com/go-delve/delve/service/debugger"
2021-05-19 17:29:05 +00:00
"github.com/go-delve/delve/service/internal/sameuser"
2020-02-26 05:00:54 +00:00
"github.com/google/go-dap"
2020-02-15 19:52:53 +00:00
"github.com/sirupsen/logrus"
)
// Server implements a DAP server that can accept a single client for
2021-05-04 19:49:52 +00:00
// a single debug session (for now). It does not yet support restarting.
2021-04-21 20:28:15 +00:00
// That means that in addition to explicit shutdown requests,
// program termination and failed or closed client connection
// would also result in stopping this single-use server.
//
// The DAP server operates via the following goroutines:
//
2020-02-15 19:52:53 +00:00
// (1) Main goroutine where the server is created via NewServer(),
2021-04-21 20:28:15 +00:00
// started via Run() and stopped via Stop(). Once the server is
// started, this goroutine blocks until it receives a stop-server
// signal that can come from an OS interrupt (such as Ctrl-C) or
// config.DisconnectChan (passed to NewServer()) as a result of
// client connection failure or closure or a DAP disconnect request.
//
// (2) Run goroutine started from Run() that serves as both
// a listener and a client goroutine. It accepts a client connection,
2021-05-04 19:49:52 +00:00
// reads, decodes and dispatches each request from the client.
// For synchronous requests, it issues commands to the
// underlying debugger and sends back events and responses.
// These requests block while the debuggee is running, so,
// where applicable, the handlers need to check if debugging
// state is running, so there is a need for a halt request or
// a dummy/error response to avoid blocking.
//
// This is the only goroutine that sends a stop-server signal
// via config.DisconnecChan when encountering a client connection
// error or responding to a (synchronous) DAP disconnect request.
// Once stop is triggered, the goroutine exits.
2021-04-21 20:28:15 +00:00
//
// TODO(polina): add another layer of per-client goroutines to support multiple clients
2021-05-04 19:49:52 +00:00
//
// (3) Per-request goroutine is started for each asynchronous request
// that resumes execution. We check if target is running already, so
// there should be no more than one pending asynchronous request at
// a time. This goroutine issues commands to the underlying debugger
// and sends back events and responses. It takes a setup-done channel
// as an argument and temporarily blocks the request loop until setup
// for asynchronous execution is complete and targe is running.
// Once done, it unblocks processing of parallel requests unblocks
// (e.g. disconnecting while the program is running).
//
// These per-request goroutines never send a stop-server signal.
// They block on running debugger commands that are interrupted
// when halt is issued while stopping. At that point these goroutines
// wrap-up and exit.
2020-02-15 19:52:53 +00:00
type Server struct {
// config is all the information necessary to start the debugger and server.
config * service . Config
// listener is used to accept the client connection.
listener net . Listener
2021-05-04 19:49:52 +00:00
// stopTriggered is closed when the server is Stop()-ed.
2021-04-21 20:28:15 +00:00
stopTriggered chan struct { }
2020-02-15 19:52:53 +00:00
// reader is used to read requests from the connection.
reader * bufio . Reader
// log is used for structured logging.
log * logrus . Entry
2020-08-11 15:34:27 +00:00
// stackFrameHandles maps frames of each goroutine to unique ids across all goroutines.
2020-11-12 23:24:31 +00:00
// Reset at every stop.
2020-07-01 18:01:17 +00:00
stackFrameHandles * handlesMap
2020-08-11 15:34:27 +00:00
// variableHandles maps compound variables to unique references within their stack frame.
2020-11-12 23:24:31 +00:00
// Reset at every stop.
2020-08-11 15:34:27 +00:00
// See also comment for convertVariable.
2020-07-07 13:21:18 +00:00
variableHandles * variablesHandlesMap
2020-07-08 17:20:05 +00:00
// args tracks special settings for handling debug session requests.
args launchAttachArgs
2021-05-17 16:25:41 +00:00
// exceptionErr tracks the runtime error that last occurred.
exceptionErr error
2021-05-10 18:34:42 +00:00
// clientCapabilities tracks special settings for handling debug session requests.
clientCapabilities dapClientCapabilites
2021-03-23 19:10:21 +00:00
2021-04-21 20:28:15 +00:00
// mu synchronizes access to objects set on start-up (from run goroutine)
// and stopped on teardown (from main goroutine)
2021-03-23 19:10:21 +00:00
mu sync . Mutex
2021-05-04 19:49:52 +00:00
2021-04-21 20:28:15 +00:00
// conn is the accepted client connection.
conn net . Conn
// debugger is the underlying debugger service.
debugger * debugger . Debugger
2021-04-14 07:22:40 +00:00
// binaryToRemove is the temp compiled binary to be removed on disconnect (if any).
binaryToRemove string
2021-03-23 19:10:21 +00:00
// noDebugProcess is set for the noDebug launch process.
noDebugProcess * exec . Cmd
2021-05-04 19:49:52 +00:00
// sendingMu synchronizes writing to net.Conn
// to ensure that messages do not get interleaved
sendingMu sync . Mutex
2020-07-08 17:20:05 +00:00
}
// launchAttachArgs captures arguments from launch/attach request that
// impact handling of subsequent requests.
type launchAttachArgs struct {
// stopOnEntry is set to automatically stop the debugee after start.
stopOnEntry bool
// stackTraceDepth is the maximum length of the returned list of stack frames.
stackTraceDepth int
2020-09-15 20:14:55 +00:00
// showGlobalVariables indicates if global package variables should be loaded.
showGlobalVariables bool
2021-04-15 23:35:37 +00:00
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
// These must be directory paths.
substitutePathClientToServer [ ] [ 2 ] string
// substitutePathServerToClient indicates rules for converting file paths between debugger and client.
// These must be directory paths.
substitutePathServerToClient [ ] [ 2 ] string
2020-07-08 17:20:05 +00:00
}
// defaultArgs borrows the defaults for the arguments from the original vscode-go adapter.
var defaultArgs = launchAttachArgs {
2021-04-15 23:35:37 +00:00
stopOnEntry : false ,
stackTraceDepth : 50 ,
showGlobalVariables : false ,
substitutePathClientToServer : [ ] [ 2 ] string { } ,
substitutePathServerToClient : [ ] [ 2 ] string { } ,
2020-02-15 19:52:53 +00:00
}
2021-05-10 18:34:42 +00:00
// dapClientCapabilites captures arguments from intitialize request that
// impact handling of subsequent requests.
type dapClientCapabilites struct {
supportsVariableType bool
supportsVariablePaging bool
supportsRunInTerminalRequest bool
supportsMemoryReferences bool
supportsProgressReporting bool
}
2021-06-22 15:14:47 +00:00
// DefaultLoadConfig controls how variables are loaded from the target's memory.
// These limits are conservative to minimize performace overhead for bulk loading.
// With dlv-dap, users do not have a way to adjust these.
// Instead we are focusing in interacive loading with nested reloads, array/map
// paging and context-specific string limits.
2021-02-23 16:29:06 +00:00
var DefaultLoadConfig = proc . LoadConfig {
FollowPointers : true ,
MaxVariableRecurse : 1 ,
2021-06-22 15:14:47 +00:00
// TODO(polina): consider 1024 limit instead:
// - vscode+C appears to use 1024 as the load limit
// - vscode viewlet hover truncates at 1023 characters
MaxStringLen : 512 ,
MaxArrayValues : 64 ,
MaxStructFields : - 1 ,
2021-02-23 16:29:06 +00:00
}
2021-06-22 15:14:47 +00:00
const (
// When a user examines a single string, we can relax the loading limit.
maxSingleStringLen = 4 << 10 // 4096
// Results of a call are single-use and transient. We need to maximize
// what is presented. A common use case of a call injection is to
// stringify complex data conveniently.
maxStringLenInCallRetVars = 1 << 10 // 1024
)
2020-02-15 19:52:53 +00:00
// NewServer creates a new DAP Server. It takes an opened Listener
2021-04-21 20:28:15 +00:00
// via config and assumes its ownership. config.DisconnectChan has to be set;
// it will be closed by the server when the client fails to connect,
// disconnects or requests shutdown. Once config.DisconnectChan is closed,
// Server.Stop() must be called to shutdown this single-user server.
2020-02-15 19:52:53 +00:00
func NewServer ( config * service . Config ) * Server {
logger := logflags . DAPLogger ( )
logflags . WriteDAPListeningMessage ( config . Listener . Addr ( ) . String ( ) )
2020-03-09 17:14:34 +00:00
logger . Debug ( "DAP server pid = " , os . Getpid ( ) )
2020-02-15 19:52:53 +00:00
return & Server {
2020-07-01 18:01:17 +00:00
config : config ,
listener : config . Listener ,
2021-04-21 20:28:15 +00:00
stopTriggered : make ( chan struct { } ) ,
2020-07-01 18:01:17 +00:00
log : logger ,
stackFrameHandles : newHandlesMap ( ) ,
2020-07-07 13:21:18 +00:00
variableHandles : newVariablesHandlesMap ( ) ,
2020-07-08 17:20:05 +00:00
args : defaultArgs ,
2021-05-17 16:25:41 +00:00
exceptionErr : nil ,
2020-02-15 19:52:53 +00:00
}
}
2020-12-28 17:14:15 +00:00
// If user-specified options are provided via Launch/AttachRequest,
// we override the defaults for optional args.
2021-04-15 23:35:37 +00:00
func ( s * Server ) setLaunchAttachArgs ( request dap . LaunchAttachRequest ) error {
2020-12-28 17:14:15 +00:00
stop , ok := request . GetArguments ( ) [ "stopOnEntry" ] . ( bool )
if ok {
s . args . stopOnEntry = stop
}
depth , ok := request . GetArguments ( ) [ "stackTraceDepth" ] . ( float64 )
if ok && depth > 0 {
s . args . stackTraceDepth = int ( depth )
}
globals , ok := request . GetArguments ( ) [ "showGlobalVariables" ] . ( bool )
if ok {
s . args . showGlobalVariables = globals
}
2021-04-15 23:35:37 +00:00
paths , ok := request . GetArguments ( ) [ "substitutePath" ]
if ok {
typeMismatchError := fmt . Errorf ( "'substitutePath' attribute '%v' in debug configuration is not a []{'from': string, 'to': string}" , paths )
pathsParsed , ok := paths . ( [ ] interface { } )
if ! ok {
return typeMismatchError
}
clientToServer := make ( [ ] [ 2 ] string , 0 , len ( pathsParsed ) )
serverToClient := make ( [ ] [ 2 ] string , 0 , len ( pathsParsed ) )
for _ , arg := range pathsParsed {
pathMapping , ok := arg . ( map [ string ] interface { } )
if ! ok {
return typeMismatchError
}
from , ok := pathMapping [ "from" ] . ( string )
if ! ok {
return typeMismatchError
}
to , ok := pathMapping [ "to" ] . ( string )
if ! ok {
return typeMismatchError
}
clientToServer = append ( clientToServer , [ 2 ] string { from , to } )
serverToClient = append ( serverToClient , [ 2 ] string { to , from } )
}
s . args . substitutePathClientToServer = clientToServer
s . args . substitutePathServerToClient = serverToClient
}
return nil
2020-12-28 17:14:15 +00:00
}
2020-02-27 04:45:48 +00:00
// Stop stops the DAP debugger service, closes the listener and the client
// connection. It shuts down the underlying debugger and kills the target
2021-04-21 20:28:15 +00:00
// process if it was launched by it or stops the noDebug process.
// This method mustn't be called more than once.
2020-02-15 19:52:53 +00:00
func ( s * Server ) Stop ( ) {
2021-04-12 21:50:15 +00:00
s . log . Debug ( "DAP server stopping..." )
2021-04-21 20:28:15 +00:00
close ( s . stopTriggered )
2021-03-15 16:36:46 +00:00
_ = s . listener . Close ( )
2021-04-21 20:28:15 +00:00
s . mu . Lock ( )
defer s . mu . Unlock ( )
2020-02-15 19:52:53 +00:00
if s . conn != nil {
// Unless Stop() was called after serveDAPCodec()
// returned, this will result in closed connection error
// on next read, breaking out of the read loop and
// allowing the run goroutine to exit.
2021-03-15 16:36:46 +00:00
_ = s . conn . Close ( )
2020-02-15 19:52:53 +00:00
}
2021-05-04 19:49:52 +00:00
2020-02-15 19:52:53 +00:00
if s . debugger != nil {
2021-04-21 20:28:15 +00:00
killProcess := s . config . Debugger . AttachPid == 0
s . stopDebugSession ( killProcess )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
} else {
s . stopNoDebugProcess ( )
2020-02-15 19:52:53 +00:00
}
2021-04-14 07:22:40 +00:00
// The binary is no longer in use by the debugger. It is safe to remove it.
if s . binaryToRemove != "" {
gobuild . Remove ( s . binaryToRemove )
s . binaryToRemove = ""
}
2021-04-12 21:50:15 +00:00
s . log . Debug ( "DAP server stopped" )
2020-02-15 19:52:53 +00:00
}
2021-04-21 20:28:15 +00:00
// triggerServerStop closes config.DisconnectChan if not nil, which
// signals that client sent a disconnect request or there was connection
// failure or closure. Since the server currently services only one
// client, this is used as a signal to stop the entire server.
// The function safeguards agaist closing the channel more
2020-02-15 19:52:53 +00:00
// than once and can be called multiple times. It is not thread-safe
// and is currently only called from the run goroutine.
2021-04-21 20:28:15 +00:00
func ( s * Server ) triggerServerStop ( ) {
2020-02-27 04:45:48 +00:00
// Avoid accidentally closing the channel twice and causing a panic, when
// this function is called more than once. For example, we could have the
// following sequence of events:
// -- run goroutine: calls onDisconnectRequest()
2021-04-21 20:28:15 +00:00
// -- run goroutine: calls triggerServerStop()
2020-02-27 04:45:48 +00:00
// -- main goroutine: calls Stop()
2021-04-21 20:28:15 +00:00
// -- main goroutine: Stop() closes client connection (or client closed it)
2020-02-27 04:45:48 +00:00
// -- run goroutine: serveDAPCodec() gets "closed network connection"
2021-04-21 20:28:15 +00:00
// -- run goroutine: serveDAPCodec() returns and calls triggerServerStop()
2020-02-15 19:52:53 +00:00
if s . config . DisconnectChan != nil {
close ( s . config . DisconnectChan )
s . config . DisconnectChan = nil
}
2021-04-14 07:22:40 +00:00
// There should be no logic here after the stop-server
// signal that might cause everything to shutdown before this
// logic gets executed.
2020-02-15 19:52:53 +00:00
}
// Run launches a new goroutine where it accepts a client connection
// and starts processing requests from it. Use Stop() to close connection.
// The server does not support multiple clients, serially or in parallel.
// The server should be restarted for every new debug session.
// The debugger won't be started until launch/attach request is received.
// TODO(polina): allow new client connections for new debug sessions,
// so the editor needs to launch delve only once?
func ( s * Server ) Run ( ) {
go func ( ) {
2021-04-21 20:28:15 +00:00
conn , err := s . listener . Accept ( ) // listener is closed in Stop()
2020-02-15 19:52:53 +00:00
if err != nil {
2020-02-27 04:45:48 +00:00
select {
2021-04-21 20:28:15 +00:00
case <- s . stopTriggered :
2020-02-27 04:45:48 +00:00
default :
s . log . Errorf ( "Error accepting client connection: %s\n" , err )
2021-04-21 20:28:15 +00:00
s . triggerServerStop ( )
2020-02-27 04:45:48 +00:00
}
2020-02-15 19:52:53 +00:00
return
}
2021-05-19 17:29:05 +00:00
if s . config . CheckLocalConnUser {
if ! sameuser . CanAccept ( s . listener . Addr ( ) , conn . RemoteAddr ( ) ) {
s . log . Error ( "Error accepting client connection: Only connections from the same user that started this instance of Delve are allowed to connect. See --only-same-user." )
s . triggerServerStop ( )
return
}
}
2021-04-21 20:28:15 +00:00
s . mu . Lock ( )
s . conn = conn // closed in Stop()
s . mu . Unlock ( )
2020-02-15 19:52:53 +00:00
s . serveDAPCodec ( )
} ( )
}
// serveDAPCodec reads and decodes requests from the client
// until it encounters an error or EOF, when it sends
2021-04-21 20:28:15 +00:00
// a disconnect signal and returns.
2020-02-15 19:52:53 +00:00
func ( s * Server ) serveDAPCodec ( ) {
s . reader = bufio . NewReader ( s . conn )
for {
request , err := dap . ReadProtocolMessage ( s . reader )
// TODO(polina): Differentiate between errors and handle them
// gracefully. For example,
// -- "Request command 'foo' is not supported" means we
// potentially got some new DAP request that we do not yet have
// decoding support for, so we can respond with an ErrorResponse.
// TODO(polina): to support this add Seq to
// dap.DecodeProtocolMessageFieldError.
if err != nil {
2020-02-27 04:45:48 +00:00
select {
2021-04-21 20:28:15 +00:00
case <- s . stopTriggered :
2020-02-27 04:45:48 +00:00
default :
2021-04-21 20:28:15 +00:00
if err != io . EOF {
s . log . Error ( "DAP error: " , err )
}
s . triggerServerStop ( )
2020-02-15 19:52:53 +00:00
}
return
}
s . handleRequest ( request )
}
}
2021-05-04 19:49:52 +00:00
// In case a handler panics, we catch the panic to avoid crashing both
// the server and the target. We send an error response back, but
// in case its a dup and ignored by the client, we also log the error.
func ( s * Server ) recoverPanic ( request dap . Message ) {
if ierr := recover ( ) ; ierr != nil {
s . log . Errorf ( "recovered panic: %s\n%s\n" , ierr , debug . Stack ( ) )
s . sendInternalErrorResponse ( request . GetSeq ( ) , fmt . Sprintf ( "%v" , ierr ) )
}
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) handleRequest ( request dap . Message ) {
2021-05-04 19:49:52 +00:00
defer s . recoverPanic ( request )
2020-02-26 05:00:54 +00:00
2020-02-15 19:52:53 +00:00
jsonmsg , _ := json . Marshal ( request )
s . log . Debug ( "[<- from client]" , string ( jsonmsg ) )
2021-05-04 19:49:52 +00:00
if _ , ok := request . ( dap . RequestMessage ) ; ! ok {
s . sendInternalErrorResponse ( request . GetSeq ( ) , fmt . Sprintf ( "Unable to process non-request %#v\n" , request ) )
return
}
2021-05-17 17:37:15 +00:00
// These requests, can be handled regardless of whether the targret is running
2020-02-15 19:52:53 +00:00
switch request := request . ( type ) {
case * dap . DisconnectRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onDisconnectRequest ( request )
2021-05-04 19:49:52 +00:00
return
case * dap . PauseRequest :
// Required
s . onPauseRequest ( request )
return
2020-02-15 19:52:53 +00:00
case * dap . TerminateRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsTerminateRequest‘ )
// TODO: implement this request in V1
s . onTerminateRequest ( request )
2021-05-04 19:49:52 +00:00
return
2020-02-15 19:52:53 +00:00
case * dap . RestartRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsRestartRequest’ )
// TODO: implement this request in V1
s . onRestartRequest ( request )
2021-05-04 19:49:52 +00:00
return
}
// Most requests cannot be processed while the debuggee is running.
// We have a couple of options for handling these without blocking
// the request loop indefinitely when we are in running state.
// --1-- Return a dummy response or an error right away.
2021-05-17 16:17:00 +00:00
// --2-- Halt execution, process the request, maybe resume execution.
2021-05-04 19:49:52 +00:00
// --3-- Handle such requests asynchronously and let them block until
// the process stops or terminates (e.g. using a channel and a single
// goroutine to preserve the order). This might not be appropriate
// for requests such as continue or step because they would skip
// the stop, resuming execution right away. Other requests
// might not be relevant anymore when the stop is finally reached, and
// state changed from the previous snapshot. The user might want to
// resume execution before the backlog of buffered requests is cleared,
// so we would have to either cancel them or delay processing until
// the next stop. In addition, the editor itself might block waiting
// for these requests to return. We are not aware of any requests
// that would benefit from this approach at this time.
if s . debugger != nil && s . debugger . IsRunning ( ) {
switch request := request . ( type ) {
case * dap . ThreadsRequest :
// On start-up, the client requests the baseline of currently existing threads
// right away as there are a number of DAP requests that require a thread id
// (pause, continue, stacktrace, etc). This can happen after the program
// continues on entry, preventing the client from handling any pause requests
// from the user. We remedy this by sending back a placeholder thread id
// for the current goroutine.
response := & dap . ThreadsResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . ThreadsResponseBody { Threads : [ ] dap . Thread { { Id : - 1 , Name : "Current" } } } ,
}
s . send ( response )
2021-05-17 16:17:00 +00:00
case * dap . SetBreakpointsRequest :
s . log . Debug ( "halting execution to set breakpoints" )
_ , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . Halt } , nil )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , err . Error ( ) )
return
}
s . onSetBreakpointsRequest ( request )
// TODO(polina): consider resuming execution here automatically after suppressing
// a stop event when an operation in doRunCommand returns. In case that operation
// was already stopping for a different reason, we would need to examine the state
// that is returned to determine if this halt was the cause of the stop or not.
// We should stop with an event and not resume if one of the following is true:
// - StopReason is anything but manual
// - Any thread has a breakpoint or CallReturn set
// - NextInProgress is false and the last command sent by the user was: next,
// step, stepOut, reverseNext, reverseStep or reverseStepOut
// Otherwise, we can skip the stop event and resume the temporarily
// interrupted process execution with api.DirectionCongruentContinue.
// For this to apply in cases other than api.Continue, we would also need to
// introduce a new version of halt that skips ClearInternalBreakpoints
// in proc.(*Target).Continue, leaving NextInProgress as true.
2021-05-20 17:00:51 +00:00
case * dap . SetFunctionBreakpointsRequest :
s . log . Debug ( "halting execution to set breakpoints" )
_ , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . Halt } , nil )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , err . Error ( ) )
return
}
s . onSetFunctionBreakpointsRequest ( request )
2021-05-04 19:49:52 +00:00
default :
r := request . ( dap . RequestMessage ) . GetRequest ( )
s . sendErrorResponse ( * r , DebuggeeIsRunning , fmt . Sprintf ( "Unable to process `%s`" , r . Command ) , "debuggee is running" )
}
return
}
// Requests below can only be handled while target is stopped.
// Some of them are blocking and will be handled synchronously
// on this goroutine while non-blocking requests will be dispatched
// to another goroutine. Please note that because of the running
// check above, there should be no more than one pending asynchronous
// request at a time.
// Non-blocking request handlers will signal when they are ready
// setting up for async execution, so more requests can be processed.
resumeRequestLoop := make ( chan struct { } )
switch request := request . ( type ) {
//--- Asynchronous requests ---
2020-02-15 19:52:53 +00:00
case * dap . ConfigurationDoneRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsConfigurationDoneRequest’ )
2021-05-04 19:49:52 +00:00
go func ( ) {
defer s . recoverPanic ( request )
s . onConfigurationDoneRequest ( request , resumeRequestLoop )
} ( )
<- resumeRequestLoop
2020-02-15 19:52:53 +00:00
case * dap . ContinueRequest :
2020-05-01 19:24:44 +00:00
// Required
2021-05-04 19:49:52 +00:00
go func ( ) {
defer s . recoverPanic ( request )
s . onContinueRequest ( request , resumeRequestLoop )
} ( )
<- resumeRequestLoop
2020-02-15 19:52:53 +00:00
case * dap . NextRequest :
2020-05-01 19:24:44 +00:00
// Required
2021-05-04 19:49:52 +00:00
go func ( ) {
defer s . recoverPanic ( request )
s . onNextRequest ( request , resumeRequestLoop )
} ( )
<- resumeRequestLoop
2020-02-15 19:52:53 +00:00
case * dap . StepInRequest :
2020-05-01 19:24:44 +00:00
// Required
2021-05-04 19:49:52 +00:00
go func ( ) {
defer s . recoverPanic ( request )
s . onStepInRequest ( request , resumeRequestLoop )
} ( )
<- resumeRequestLoop
2020-02-15 19:52:53 +00:00
case * dap . StepOutRequest :
2020-05-01 19:24:44 +00:00
// Required
2021-05-04 19:49:52 +00:00
go func ( ) {
defer s . recoverPanic ( request )
s . onStepOutRequest ( request , resumeRequestLoop )
} ( )
<- resumeRequestLoop
2020-02-15 19:52:53 +00:00
case * dap . StepBackRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsStepBack’ )
// TODO: implement this request in V1
s . onStepBackRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . ReverseContinueRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsStepBack’ )
// TODO: implement this request in V1
s . onReverseContinueRequest ( request )
2021-05-04 19:49:52 +00:00
//--- Synchronous requests ---
case * dap . InitializeRequest :
2020-05-01 19:24:44 +00:00
// Required
2021-05-04 19:49:52 +00:00
s . onInitializeRequest ( request )
case * dap . LaunchRequest :
// Required
s . onLaunchRequest ( request )
case * dap . AttachRequest :
// Required
s . onAttachRequest ( request )
case * dap . SetBreakpointsRequest :
// Required
s . onSetBreakpointsRequest ( request )
case * dap . SetFunctionBreakpointsRequest :
// Optional (capability ‘ supportsFunctionBreakpoints’ )
s . onSetFunctionBreakpointsRequest ( request )
case * dap . SetExceptionBreakpointsRequest :
// Optional (capability ‘ exceptionBreakpointFilters’ )
s . onSetExceptionBreakpointsRequest ( request )
case * dap . ThreadsRequest :
// Required
s . onThreadsRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . StackTraceRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onStackTraceRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . ScopesRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onScopesRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . VariablesRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onVariablesRequest ( request )
2021-05-04 19:49:52 +00:00
case * dap . EvaluateRequest :
// Required
s . onEvaluateRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . SetVariableRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsSetVariable’ )
// Supported by vscode-go
// TODO: implement this request in V0
s . onSetVariableRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . SetExpressionRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsSetExpression’ )
// TODO: implement this request in V1
s . onSetExpressionRequest ( request )
2021-05-04 19:49:52 +00:00
case * dap . LoadedSourcesRequest :
// Optional (capability ‘ supportsLoadedSourcesRequest’ )
// TODO: implement this request in V1
s . onLoadedSourcesRequest ( request )
case * dap . ReadMemoryRequest :
// Optional (capability ‘ supportsReadMemoryRequest‘ )
// TODO: implement this request in V1
s . onReadMemoryRequest ( request )
case * dap . DisassembleRequest :
// Optional (capability ‘ supportsDisassembleRequest’ )
// TODO: implement this request in V1
s . onDisassembleRequest ( request )
case * dap . CancelRequest :
// Optional (capability ‘ supportsCancelRequest’ )
// TODO: does this request make sense for delve?
s . onCancelRequest ( request )
2021-05-17 16:25:41 +00:00
case * dap . ExceptionInfoRequest :
// Optional (capability ‘ supportsExceptionInfoRequest’ )
s . onExceptionInfoRequest ( request )
2021-05-04 19:49:52 +00:00
//--- Requests that we do not plan to support ---
case * dap . RestartFrameRequest :
// Optional (capability ’ supportsRestartFrame’ )
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . GotoRequest :
// Optional (capability ‘ supportsGotoTargetsRequest’ )
s . sendUnsupportedErrorResponse ( request . Request )
2020-02-15 19:52:53 +00:00
case * dap . SourceRequest :
2020-05-01 19:24:44 +00:00
// Required
// This does not make sense in the context of Go as
// the source cannot be a string eval'ed at runtime.
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . TerminateThreadsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsTerminateThreadsRequest’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . StepInTargetsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsStepInTargetsRequest’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . GotoTargetsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsGotoTargetsRequest’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . CompletionsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsCompletionsRequest’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . DataBreakpointInfoRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsDataBreakpoints’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . SetDataBreakpointsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsDataBreakpoints’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . BreakpointLocationsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsBreakpointLocationsRequest’ )
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . ModulesRequest :
// Optional (capability ‘ supportsModulesRequest’ )
// TODO: does this request make sense for delve?
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
default :
// This is a DAP message that go-dap has a struct for, so
// decoding succeeded, but this function does not know how
2020-02-26 05:00:54 +00:00
// to handle.
s . sendInternalErrorResponse ( request . GetSeq ( ) , fmt . Sprintf ( "Unable to process %#v\n" , request ) )
2020-02-15 19:52:53 +00:00
}
}
func ( s * Server ) send ( message dap . Message ) {
jsonmsg , _ := json . Marshal ( message )
s . log . Debug ( "[-> to client]" , string ( jsonmsg ) )
2021-05-04 19:49:52 +00:00
// TODO(polina): consider using a channel for all the sends and to have a dedicated
// goroutine that reads from that channel and sends over the connection.
// This will avoid blocking on slow network sends.
s . sendingMu . Lock ( )
defer s . sendingMu . Unlock ( )
2021-03-15 16:36:46 +00:00
_ = dap . WriteProtocolMessage ( s . conn , message )
2020-02-15 19:52:53 +00:00
}
2021-04-12 21:50:15 +00:00
func ( s * Server ) logToConsole ( msg string ) {
s . send ( & dap . OutputEvent {
Event : * newEvent ( "output" ) ,
Body : dap . OutputEventBody {
Output : msg + "\n" ,
Category : "console" ,
} } )
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) onInitializeRequest ( request * dap . InitializeRequest ) {
2021-05-10 18:34:42 +00:00
s . setClientCapabilities ( request . Arguments )
2021-05-06 07:56:29 +00:00
if request . Arguments . PathFormat != "path" {
s . sendErrorResponse ( request . Request , FailedToInitialize , "Failed to initialize" ,
fmt . Sprintf ( "Unsupported 'pathFormat' value '%s'." , request . Arguments . PathFormat ) )
return
}
if ! request . Arguments . LinesStartAt1 {
s . sendErrorResponse ( request . Request , FailedToInitialize , "Failed to initialize" ,
"Only 1-based line numbers are supported." )
return
}
if ! request . Arguments . ColumnsStartAt1 {
s . sendErrorResponse ( request . Request , FailedToInitialize , "Failed to initialize" ,
"Only 1-based column numbers are supported." )
return
}
2020-02-15 19:52:53 +00:00
// TODO(polina): Respond with an error if debug session is in progress?
response := & dap . InitializeResponse { Response : * newResponse ( request . Request ) }
response . Body . SupportsConfigurationDoneRequest = true
2020-10-02 16:18:33 +00:00
response . Body . SupportsConditionalBreakpoints = true
2020-11-30 17:43:09 +00:00
response . Body . SupportsDelayedStackTraceLoading = true
2020-12-28 17:14:15 +00:00
response . Body . SupportTerminateDebuggee = true
2021-05-18 17:25:16 +00:00
response . Body . SupportsFunctionBreakpoints = true
2021-05-17 16:25:41 +00:00
response . Body . SupportsExceptionInfoRequest = true
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
response . Body . SupportsSetVariable = true
2021-05-19 18:17:36 +00:00
response . Body . SupportsEvaluateForHovers = true
2021-06-04 07:27:57 +00:00
response . Body . SupportsClipboardContext = true
2020-05-01 19:24:44 +00:00
// TODO(polina): support these requests in addition to vscode-go feature parity
response . Body . SupportsTerminateRequest = false
response . Body . SupportsRestartRequest = false
response . Body . SupportsStepBack = false
response . Body . SupportsSetExpression = false
response . Body . SupportsLoadedSourcesRequest = false
response . Body . SupportsReadMemoryRequest = false
response . Body . SupportsDisassembleRequest = false
response . Body . SupportsCancelRequest = false
2020-02-15 19:52:53 +00:00
s . send ( response )
}
2021-05-10 18:34:42 +00:00
func ( s * Server ) setClientCapabilities ( args dap . InitializeRequestArguments ) {
s . clientCapabilities . supportsMemoryReferences = args . SupportsMemoryReferences
s . clientCapabilities . supportsProgressReporting = args . SupportsProgressReporting
s . clientCapabilities . supportsRunInTerminalRequest = args . SupportsRunInTerminalRequest
s . clientCapabilities . supportsVariablePaging = args . SupportsVariablePaging
s . clientCapabilities . supportsVariableType = args . SupportsVariableType
}
2021-03-24 18:02:22 +00:00
// Default output file pathname for the compiled binary in debug or test modes,
// relative to the current working directory of the server.
const defaultDebugBinary string = "./__debug_bin"
2020-03-04 17:22:51 +00:00
2021-03-23 19:10:21 +00:00
func cleanExeName ( name string ) string {
if runtime . GOOS == "windows" && filepath . Ext ( name ) != ".exe" {
return name + ".exe"
}
return name
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) onLaunchRequest ( request * dap . LaunchRequest ) {
2021-03-05 09:07:23 +00:00
// Validate launch request mode
mode , ok := request . Arguments [ "mode" ]
if ! ok || mode == "" {
mode = "debug"
}
if ! isValidLaunchMode ( mode ) {
s . sendErrorResponse ( request . Request ,
FailedToLaunch , "Failed to launch" ,
fmt . Sprintf ( "Unsupported 'mode' value %q in debug configuration." , mode ) )
return
}
2020-03-04 17:22:51 +00:00
2021-03-05 09:07:23 +00:00
// TODO(polina): Respond with an error if debug session is in progress?
2020-03-04 17:22:51 +00:00
program , ok := request . Arguments [ "program" ] . ( string )
2020-02-15 19:52:53 +00:00
if ! ok || program == "" {
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2020-02-15 19:52:53 +00:00
"The program attribute is missing in debug configuration." )
return
}
2020-03-04 17:22:51 +00:00
if mode == "debug" || mode == "test" {
output , ok := request . Arguments [ "output" ] . ( string )
if ! ok || output == "" {
2021-05-27 21:23:32 +00:00
output = defaultDebugBinary
2020-03-04 17:22:51 +00:00
}
2021-05-27 21:23:32 +00:00
output = cleanExeName ( output )
2021-03-24 18:02:22 +00:00
debugbinary , err := filepath . Abs ( output )
2020-03-04 17:22:51 +00:00
if err != nil {
s . sendInternalErrorResponse ( request . Seq , err . Error ( ) )
return
}
2020-05-28 21:01:51 +00:00
buildFlags := ""
buildFlagsArg , ok := request . Arguments [ "buildFlags" ]
if ok {
buildFlags , ok = buildFlagsArg . ( string )
if ! ok {
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2020-05-28 21:01:51 +00:00
fmt . Sprintf ( "'buildFlags' attribute '%v' in debug configuration is not a string." , buildFlagsArg ) )
return
}
2020-03-04 17:22:51 +00:00
}
2021-03-24 18:02:22 +00:00
s . log . Debugf ( "building binary at %s" , debugbinary )
2021-05-17 16:13:25 +00:00
var cmd string
2021-04-26 17:31:59 +00:00
var out [ ] byte
2020-03-04 17:22:51 +00:00
switch mode {
case "debug" :
2021-05-17 16:13:25 +00:00
cmd , out , err = gobuild . GoBuildCombinedOutput ( debugbinary , [ ] string { program } , buildFlags )
2020-03-04 17:22:51 +00:00
case "test" :
2021-05-17 16:13:25 +00:00
cmd , out , err = gobuild . GoTestBuildCombinedOutput ( debugbinary , [ ] string { program } , buildFlags )
2020-03-04 17:22:51 +00:00
}
if err != nil {
2021-05-17 16:13:25 +00:00
s . send ( & dap . OutputEvent {
Event : * newEvent ( "output" ) ,
Body : dap . OutputEventBody {
Output : fmt . Sprintf ( "Build Error: %s\n%s (%s)\n" , cmd , strings . TrimSpace ( string ( out ) ) , err . Error ( ) ) ,
Category : "stderr" ,
} } )
2020-03-04 17:22:51 +00:00
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2021-05-17 16:13:25 +00:00
"Build error: Check the debug console for details." )
2020-03-04 17:22:51 +00:00
return
}
2021-03-24 18:02:22 +00:00
program = debugbinary
2021-04-14 07:22:40 +00:00
s . mu . Lock ( )
2021-03-24 18:02:22 +00:00
s . binaryToRemove = debugbinary
2021-04-14 07:22:40 +00:00
s . mu . Unlock ( )
2020-03-04 17:22:51 +00:00
}
2021-04-15 23:35:37 +00:00
err := s . setLaunchAttachArgs ( request )
if err != nil {
s . sendErrorResponse ( request . Request ,
FailedToLaunch , "Failed to launch" ,
err . Error ( ) )
return
}
2020-03-04 17:22:51 +00:00
2020-05-11 16:52:26 +00:00
var targetArgs [ ] string
args , ok := request . Arguments [ "args" ]
if ok {
argsParsed , ok := args . ( [ ] interface { } )
if ! ok {
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2020-05-11 16:52:26 +00:00
fmt . Sprintf ( "'args' attribute '%v' in debug configuration is not an array." , args ) )
return
}
for _ , arg := range argsParsed {
argParsed , ok := arg . ( string )
if ! ok {
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2020-05-11 16:52:26 +00:00
fmt . Sprintf ( "value '%v' in 'args' attribute in debug configuration is not a string." , arg ) )
return
}
targetArgs = append ( targetArgs , argParsed )
}
}
s . config . ProcessArgs = append ( [ ] string { program } , targetArgs ... )
2020-04-13 18:07:15 +00:00
s . config . Debugger . WorkingDir = filepath . Dir ( program )
2020-03-04 17:22:51 +00:00
2021-03-23 03:06:09 +00:00
// Set the WorkingDir for this program to the one specified in the request arguments.
2021-04-20 07:40:34 +00:00
wd , ok := request . Arguments [ "cwd" ]
2021-03-23 03:06:09 +00:00
if ok {
wdParsed , ok := wd . ( string )
if ! ok {
s . sendErrorResponse ( request . Request ,
FailedToLaunch , "Failed to launch" ,
2021-04-20 07:40:34 +00:00
fmt . Sprintf ( "'cwd' attribute '%v' in debug configuration is not a string." , wd ) )
2021-03-23 03:06:09 +00:00
return
}
s . config . Debugger . WorkingDir = wdParsed
}
2021-04-23 13:17:38 +00:00
s . log . Debugf ( "running program in %s\n" , s . config . Debugger . WorkingDir )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
if noDebug , ok := request . Arguments [ "noDebug" ] . ( bool ) ; ok && noDebug {
2021-04-21 20:28:15 +00:00
s . mu . Lock ( )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
cmd , err := s . startNoDebugProcess ( program , targetArgs , s . config . Debugger . WorkingDir )
2021-04-21 20:28:15 +00:00
s . mu . Unlock ( )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
if err != nil {
s . sendErrorResponse ( request . Request , FailedToLaunch , "Failed to launch" , err . Error ( ) )
2021-03-23 19:10:21 +00:00
return
}
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
// Skip 'initialized' event, which will prevent the client from sending
// debug-related requests.
s . send ( & dap . LaunchResponse { Response : * newResponse ( request . Request ) } )
// Then, block until the program terminates or is stopped.
if err := cmd . Wait ( ) ; err != nil {
2021-04-12 21:50:15 +00:00
s . log . Debugf ( "program exited with error: %v" , err )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
}
stopped := false
s . mu . Lock ( )
stopped = s . noDebugProcess == nil // if it was stopped, this should be nil.
s . noDebugProcess = nil
s . mu . Unlock ( )
if ! stopped {
2021-04-12 21:50:15 +00:00
s . logToConsole ( proc . ErrProcessExited { Pid : cmd . ProcessState . Pid ( ) , Status : cmd . ProcessState . ExitCode ( ) } . Error ( ) )
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
s . send ( & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) } )
}
return
2021-03-23 19:10:21 +00:00
}
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
2021-04-21 20:28:15 +00:00
func ( ) {
s . mu . Lock ( )
defer s . mu . Unlock ( ) // Make sure to unlock in case of panic that will become internal error
s . debugger , err = debugger . New ( & s . config . Debugger , s . config . ProcessArgs )
} ( )
if err != nil {
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
s . sendErrorResponse ( request . Request , FailedToLaunch , "Failed to launch" , err . Error ( ) )
2020-02-15 19:52:53 +00:00
return
}
2021-04-21 20:28:15 +00:00
// Notify the client that the debugger is ready to start accepting
// configuration requests for setting breakpoints, etc. The client
// will end the configuration sequence with 'configurationDone'.
2020-02-15 19:52:53 +00:00
s . send ( & dap . InitializedEvent { Event : * newEvent ( "initialized" ) } )
s . send ( & dap . LaunchResponse { Response : * newResponse ( request . Request ) } )
}
2021-04-21 20:28:15 +00:00
// startNoDebugProcess is called from onLaunchRequest (run goroutine) and
// requires holding mu lock.
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
func ( s * Server ) startNoDebugProcess ( program string , targetArgs [ ] string , wd string ) ( * exec . Cmd , error ) {
2021-03-23 19:10:21 +00:00
if s . noDebugProcess != nil {
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
return nil , fmt . Errorf ( "another launch request is in progress" )
2021-03-23 19:10:21 +00:00
}
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
cmd := exec . Command ( program , targetArgs ... )
cmd . Stdout , cmd . Stderr , cmd . Stdin , cmd . Dir = os . Stdout , os . Stderr , os . Stdin , wd
2021-03-23 19:10:21 +00:00
if err := cmd . Start ( ) ; err != nil {
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
return nil , err
2021-03-23 19:10:21 +00:00
}
s . noDebugProcess = cmd
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
return cmd , nil
2021-03-23 19:10:21 +00:00
}
2021-04-21 20:28:15 +00:00
// stopNoDebugProcess is called from Stop (main goroutine) and
// onDisconnectRequest (run goroutine) and requires holding mu lock.
2021-03-23 19:10:21 +00:00
func ( s * Server ) stopNoDebugProcess ( ) {
2021-04-21 20:28:15 +00:00
if s . noDebugProcess == nil {
2021-04-12 21:50:15 +00:00
// We already handled termination or there was never a process
return
}
2021-04-21 20:28:15 +00:00
if s . noDebugProcess . ProcessState . Exited ( ) {
s . logToConsole ( proc . ErrProcessExited { Pid : s . noDebugProcess . ProcessState . Pid ( ) , Status : s . noDebugProcess . ProcessState . ExitCode ( ) } . Error ( ) )
2021-04-12 21:50:15 +00:00
} else {
// TODO(hyangah): gracefully terminate the process and its children processes.
2021-04-21 20:28:15 +00:00
s . logToConsole ( fmt . Sprintf ( "Terminating process %d" , s . noDebugProcess . Process . Pid ) )
s . noDebugProcess . Process . Kill ( ) // Don't check error. Process killing and self-termination may race.
2021-03-23 19:10:21 +00:00
}
2021-04-21 20:28:15 +00:00
s . noDebugProcess = nil
2021-03-23 19:10:21 +00:00
}
2021-03-05 09:07:23 +00:00
// TODO(polina): support "remote" mode
func isValidLaunchMode ( launchMode interface { } ) bool {
switch launchMode {
case "exec" , "debug" , "test" :
return true
}
return false
}
2020-02-15 19:52:53 +00:00
// onDisconnectRequest handles the DisconnectRequest. Per the DAP spec,
// it disconnects the debuggee and signals that the debug adaptor
// (in our case this TCP server) can be terminated.
func ( s * Server ) onDisconnectRequest ( request * dap . DisconnectRequest ) {
2021-04-21 20:28:15 +00:00
defer s . triggerServerStop ( )
s . mu . Lock ( )
defer s . mu . Unlock ( )
2021-04-12 21:50:15 +00:00
var err error
2020-02-15 19:52:53 +00:00
if s . debugger != nil {
2021-04-21 20:28:15 +00:00
// We always kill launched programs.
2020-12-28 17:14:15 +00:00
// In case of attach, we leave the program
2021-04-12 21:50:15 +00:00
// running by default, which can be
2020-12-28 17:14:15 +00:00
// overridden by an explicit request to terminate.
2021-04-21 20:28:15 +00:00
killProcess := s . config . Debugger . AttachPid == 0 || request . Arguments . TerminateDebuggee
err = s . stopDebugSession ( killProcess )
2021-03-23 19:10:21 +00:00
} else {
s . stopNoDebugProcess ( )
2020-02-15 19:52:53 +00:00
}
2021-04-21 20:28:15 +00:00
if err != nil {
2021-04-12 21:50:15 +00:00
s . sendErrorResponse ( request . Request , DisconnectError , "Error while disconnecting" , err . Error ( ) )
} else {
s . send ( & dap . DisconnectResponse { Response : * newResponse ( request . Request ) } )
}
2021-04-21 20:28:15 +00:00
}
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
2021-04-21 20:28:15 +00:00
// stopDebugSession is called from Stop (main goroutine) and
// onDisconnectRequest (run goroutine) and requires holding mu lock.
// Returns any detach error other than proc.ErrProcessExited.
func ( s * Server ) stopDebugSession ( killProcess bool ) error {
if s . debugger == nil {
return nil
}
var err error
var exited error
2021-05-04 19:49:52 +00:00
// Halting will stop any debugger command that's pending on another
// per-request goroutine, hence unblocking that goroutine to wrap-up and exit.
// TODO(polina): Per-request goroutine could still not be done when this one is.
// To avoid goroutine leaks, we can use a wait group or have the goroutine listen
// for a stop signal on a dedicated quit channel at suitable points (use context?).
// Additional clean-up might be especially critical when we support multiple clients.
2021-04-21 20:28:15 +00:00
state , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . Halt } , nil )
if err == proc . ErrProcessDetached {
2021-06-10 16:30:31 +00:00
s . log . Debug ( "halt returned error: " , err )
2021-04-21 20:28:15 +00:00
return nil
}
if err != nil {
switch err . ( type ) {
case proc . ErrProcessExited :
exited = err
default :
2021-06-10 16:30:31 +00:00
s . log . Error ( "halt returned error: " , err )
if err . Error ( ) == "no such process" {
exited = err
}
2021-04-21 20:28:15 +00:00
}
} else if state . Exited {
exited = proc . ErrProcessExited { Pid : s . debugger . ProcessPid ( ) , Status : state . ExitStatus }
2021-06-10 16:30:31 +00:00
s . log . Debug ( "halt returned state: " , exited )
2021-04-21 20:28:15 +00:00
}
if exited != nil {
s . logToConsole ( exited . Error ( ) )
s . logToConsole ( "Detaching" )
} else if killProcess {
s . logToConsole ( "Detaching and terminating target process" )
} else {
s . logToConsole ( "Detaching without terminating target processs" )
}
err = s . debugger . Detach ( killProcess )
s . debugger = nil
if err != nil {
switch err . ( type ) {
case proc . ErrProcessExited :
s . log . Debug ( err )
s . logToConsole ( exited . Error ( ) )
err = nil
default :
s . log . Error ( err )
}
}
return err
2020-02-15 19:52:53 +00:00
}
2021-05-04 19:49:52 +00:00
func ( s * Server ) isNoDebug ( ) bool {
s . mu . Lock ( )
defer s . mu . Unlock ( )
return s . noDebugProcess != nil
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) onSetBreakpointsRequest ( request * dap . SetBreakpointsRequest ) {
2021-05-04 19:49:52 +00:00
if s . isNoDebug ( ) {
dap: change how noDebug launch request is served (#2407)
* dap: change how noDebug launch request is served
PR #2400 added support for noDebug launch requests - that was done
by directly starting the target program using os/exec.Cmd and blocking
the launch request indefinitely until the program terminates or
is stopped with a disconnect request (when dlv dap can support it).
Even though the approach seemed to work for user, that adds an extra
control flow and complexity to the codebase.
This change takes a different approach to implement the noDebug
launch feature. Instead of using os/exec.Cmd, this uses the existing
debug launch path, but avoids setting breakpoints. Finally, the program
will start upon receiving onConfigurationDoneRequest and blocks there
until the program terminates. This simplifies the code.
The DAP spec does not explicitly specify what to do about other
requests. It seems like VSCode can issue evaluate requests or other
requests after the configuration done request. Currently they are
blocked because the program is in running state. We can consider checking
s.noDebug and responding with an error in the future if we want/need to.
See the log below for a typical DAP request/response sequence:
2021-03-29T01:42:53-04:00 debug layer=dap building binary at /Users/hakim/projects/s/__debug_bin
2021-03-29T01:42:55-04:00 info layer=debugger launching process with args: [/Users/hakim/projects/s/__debug_bin]
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"initialized"}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":2,"success":true,"command":"launch"}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":3,"type":"request","command":"setBreakpoints","arguments":{"source":{"name":"main.go","path":"/Users/hakim/projects/s/main.go"},"breakpoints":[{"line":9}],"lines":[9]}}
2021-03-29T01:42:55-04:00 error layer=dap Unable to set or clear breakpoints: running in noDebug mode
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":3,"success":false,"command":"setBreakpoints","message":"Unable to set or clear breakpoints","body":{"error":{"id":2002,"format":"Unable to set or clear breakpoints: running in noDebug mode"}}}
2021-03-29T01:42:55-04:00 debug layer=dap [<- from client]{"seq":4,"type":"request","command":"configurationDone","arguments":{}}
2021-03-29T01:42:55-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}
2021-03-29T01:42:55-04:00 debug layer=debugger continuing
Hello
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"event","event":"terminated","body":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":5,"type":"request","command":"threads"}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":5,"success":true,"command":"threads","body":{"threads":null}}
2021-03-29T01:43:00-04:00 debug layer=dap [<- from client]{"seq":6,"type":"request","command":"disconnect","arguments":{}}
2021-03-29T01:43:00-04:00 debug layer=dap [-> to client]{"seq":0,"type":"response","request_seq":6,"success":true,"command":"disconnect"}
2021-03-29T01:43:00-04:00 debug layer=debugger halting
2021-03-29T01:43:00-04:00 error layer=dap Process 27219 has exited with status 0
2021-03-29T01:43:00-04:00 debug layer=debugger detaching
Updates https://github.com/golang/vscode-go/issues/1111
* service/dap: address polina's comments for noDebug logic
Reworked so the noDebug launch request again blocks launch request
handler after responding with the launch response. By skipping
the initialized event, it should prevent well-behaving clients from
sending any further requests. Currently, doesn't matter because
the handler goroutine will be blocked until the program termination
anyway.
Placed mutex back, since I noticed that's the only way to prevent
racing between Stop and the handler goroutine. The synchronization
lotic (in particular, during teardown) should be revisited after
asynchronous request support work is done.
* dap: address more comments
2021-04-05 18:44:02 +00:00
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , "running in noDebug mode" )
return
}
2020-10-02 16:18:33 +00:00
2020-02-15 19:52:53 +00:00
if request . Arguments . Source . Path == "" {
2020-10-02 16:18:33 +00:00
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , "empty file path" )
return
}
2021-04-15 23:35:37 +00:00
clientPath := request . Arguments . Source . Path
serverPath := s . toServerPath ( clientPath )
2020-10-02 16:18:33 +00:00
// According to the spec we should "set multiple breakpoints for a single source
// and clear all previous breakpoints in that source." The simplest way is
2021-05-28 18:21:53 +00:00
// to clear all and then set all. To maintain state (for hit count conditions)
// we want to amend existing breakpoints.
2020-10-02 16:18:33 +00:00
//
// See https://github.com/golang/vscode-go/issues/163 for details.
// If a breakpoint:
// -- exists and not in request => ClearBreakpoint
// -- exists and in request => AmendBreakpoint
// -- doesn't exist and in request => SetBreakpoint
2021-05-28 18:21:53 +00:00
// Get all existing breakpoints that match for this source.
sourceRequestPrefix := fmt . Sprintf ( "sourceBp Path=%q " , request . Arguments . Source . Path )
existingBps := s . getMatchingBreakpoints ( sourceRequestPrefix )
bpAdded := make ( map [ string ] struct { } , len ( existingBps ) )
// Amend existing breakpoints.
breakpoints := make ( [ ] dap . Breakpoint , len ( request . Arguments . Breakpoints ) )
for i , want := range request . Arguments . Breakpoints {
reqString := fmt . Sprintf ( "%s Line=%d Column=%d" , sourceRequestPrefix , want . Line , want . Column )
var err error
got , ok := existingBps [ reqString ]
if ! ok {
// Skip if the breakpoint does not already exist.
// These will be created after deleting existing
// breakpoints to avoid conflicts.
continue
}
if _ , ok := bpAdded [ reqString ] ; ok {
2021-06-23 18:21:19 +00:00
err = fmt . Errorf ( "breakpoint exists at %q, line: %d, column: %d" , request . Arguments . Source . Path , want . Line , want . Column )
2021-05-28 18:21:53 +00:00
} else {
got . Cond = want . Condition
got . HitCond = want . HitCondition
err = s . debugger . AmendBreakpoint ( got )
bpAdded [ reqString ] = struct { } { }
}
updateBreakpointsResponse ( breakpoints , i , err , got , clientPath )
}
// Clear existing breakpoints that were not added.
err := s . clearBreakpoints ( existingBps , bpAdded )
if err != nil {
2021-05-18 17:25:16 +00:00
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , err . Error ( ) )
return
2020-02-15 19:52:53 +00:00
}
2020-10-02 16:18:33 +00:00
2021-05-28 18:21:53 +00:00
for i , want := range request . Arguments . Breakpoints {
reqString := fmt . Sprintf ( "%s Line=%d Column=%d" , sourceRequestPrefix , want . Line , want . Column )
if _ , ok := existingBps [ reqString ] ; ok {
continue
}
var got * api . Breakpoint
var err error
if _ , ok := bpAdded [ reqString ] ; ok {
2021-06-23 18:21:19 +00:00
err = fmt . Errorf ( "breakpoint exists at %q, line: %d, column: %d" , request . Arguments . Source . Path , want . Line , want . Column )
2021-05-28 18:21:53 +00:00
} else {
// Create new breakpoints.
got , err = s . debugger . CreateBreakpoint (
& api . Breakpoint { File : serverPath , Line : want . Line , Cond : want . Condition , HitCond : want . HitCondition , Name : reqString } )
bpAdded [ reqString ] = struct { } { }
}
updateBreakpointsResponse ( breakpoints , i , err , got , clientPath )
}
2020-02-15 19:52:53 +00:00
response := & dap . SetBreakpointsResponse { Response : * newResponse ( request . Request ) }
2021-05-28 18:21:53 +00:00
response . Body . Breakpoints = breakpoints
s . send ( response )
}
func updateBreakpointsResponse ( breakpoints [ ] dap . Breakpoint , i int , err error , got * api . Breakpoint , path string ) {
breakpoints [ i ] . Verified = ( err == nil )
if err != nil {
breakpoints [ i ] . Message = err . Error ( )
} else {
breakpoints [ i ] . Id = got . ID
breakpoints [ i ] . Line = got . Line
breakpoints [ i ] . Source = dap . Source { Name : filepath . Base ( path ) , Path : path }
}
}
// functionBpPrefix is the prefix of bp.Name for every breakpoint bp set
// in this request.
const functionBpPrefix = "functionBreakpoint"
func ( s * Server ) onSetFunctionBreakpointsRequest ( request * dap . SetFunctionBreakpointsRequest ) {
if s . noDebugProcess != nil {
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , "running in noDebug mode" )
return
}
// According to the spec, setFunctionBreakpoints "replaces all existing function
// breakpoints with new function breakpoints." The simplest way is
// to clear all and then set all. To maintain state (for hit count conditions)
// we want to amend existing breakpoints.
//
// See https://github.com/golang/vscode-go/issues/163 for details.
// If a breakpoint:
// -- exists and not in request => ClearBreakpoint
// -- exists and in request => AmendBreakpoint
// -- doesn't exist and in request => SetBreakpoint
// Get all existing function breakpoints.
existingBps := s . getMatchingBreakpoints ( functionBpPrefix )
bpAdded := make ( map [ string ] struct { } , len ( existingBps ) )
for _ , bp := range existingBps {
existingBps [ bp . Name ] = bp
}
// Amend any existing breakpoints.
breakpoints := make ( [ ] dap . Breakpoint , len ( request . Arguments . Breakpoints ) )
2020-10-02 16:18:33 +00:00
for i , want := range request . Arguments . Breakpoints {
2021-05-28 18:21:53 +00:00
reqString := fmt . Sprintf ( "%s Name=%s" , functionBpPrefix , want . Name )
var err error
got , ok := existingBps [ reqString ]
if ! ok {
// Skip if the breakpoint does not already exist.
// These will be created after deleting existing
// breakpoints to avoid conflicts.
continue
}
if _ , ok := bpAdded [ reqString ] ; ok {
2021-06-23 18:21:19 +00:00
err = fmt . Errorf ( "breakpoint exists at function %q" , want . Name )
2020-10-02 16:18:33 +00:00
} else {
2021-05-28 18:21:53 +00:00
got . Cond = want . Condition
got . HitCond = want . HitCondition
err = s . debugger . AmendBreakpoint ( got )
bpAdded [ reqString ] = struct { } { }
}
var clientPath string
if got != nil {
clientPath = s . toClientPath ( got . File )
}
updateBreakpointsResponse ( breakpoints , i , err , got , clientPath )
}
// Clear existing breakpoints that were not added.
err := s . clearBreakpoints ( existingBps , bpAdded )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToSetBreakpoints , "Unable to set or clear breakpoints" , err . Error ( ) )
return
}
// Create new breakpoints.
for i , want := range request . Arguments . Breakpoints {
reqString := fmt . Sprintf ( "%s Name=%s" , functionBpPrefix , want . Name )
if _ , ok := existingBps [ reqString ] ; ok {
// Amend existing breakpoints.
continue
}
// Set the function breakpoint breakpoint
spec , err := locspec . Parse ( want . Name )
if err != nil {
breakpoints [ i ] . Message = err . Error ( )
continue
}
if loc , ok := spec . ( * locspec . NormalLocationSpec ) ; ! ok || loc . FuncBase == nil {
// Other locations do not make sense in the context of function breakpoints.
// Regex locations are likely to resolve to multiple places and offset locations
// are only meaningful at the time the breakpoint was created.
breakpoints [ i ] . Message = fmt . Sprintf ( "breakpoint name %q could not be parsed as a function. name must be in the format 'funcName', 'funcName:line' or 'fileName:line'." , want . Name )
continue
}
if want . Name [ 0 ] == '.' {
breakpoints [ i ] . Message = "breakpoint names that are relative paths are not supported."
continue
}
// Find the location of the function name. CreateBreakpoint requires the name to include the base
// (e.g. main.functionName is supported but not functionName).
// We first find the location of the function, and then set breakpoints for that location.
var locs [ ] api . Location
locs , err = s . debugger . FindLocationSpec ( - 1 , 0 , 0 , want . Name , spec , true , s . args . substitutePathClientToServer )
if err != nil {
breakpoints [ i ] . Message = err . Error ( )
continue
}
if len ( locs ) == 0 {
breakpoints [ i ] . Message = fmt . Sprintf ( "no location found for %q" , want . Name )
continue
}
if len ( locs ) > 0 {
s . log . Debugf ( "multiple locations found for %s" , want . Name )
breakpoints [ i ] . Message = fmt . Sprintf ( "multiple locations found for %s, function breakpoint is only set for the first location" , want . Name )
}
// Set breakpoint using the PCs that were found.
loc := locs [ 0 ]
got , err := s . debugger . CreateBreakpoint ( & api . Breakpoint { Addr : loc . PC , Addrs : loc . PCs , Cond : want . Condition , Name : reqString } )
var clientPath string
if got != nil {
clientPath = s . toClientPath ( got . File )
2020-02-15 19:52:53 +00:00
}
2021-05-28 18:21:53 +00:00
updateBreakpointsResponse ( breakpoints , i , err , got , clientPath )
2020-02-15 19:52:53 +00:00
}
2021-05-28 18:21:53 +00:00
response := & dap . SetFunctionBreakpointsResponse { Response : * newResponse ( request . Request ) }
response . Body . Breakpoints = breakpoints
2020-02-15 19:52:53 +00:00
s . send ( response )
}
2021-05-28 18:21:53 +00:00
func ( s * Server ) clearBreakpoints ( existingBps map [ string ] * api . Breakpoint , bpAdded map [ string ] struct { } ) error {
for req , bp := range existingBps {
if _ , ok := bpAdded [ req ] ; ok {
continue
}
_ , err := s . debugger . ClearBreakpoint ( bp )
if err != nil {
return err
}
}
return nil
}
func ( s * Server ) getMatchingBreakpoints ( prefix string ) map [ string ] * api . Breakpoint {
existing := s . debugger . Breakpoints ( )
matchingBps := make ( map [ string ] * api . Breakpoint , len ( existing ) )
for _ , bp := range existing {
// Skip special breakpoints such as for panic.
if bp . ID < 0 {
continue
}
// Skip breakpoints that do not meet the condition.
if ! strings . HasPrefix ( bp . Name , prefix ) {
continue
}
matchingBps [ bp . Name ] = bp
}
return matchingBps
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) onSetExceptionBreakpointsRequest ( request * dap . SetExceptionBreakpointsRequest ) {
// Unlike what DAP documentation claims, this request is always sent
2020-05-01 19:24:44 +00:00
// even though we specified no filters at initialization. Handle as no-op.
2020-02-15 19:52:53 +00:00
s . send ( & dap . SetExceptionBreakpointsResponse { Response : * newResponse ( request . Request ) } )
}
2021-05-04 19:49:52 +00:00
func ( s * Server ) asyncCommandDone ( asyncSetupDone chan struct { } ) {
if asyncSetupDone != nil {
select {
case <- asyncSetupDone :
// already closed
default :
close ( asyncSetupDone )
}
}
}
// onConfigurationDoneRequest handles 'configurationDone' request.
// This is an optional request enabled by capability ‘ supportsConfigurationDoneRequest’ .
// It gets triggered after all the debug requests that followinitalized event,
// so the s.debugger is guaranteed to be set.
func ( s * Server ) onConfigurationDoneRequest ( request * dap . ConfigurationDoneRequest , asyncSetupDone chan struct { } ) {
defer s . asyncCommandDone ( asyncSetupDone )
if s . args . stopOnEntry {
2020-02-15 19:52:53 +00:00
e := & dap . StoppedEvent {
Event : * newEvent ( "stopped" ) ,
2020-04-01 19:51:31 +00:00
Body : dap . StoppedEventBody { Reason : "entry" , ThreadId : 1 , AllThreadsStopped : true } ,
2020-02-15 19:52:53 +00:00
}
s . send ( e )
}
s . send ( & dap . ConfigurationDoneResponse { Response : * newResponse ( request . Request ) } )
2021-05-04 19:49:52 +00:00
if ! s . args . stopOnEntry {
2021-05-17 16:17:00 +00:00
s . doRunCommand ( api . Continue , asyncSetupDone )
2020-02-15 19:52:53 +00:00
}
}
2021-05-04 19:49:52 +00:00
// onContinueRequest handles 'continue' request.
// This is a mandatory request to support.
func ( s * Server ) onContinueRequest ( request * dap . ContinueRequest , asyncSetupDone chan struct { } ) {
2020-08-24 17:21:51 +00:00
s . send ( & dap . ContinueResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . ContinueResponseBody { AllThreadsContinued : true } } )
2021-05-17 16:17:00 +00:00
s . doRunCommand ( api . Continue , asyncSetupDone )
2020-02-15 19:52:53 +00:00
}
2020-11-30 17:43:37 +00:00
func fnName ( loc * proc . Location ) string {
if loc . Fn == nil {
return "???"
}
return loc . Fn . Name
}
2021-06-10 17:59:24 +00:00
func fnPackageName ( loc * proc . Location ) string {
if loc . Fn == nil {
// attribute unknown functions to the runtime
return "runtime"
}
return loc . Fn . PackageName ( )
}
2021-05-04 19:49:52 +00:00
// onThreadsRequest handles 'threads' request.
// This is a mandatory request to support.
// It is sent in response to configurationDone response and stopped events.
2020-03-10 19:29:06 +00:00
func ( s * Server ) onThreadsRequest ( request * dap . ThreadsRequest ) {
if s . debugger == nil {
s . sendErrorResponse ( request . Request , UnableToDisplayThreads , "Unable to display threads" , "debugger is nil" )
return
}
2021-05-04 19:49:52 +00:00
2020-03-10 19:29:06 +00:00
gs , _ , err := s . debugger . Goroutines ( 0 , 0 )
if err != nil {
switch err . ( type ) {
2021-04-13 06:52:29 +00:00
case proc . ErrProcessExited :
2020-03-10 19:29:06 +00:00
// If the program exits very quickly, the initial threads request will complete after it has exited.
// A TerminatedEvent has already been sent. Ignore the err returned in this case.
s . send ( & dap . ThreadsResponse { Response : * newResponse ( request . Request ) } )
default :
s . sendErrorResponse ( request . Request , UnableToDisplayThreads , "Unable to display threads" , err . Error ( ) )
}
return
}
threads := make ( [ ] dap . Thread , len ( gs ) )
if len ( threads ) == 0 {
// Depending on the debug session stage, goroutines information
// might not be available. However, the DAP spec states that
// "even if a debug adapter does not support multiple threads,
// it must implement the threads request and return a single
// (dummy) thread".
threads = [ ] dap . Thread { { Id : 1 , Name : "Dummy" } }
} else {
2020-11-30 17:43:37 +00:00
state , err := s . debugger . State ( /*nowait*/ true )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToDisplayThreads , "Unable to display threads" , err . Error ( ) )
return
}
s . debugger . LockTarget ( )
defer s . debugger . UnlockTarget ( )
2020-03-10 19:29:06 +00:00
for i , g := range gs {
2020-11-30 17:43:37 +00:00
selected := ""
if state . SelectedGoroutine != nil && g . ID == state . SelectedGoroutine . ID {
selected = "* "
}
thread := ""
if g . Thread != nil && g . Thread . ThreadID ( ) != 0 {
thread = fmt . Sprintf ( " (Thread %d)" , g . Thread . ThreadID ( ) )
2020-03-10 19:29:06 +00:00
}
2020-11-30 17:43:37 +00:00
// File name and line number are communicated via `stackTrace`
// so no need to include them here.
loc := g . UserCurrent ( )
threads [ i ] . Name = fmt . Sprintf ( "%s[Go %d] %s%s" , selected , g . ID , fnName ( & loc ) , thread )
threads [ i ] . Id = g . ID
2020-03-10 19:29:06 +00:00
}
}
2021-05-17 16:25:41 +00:00
2020-03-10 19:29:06 +00:00
response := & dap . ThreadsResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . ThreadsResponseBody { Threads : threads } ,
}
s . send ( response )
}
2020-12-28 17:14:15 +00:00
// onAttachRequest handles 'attach' request.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2020-12-28 17:14:15 +00:00
func ( s * Server ) onAttachRequest ( request * dap . AttachRequest ) {
mode , ok := request . Arguments [ "mode" ]
if ! ok || mode == "" {
mode = "local"
}
if mode == "local" {
pid , ok := request . Arguments [ "processId" ] . ( float64 )
if ! ok || pid == 0 {
s . sendErrorResponse ( request . Request ,
FailedToAttach , "Failed to attach" ,
"The 'processId' attribute is missing in debug configuration" )
return
}
s . config . Debugger . AttachPid = int ( pid )
2021-04-15 23:35:37 +00:00
err := s . setLaunchAttachArgs ( request )
if err != nil {
2021-04-21 20:28:15 +00:00
s . sendErrorResponse ( request . Request , FailedToAttach , "Failed to attach" , err . Error ( ) )
2021-04-15 23:35:37 +00:00
return
}
2021-04-21 20:28:15 +00:00
func ( ) {
s . mu . Lock ( )
defer s . mu . Unlock ( ) // Make sure to unlock in case of panic that will become internal error
s . debugger , err = debugger . New ( & s . config . Debugger , nil )
} ( )
if err != nil {
s . sendErrorResponse ( request . Request , FailedToAttach , "Failed to attach" , err . Error ( ) )
2020-12-28 17:14:15 +00:00
return
}
} else {
// TODO(polina): support 'remote' mode with 'host' and 'port'
s . sendErrorResponse ( request . Request ,
FailedToAttach , "Failed to attach" ,
fmt . Sprintf ( "Unsupported 'mode' value %q in debug configuration" , mode ) )
return
}
// Notify the client that the debugger is ready to start accepting
// configuration requests for setting breakpoints, etc. The client
// will end the configuration sequence with 'configurationDone'.
s . send ( & dap . InitializedEvent { Event : * newEvent ( "initialized" ) } )
s . send ( & dap . AttachResponse { Response : * newResponse ( request . Request ) } )
2020-05-01 19:24:44 +00:00
}
2020-08-24 17:21:51 +00:00
// onNextRequest handles 'next' request.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-04 19:49:52 +00:00
func ( s * Server ) onNextRequest ( request * dap . NextRequest , asyncSetupDone chan struct { } ) {
2020-08-24 17:21:51 +00:00
s . send ( & dap . NextResponse { Response : * newResponse ( request . Request ) } )
2021-05-04 19:49:52 +00:00
s . doStepCommand ( api . Next , request . Arguments . ThreadId , asyncSetupDone )
2020-05-01 19:24:44 +00:00
}
2020-08-24 17:21:51 +00:00
// onStepInRequest handles 'stepIn' request
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-04 19:49:52 +00:00
func ( s * Server ) onStepInRequest ( request * dap . StepInRequest , asyncSetupDone chan struct { } ) {
2020-08-24 17:21:51 +00:00
s . send ( & dap . StepInResponse { Response : * newResponse ( request . Request ) } )
2021-05-04 19:49:52 +00:00
s . doStepCommand ( api . Step , request . Arguments . ThreadId , asyncSetupDone )
2020-05-01 19:24:44 +00:00
}
2020-08-24 17:21:51 +00:00
// onStepOutRequest handles 'stepOut' request
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-04 19:49:52 +00:00
func ( s * Server ) onStepOutRequest ( request * dap . StepOutRequest , asyncSetupDone chan struct { } ) {
2020-08-24 17:21:51 +00:00
s . send ( & dap . StepOutResponse { Response : * newResponse ( request . Request ) } )
2021-05-04 19:49:52 +00:00
s . doStepCommand ( api . StepOut , request . Arguments . ThreadId , asyncSetupDone )
2021-04-02 16:19:16 +00:00
}
2021-04-12 22:00:26 +00:00
func stoppedGoroutineID ( state * api . DebuggerState ) ( id int ) {
if state . SelectedGoroutine != nil {
id = state . SelectedGoroutine . ID
} else if state . CurrentThread != nil {
id = state . CurrentThread . GoroutineID
}
return id
}
2021-05-17 16:17:00 +00:00
// doStepCommand is a wrapper around doRunCommand that
2021-05-04 19:49:52 +00:00
// first switches selected goroutine. asyncSetupDone is
// a channel that will be closed to signal that an
// asynchornous command has completed setup or was interrupted
// due to an error, so the server is ready to receive new requests.
func ( s * Server ) doStepCommand ( command string , threadId int , asyncSetupDone chan struct { } ) {
defer s . asyncCommandDone ( asyncSetupDone )
2021-05-18 03:34:27 +00:00
// All of the threads will be continued by this request, so we need to send
// a continued event so the UI can properly reflect the current state.
s . send ( & dap . ContinuedEvent {
Event : * newEvent ( "continued" ) ,
Body : dap . ContinuedEventBody {
ThreadId : threadId ,
AllThreadsContinued : true ,
} ,
} )
2021-04-21 20:39:19 +00:00
_ , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . SwitchGoroutine , GoroutineID : threadId } , nil )
2021-04-02 16:19:16 +00:00
if err != nil {
2021-04-21 20:39:19 +00:00
s . log . Errorf ( "Error switching goroutines while stepping: %v" , err )
2021-04-02 16:19:16 +00:00
// If we encounter an error, we will have to send a stopped event
// since we already sent the step response.
stopped := & dap . StoppedEvent { Event : * newEvent ( "stopped" ) }
stopped . Body . AllThreadsStopped = true
2021-04-21 20:39:19 +00:00
if state , err := s . debugger . State ( false ) ; err != nil {
s . log . Errorf ( "Error retrieving state: %e" , err )
} else {
2021-04-12 22:00:26 +00:00
stopped . Body . ThreadId = stoppedGoroutineID ( state )
2021-04-02 16:19:16 +00:00
}
stopped . Body . Reason = "error"
stopped . Body . Text = err . Error ( )
s . send ( stopped )
return
}
2021-05-17 16:17:00 +00:00
s . doRunCommand ( command , asyncSetupDone )
2020-05-01 19:24:44 +00:00
}
2021-05-17 17:37:15 +00:00
// onPauseRequest handles 'pause' request.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-17 17:37:15 +00:00
func ( s * Server ) onPauseRequest ( request * dap . PauseRequest ) {
_ , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . Halt } , nil )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToHalt , "Unable to halt execution" , err . Error ( ) )
return
}
s . send ( & dap . PauseResponse { Response : * newResponse ( request . Request ) } )
// No need to send any event here.
// If we received this request while stopped, there already was an event for the stop.
// If we received this while running, then doCommand will unblock and trigger the right
// event, using debugger.StopReason because manual stop reason always wins even if we
// simultaneously receive a manual stop request and hit a breakpoint.
2020-05-01 19:24:44 +00:00
}
2020-07-01 18:01:17 +00:00
// stackFrame represents the index of a frame within
// the context of a stack of a specific goroutine.
type stackFrame struct {
goroutineID int
frameIndex int
}
// onStackTraceRequest handles ‘ stackTrace’ requests.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-04 19:49:52 +00:00
// As per DAP spec, this request only gets triggered as a follow-up
// to a successful threads request as part of the "request waterfall".
2020-07-01 18:01:17 +00:00
func ( s * Server ) onStackTraceRequest ( request * dap . StackTraceRequest ) {
goroutineID := request . Arguments . ThreadId
2020-07-07 13:21:18 +00:00
frames , err := s . debugger . Stacktrace ( goroutineID , s . args . stackTraceDepth , 0 )
2020-07-01 18:01:17 +00:00
if err != nil {
s . sendErrorResponse ( request . Request , UnableToProduceStackTrace , "Unable to produce stack trace" , err . Error ( ) )
return
}
2021-06-10 17:59:24 +00:00
// Determine if the goroutine is a system goroutine.
2021-07-01 18:25:33 +00:00
isSystemGoroutine := true
if g , _ := s . debugger . FindGoroutine ( goroutineID ) ; g != nil {
isSystemGoroutine = g . System ( )
2021-06-10 17:59:24 +00:00
}
2020-07-07 13:21:18 +00:00
stackFrames := make ( [ ] dap . StackFrame , len ( frames ) )
for i , frame := range frames {
loc := & frame . Call
2020-07-01 18:01:17 +00:00
uniqueStackFrameID := s . stackFrameHandles . create ( stackFrame { goroutineID , i } )
2020-11-30 17:43:37 +00:00
stackFrames [ i ] = dap . StackFrame { Id : uniqueStackFrameID , Line : loc . Line , Name : fnName ( loc ) }
2020-07-01 18:01:17 +00:00
if loc . File != "<autogenerated>" {
2021-04-15 23:35:37 +00:00
clientPath := s . toClientPath ( loc . File )
stackFrames [ i ] . Source = dap . Source { Name : filepath . Base ( clientPath ) , Path : clientPath }
2020-07-01 18:01:17 +00:00
}
stackFrames [ i ] . Column = 0
2021-06-10 17:59:24 +00:00
packageName := fnPackageName ( loc )
if ! isSystemGoroutine && packageName == "runtime" {
stackFrames [ i ] . Source . PresentationHint = "deemphasize"
}
2020-07-01 18:01:17 +00:00
}
2020-11-30 17:43:09 +00:00
// Since the backend doesn't support paging, we load all frames up to
// pre-configured depth every time and then slice them here per
// `supportsDelayedStackTraceLoading` capability.
// TODO(polina): consider optimizing this, so subsequent stack requests
// slice already loaded frames and handles instead of reloading every time.
2020-07-01 18:01:17 +00:00
if request . Arguments . StartFrame > 0 {
stackFrames = stackFrames [ min ( request . Arguments . StartFrame , len ( stackFrames ) ) : ]
}
if request . Arguments . Levels > 0 {
stackFrames = stackFrames [ : min ( request . Arguments . Levels , len ( stackFrames ) ) ]
}
response := & dap . StackTraceResponse {
Response : * newResponse ( request . Request ) ,
2020-07-07 13:21:18 +00:00
Body : dap . StackTraceResponseBody { StackFrames : stackFrames , TotalFrames : len ( frames ) } ,
2020-07-01 18:01:17 +00:00
}
s . send ( response )
2020-05-01 19:24:44 +00:00
}
2020-08-11 15:34:27 +00:00
// onScopesRequest handles 'scopes' requests.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2021-05-04 19:49:52 +00:00
// It is automatically sent as part of the threads > stacktrace > scopes > variables
// "waterfall" to highlight the topmost frame at stops, after an evaluate request
// for the selected scope or when a user selects different scopes in the UI.
2020-08-11 15:34:27 +00:00
func ( s * Server ) onScopesRequest ( request * dap . ScopesRequest ) {
sf , ok := s . stackFrameHandles . get ( request . Arguments . FrameId )
if ! ok {
s . sendErrorResponse ( request . Request , UnableToListLocals , "Unable to list locals" , fmt . Sprintf ( "unknown frame id %d" , request . Arguments . FrameId ) )
return
}
2020-07-07 13:21:18 +00:00
goid := sf . ( stackFrame ) . goroutineID
frame := sf . ( stackFrame ) . frameIndex
2020-08-11 15:34:27 +00:00
2021-05-17 16:21:15 +00:00
// Check if the function is optimized.
fn , err := s . debugger . Function ( goid , frame , 0 , DefaultLoadConfig )
if fn == nil || err != nil {
s . sendErrorResponse ( request . Request , UnableToListArgs , "Unable to find enclosing function" , err . Error ( ) )
return
}
suffix := ""
if fn . Optimized ( ) {
suffix = " (warning: optimized function)"
}
2020-08-11 15:34:27 +00:00
// Retrieve arguments
2021-02-23 16:29:06 +00:00
args , err := s . debugger . FunctionArguments ( goid , frame , 0 , DefaultLoadConfig )
2020-08-11 15:34:27 +00:00
if err != nil {
s . sendErrorResponse ( request . Request , UnableToListArgs , "Unable to list args" , err . Error ( ) )
return
}
2021-06-10 16:34:20 +00:00
argScope := & fullyQualifiedVariable { & proc . Variable { Name : fmt . Sprintf ( "Arguments%s" , suffix ) , Children : slicePtrVarToSliceVar ( args ) } , "" , true , 0 }
2020-08-11 15:34:27 +00:00
// Retrieve local variables
2021-02-23 16:29:06 +00:00
locals , err := s . debugger . LocalVariables ( goid , frame , 0 , DefaultLoadConfig )
2020-08-11 15:34:27 +00:00
if err != nil {
2020-09-15 20:14:55 +00:00
s . sendErrorResponse ( request . Request , UnableToListLocals , "Unable to list locals" , err . Error ( ) )
2020-08-11 15:34:27 +00:00
return
}
2021-06-10 16:34:20 +00:00
locScope := & fullyQualifiedVariable { & proc . Variable { Name : fmt . Sprintf ( "Locals%s" , suffix ) , Children : slicePtrVarToSliceVar ( locals ) } , "" , true , 0 }
2020-08-11 15:34:27 +00:00
scopeArgs := dap . Scope { Name : argScope . Name , VariablesReference : s . variableHandles . create ( argScope ) }
scopeLocals := dap . Scope { Name : locScope . Name , VariablesReference : s . variableHandles . create ( locScope ) }
scopes := [ ] dap . Scope { scopeArgs , scopeLocals }
2020-09-15 20:14:55 +00:00
if s . args . showGlobalVariables {
// Limit what global variables we will return to the current package only.
// TODO(polina): This is how vscode-go currently does it to make
// the amount of the returned data manageable. In fact, this is
// considered so expensive even with the package filter, that
// the default for showGlobalVariables was recently flipped to
// not showing. If we delay loading of the globals until the corresponding
// scope is expanded, generating an explicit variable request,
// should we consider making all globals accessible with a scope per package?
// Or users can just rely on watch variables.
currPkg , err := s . debugger . CurrentPackage ( )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToListGlobals , "Unable to list globals" , err . Error ( ) )
return
}
currPkgFilter := fmt . Sprintf ( "^%s\\." , currPkg )
2021-02-23 16:29:06 +00:00
globals , err := s . debugger . PackageVariables ( currPkgFilter , DefaultLoadConfig )
2020-09-15 20:14:55 +00:00
if err != nil {
s . sendErrorResponse ( request . Request , UnableToListGlobals , "Unable to list globals" , err . Error ( ) )
return
}
// Remove package prefix from the fully-qualified variable names.
// We will include the package info once in the name of the scope instead.
for i , g := range globals {
globals [ i ] . Name = strings . TrimPrefix ( g . Name , currPkg + "." )
}
2021-01-14 18:53:12 +00:00
globScope := & fullyQualifiedVariable { & proc . Variable {
2020-09-15 20:14:55 +00:00
Name : fmt . Sprintf ( "Globals (package %s)" , currPkg ) ,
Children : slicePtrVarToSliceVar ( globals ) ,
2021-06-10 16:34:20 +00:00
} , currPkg , true , 0 }
2020-09-15 20:14:55 +00:00
scopeGlobals := dap . Scope { Name : globScope . Name , VariablesReference : s . variableHandles . create ( globScope ) }
scopes = append ( scopes , scopeGlobals )
}
2020-08-11 15:34:27 +00:00
response := & dap . ScopesResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . ScopesResponseBody { Scopes : scopes } ,
}
s . send ( response )
2020-05-01 19:24:44 +00:00
}
2020-07-07 13:21:18 +00:00
func slicePtrVarToSliceVar ( vars [ ] * proc . Variable ) [ ] proc . Variable {
r := make ( [ ] proc . Variable , len ( vars ) )
for i := range vars {
r [ i ] = * vars [ i ]
}
return r
}
2020-08-11 15:34:27 +00:00
// onVariablesRequest handles 'variables' requests.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2020-08-11 15:34:27 +00:00
func ( s * Server ) onVariablesRequest ( request * dap . VariablesRequest ) {
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
ref := request . Arguments . VariablesReference
v , ok := s . variableHandles . get ( ref )
2020-08-11 15:34:27 +00:00
if ! ok {
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
s . sendErrorResponse ( request . Request , UnableToLookupVariable , "Unable to lookup variable" , fmt . Sprintf ( "unknown reference %d" , ref ) )
2020-08-11 15:34:27 +00:00
return
}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
2021-06-10 16:34:20 +00:00
// If there is a filter applied, we will need to create a new variable that includes
// the values actually needed to load. This cannot be done when loading the parent
// node, since it is unknown at that point which children will need to be loaded.
if request . Arguments . Filter == "indexed" {
var err error
v , err = s . maybeLoadResliced ( v , request . Arguments . Start , request . Arguments . Count )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToLookupVariable , "Unable to lookup variable" , err . Error ( ) )
return
}
}
2021-06-23 23:51:57 +00:00
var children [ ] dap . Variable
if request . Arguments . Filter == "named" || request . Arguments . Filter == "" {
named , err := s . metadataToDAPVariables ( v )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToLookupVariable , "Unable to lookup variable" , err . Error ( ) )
return
}
children = append ( children , named ... )
}
if request . Arguments . Filter == "indexed" || request . Arguments . Filter == "" {
indexed , err := s . childrenToDAPVariables ( v )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToLookupVariable , "Unable to lookup variable" , err . Error ( ) )
return
}
children = append ( children , indexed ... )
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
}
response := & dap . VariablesResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . VariablesResponseBody { Variables : children } ,
}
s . send ( response )
}
2021-06-10 16:34:20 +00:00
func ( s * Server ) maybeLoadResliced ( v * fullyQualifiedVariable , start , count int ) ( * fullyQualifiedVariable , error ) {
if start == 0 && count == len ( v . Children ) {
// If we have already loaded the correct children,
// just return the variable.
return v , nil
}
indexedLoadConfig := DefaultLoadConfig
indexedLoadConfig . MaxArrayValues = count
newV , err := s . debugger . LoadResliced ( v . Variable , start , indexedLoadConfig )
if err != nil {
return nil , err
}
return & fullyQualifiedVariable { newV , v . fullyQualifiedNameOrExpr , false , start } , nil
}
func getIndexedVariableCount ( c * proc . Variable ) int {
indexedVars := 0
switch c . Kind {
case reflect . Array , reflect . Slice , reflect . Map :
indexedVars = int ( c . Len )
}
return indexedVars
}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
// childrenToDAPVariables returns the DAP presentation of the referenced variable's children.
func ( s * Server ) childrenToDAPVariables ( v * fullyQualifiedVariable ) ( [ ] dap . Variable , error ) {
// TODO(polina): consider convertVariableToString instead of convertVariable
// and avoid unnecessary creation of variable handles when this is called to
// compute evaluate names when this is called from onSetVariableRequest.
var children [ ] dap . Variable
2020-08-11 15:34:27 +00:00
switch v . Kind {
case reflect . Map :
for i := 0 ; i < len ( v . Children ) ; i += 2 {
// A map will have twice as many children as there are key-value elements.
kvIndex := i / 2
// Process children in pairs: even indices are map keys, odd indices are values.
2021-01-14 18:53:12 +00:00
keyv , valv := & v . Children [ i ] , & v . Children [ i + 1 ]
keyexpr := fmt . Sprintf ( "(*(*%q)(%#x))" , keyv . TypeString ( ) , keyv . Addr )
valexpr := fmt . Sprintf ( "%s[%s]" , v . fullyQualifiedNameOrExpr , keyexpr )
switch keyv . Kind {
// For value expression, use the key value, not the corresponding expression if the key is a scalar.
case reflect . Bool , reflect . Float32 , reflect . Float64 , reflect . Complex64 , reflect . Complex128 ,
reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 ,
reflect . Uint , reflect . Uint8 , reflect . Uint16 , reflect . Uint32 , reflect . Uint64 , reflect . Uintptr :
valexpr = fmt . Sprintf ( "%s[%s]" , v . fullyQualifiedNameOrExpr , api . VariableValueAsString ( keyv ) )
case reflect . String :
if key := constant . StringVal ( keyv . Value ) ; keyv . Len == int64 ( len ( key ) ) { // fully loaded
valexpr = fmt . Sprintf ( "%s[%q]" , v . fullyQualifiedNameOrExpr , key )
}
}
key , keyref := s . convertVariable ( keyv , keyexpr )
val , valref := s . convertVariable ( valv , valexpr )
2021-05-10 18:34:42 +00:00
keyType := s . getTypeIfSupported ( keyv )
valType := s . getTypeIfSupported ( valv )
2020-08-11 15:34:27 +00:00
// If key or value or both are scalars, we can use
// a single variable to represet key:value format.
// Otherwise, we must return separate variables for both.
if keyref > 0 && valref > 0 { // Both are not scalars
keyvar := dap . Variable {
2021-06-10 16:34:20 +00:00
Name : fmt . Sprintf ( "[key %d]" , v . startIndex + kvIndex ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : keyexpr ,
2021-05-10 18:34:42 +00:00
Type : keyType ,
2020-08-11 15:34:27 +00:00
Value : key ,
VariablesReference : keyref ,
2021-06-10 16:34:20 +00:00
IndexedVariables : getIndexedVariableCount ( keyv ) ,
2021-06-23 23:51:57 +00:00
NamedVariables : getNamedVariableCount ( keyv ) ,
2020-08-11 15:34:27 +00:00
}
valvar := dap . Variable {
2021-06-10 16:34:20 +00:00
Name : fmt . Sprintf ( "[val %d]" , v . startIndex + kvIndex ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : valexpr ,
2021-05-10 18:34:42 +00:00
Type : valType ,
2020-08-11 15:34:27 +00:00
Value : val ,
VariablesReference : valref ,
2021-06-10 16:34:20 +00:00
IndexedVariables : getIndexedVariableCount ( valv ) ,
2021-06-23 23:51:57 +00:00
NamedVariables : getNamedVariableCount ( valv ) ,
2020-08-11 15:34:27 +00:00
}
children = append ( children , keyvar , valvar )
} else { // At least one is a scalar
2021-05-10 18:34:42 +00:00
keyValType := valType
if len ( keyType ) > 0 && len ( valType ) > 0 {
keyValType = fmt . Sprintf ( "%s: %s" , keyType , valType )
}
2020-08-11 15:34:27 +00:00
kvvar := dap . Variable {
2021-01-14 18:53:12 +00:00
Name : key ,
EvaluateName : valexpr ,
2021-05-10 18:34:42 +00:00
Type : keyValType ,
2021-01-14 18:53:12 +00:00
Value : val ,
2020-08-11 15:34:27 +00:00
}
if keyref != 0 { // key is a type to be expanded
2021-06-22 15:14:47 +00:00
if len ( key ) > maxMapKeyValueLen {
2021-03-25 16:44:32 +00:00
// Truncate and make unique
2021-06-22 15:14:47 +00:00
kvvar . Name = fmt . Sprintf ( "%s... @ %#x" , key [ 0 : maxMapKeyValueLen ] , keyv . Addr )
2021-03-25 16:44:32 +00:00
}
2020-08-11 15:34:27 +00:00
kvvar . VariablesReference = keyref
2021-06-10 16:34:20 +00:00
kvvar . IndexedVariables = getIndexedVariableCount ( keyv )
2021-06-23 23:51:57 +00:00
kvvar . NamedVariables = getNamedVariableCount ( keyv )
2020-08-11 15:34:27 +00:00
} else if valref != 0 { // val is a type to be expanded
kvvar . VariablesReference = valref
2021-06-10 16:34:20 +00:00
kvvar . IndexedVariables = getIndexedVariableCount ( valv )
2021-06-23 23:51:57 +00:00
kvvar . NamedVariables = getNamedVariableCount ( valv )
2020-08-11 15:34:27 +00:00
}
children = append ( children , kvvar )
}
}
case reflect . Slice , reflect . Array :
children = make ( [ ] dap . Variable , len ( v . Children ) )
2020-07-07 13:21:18 +00:00
for i := range v . Children {
2021-06-10 16:34:20 +00:00
idx := v . startIndex + i
cfqname := fmt . Sprintf ( "%s[%d]" , v . fullyQualifiedNameOrExpr , idx )
2021-01-14 18:53:12 +00:00
cvalue , cvarref := s . convertVariable ( & v . Children [ i ] , cfqname )
2020-08-11 15:34:27 +00:00
children [ i ] = dap . Variable {
2021-06-10 16:34:20 +00:00
Name : fmt . Sprintf ( "[%d]" , idx ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : cfqname ,
2021-05-10 18:34:42 +00:00
Type : s . getTypeIfSupported ( & v . Children [ i ] ) ,
2021-01-14 18:53:12 +00:00
Value : cvalue ,
VariablesReference : cvarref ,
2021-06-10 16:34:20 +00:00
IndexedVariables : getIndexedVariableCount ( & v . Children [ i ] ) ,
2021-06-23 23:51:57 +00:00
NamedVariables : getNamedVariableCount ( & v . Children [ i ] ) ,
2020-08-11 15:34:27 +00:00
}
}
default :
children = make ( [ ] dap . Variable , len ( v . Children ) )
2020-07-07 13:21:18 +00:00
for i := range v . Children {
c := & v . Children [ i ]
2021-01-14 18:53:12 +00:00
cfqname := fmt . Sprintf ( "%s.%s" , v . fullyQualifiedNameOrExpr , c . Name )
if strings . HasPrefix ( c . Name , "~" ) || strings . HasPrefix ( c . Name , "." ) {
cfqname = ""
} else if v . isScope && v . fullyQualifiedNameOrExpr == "" {
cfqname = c . Name
} else if v . fullyQualifiedNameOrExpr == "" {
cfqname = ""
} else if v . Kind == reflect . Interface {
cfqname = fmt . Sprintf ( "%s.(%s)" , v . fullyQualifiedNameOrExpr , c . Name ) // c is data
} else if v . Kind == reflect . Ptr {
cfqname = fmt . Sprintf ( "(*%v)" , v . fullyQualifiedNameOrExpr ) // c is the nameless pointer value
} else if v . Kind == reflect . Complex64 || v . Kind == reflect . Complex128 {
cfqname = "" // complex children are not struct fields and can't be accessed directly
}
cvalue , cvarref := s . convertVariable ( c , cfqname )
2021-04-19 18:14:50 +00:00
// Annotate any shadowed variables to "(name)" in order
// to distinguish from non-shadowed variables.
// TODO(suzmue): should we support a special evaluateName syntax that
// can access shadowed variables?
name := c . Name
if c . Flags & proc . VariableShadowed == proc . VariableShadowed {
name = fmt . Sprintf ( "(%s)" , name )
}
2020-08-11 15:34:27 +00:00
children [ i ] = dap . Variable {
2021-04-19 18:14:50 +00:00
Name : name ,
2021-01-14 18:53:12 +00:00
EvaluateName : cfqname ,
2021-05-10 18:34:42 +00:00
Type : s . getTypeIfSupported ( c ) ,
2021-01-14 18:53:12 +00:00
Value : cvalue ,
VariablesReference : cvarref ,
2021-06-10 16:34:20 +00:00
IndexedVariables : getIndexedVariableCount ( c ) ,
2021-06-23 23:51:57 +00:00
NamedVariables : getNamedVariableCount ( c ) ,
2020-08-11 15:34:27 +00:00
}
}
}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
return children , nil
2020-08-11 15:34:27 +00:00
}
2021-06-23 23:51:57 +00:00
func getNamedVariableCount ( v * proc . Variable ) int {
namedVars := 0
if isListOfBytesOrRunes ( v ) {
// string value of array/slice of bytes and runes.
namedVars += 1
}
return namedVars
}
// metadataToDAPVariables returns the DAP presentation of the referenced variable's metadata.
// These are included as named variables
func ( s * Server ) metadataToDAPVariables ( v * fullyQualifiedVariable ) ( [ ] dap . Variable , error ) {
var children [ ] dap . Variable
if isListOfBytesOrRunes ( v . Variable ) {
// Return the string value of []byte or []rune.
typeName := api . PrettyTypeName ( v . DwarfType )
loadExpr := fmt . Sprintf ( "string(*(*%q)(%#x))" , typeName , v . Addr )
s . log . Debugf ( "loading %s (type %s) with %s" , v . fullyQualifiedNameOrExpr , typeName , loadExpr )
// We know that this is an array/slice of Uint8 or Int32, so we will load up to MaxStringLen.
config := DefaultLoadConfig
config . MaxArrayValues = config . MaxStringLen
vLoaded , err := s . debugger . EvalVariableInScope ( - 1 , 0 , 0 , loadExpr , config )
val := s . convertVariableToString ( vLoaded )
if err == nil {
// TODO(suzmue): Add evaluate name. Using string(name) will not get the same result because the
// MaxArrayValues is not auto adjusted in evaluate requests like MaxStringLen is adjusted.
children = append ( children , dap . Variable {
Name : "string()" ,
Value : val ,
Type : "string" ,
} )
}
}
return children , nil
}
func isListOfBytesOrRunes ( v * proc . Variable ) bool {
if len ( v . Children ) > 0 && ( v . Kind == reflect . Array || v . Kind == reflect . Slice ) {
childKind := v . Children [ 0 ] . RealType . Common ( ) . ReflectKind
return childKind == reflect . Uint8 || childKind == reflect . Int32
}
return false
}
2021-05-10 18:34:42 +00:00
func ( s * Server ) getTypeIfSupported ( v * proc . Variable ) string {
if ! s . clientCapabilities . supportsVariableType {
return ""
}
return v . TypeString ( )
}
2021-01-14 18:53:12 +00:00
// convertVariable converts proc.Variable to dap.Variable value and reference
// while keeping track of the full qualified name or load expression.
2020-08-11 15:34:27 +00:00
// Variable reference is used to keep track of the children associated with each
2020-11-12 23:24:31 +00:00
// variable. It is shared with the host via scopes or evaluate response and is an index
// into the s.variableHandles map, used to look up variables and their children on
// subsequent variables requests. A positive reference signals the host that another
// variables request can be issued to get the elements of the compound variable. As a
// custom, a zero reference, reminiscent of a zero pointer, is used to indicate that
// a scalar variable cannot be "dereferenced" to get its elements (as there are none).
2021-01-14 18:53:12 +00:00
func ( s * Server ) convertVariable ( v * proc . Variable , qualifiedNameOrExpr string ) ( value string , variablesReference int ) {
2021-06-04 07:27:57 +00:00
return s . convertVariableWithOpts ( v , qualifiedNameOrExpr , 0 )
2020-11-12 23:24:31 +00:00
}
func ( s * Server ) convertVariableToString ( v * proc . Variable ) string {
2021-06-04 07:27:57 +00:00
val , _ := s . convertVariableWithOpts ( v , "" , skipRef )
2020-11-12 23:24:31 +00:00
return val
}
2021-06-22 15:14:47 +00:00
const (
// Limit the length of a string representation of a compound or reference type variable.
maxVarValueLen = 1 << 8 // 256
// Limit the length of an inlined map key.
maxMapKeyValueLen = 64
)
2021-06-04 07:27:57 +00:00
// Flags for convertVariableWithOpts option.
type convertVariableFlags uint8
const (
skipRef convertVariableFlags = 1 << iota
showFullValue
)
2021-01-14 18:53:12 +00:00
// convertVariableWithOpts allows to skip reference generation in case all we need is
2021-06-04 07:27:57 +00:00
// a string representation of the variable. When the variable is a compound or reference
// type variable and its full string representation can be larger than defaultMaxValueLen,
// this returns a truncated value unless showFull option flag is set.
func ( s * Server ) convertVariableWithOpts ( v * proc . Variable , qualifiedNameOrExpr string , opts convertVariableFlags ) ( value string , variablesReference int ) {
canHaveRef := false
2020-11-12 23:24:31 +00:00
maybeCreateVariableHandle := func ( v * proc . Variable ) int {
2021-06-04 07:27:57 +00:00
canHaveRef = true
if opts & skipRef != 0 {
2020-11-12 23:24:31 +00:00
return 0
}
2021-06-10 16:34:20 +00:00
return s . variableHandles . create ( & fullyQualifiedVariable { v , qualifiedNameOrExpr , false /*not a scope*/ , 0 } )
2020-11-12 23:24:31 +00:00
}
2021-03-15 16:36:46 +00:00
value = api . ConvertVar ( v ) . SinglelineString ( )
2020-07-07 13:21:18 +00:00
if v . Unreadable != nil {
2021-06-04 07:27:57 +00:00
return value , 0
2020-08-11 15:34:27 +00:00
}
2021-02-23 16:29:06 +00:00
2021-05-04 19:53:42 +00:00
// Some of the types might be fully or partially not loaded based on LoadConfig.
// Those that are fully missing (e.g. due to hitting MaxVariableRecurse), can be reloaded in place.
var reloadVariable = func ( v * proc . Variable , qualifiedNameOrExpr string ) ( value string ) {
// We might be loading variables from the frame that's not topmost, so use
// frame-independent address-based expression, not fully-qualified name as per
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables.
2021-06-10 16:34:20 +00:00
// TODO(polina): Get *proc.Variable object from debugger instead. Export a function to set v.loaded to false
// and call v.loadValue gain with a different load config. It's more efficient, and it's guaranteed to keep
// working with generics.
2021-05-04 19:53:42 +00:00
value = api . ConvertVar ( v ) . SinglelineString ( )
typeName := api . PrettyTypeName ( v . DwarfType )
loadExpr := fmt . Sprintf ( "*(*%q)(%#x)" , typeName , v . Addr )
s . log . Debugf ( "loading %s (type %s) with %s" , qualifiedNameOrExpr , typeName , loadExpr )
// Make sure we can load the pointers directly, not by updating just the child
// This is not really necessary now because users have no way of setting FollowPointers to false.
config := DefaultLoadConfig
config . FollowPointers = true
vLoaded , err := s . debugger . EvalVariableInScope ( - 1 , 0 , 0 , loadExpr , config )
if err != nil {
value += fmt . Sprintf ( " - FAILED TO LOAD: %s" , err )
} else {
v . Children = vLoaded . Children
value = api . ConvertVar ( v ) . SinglelineString ( )
}
return value
}
2021-02-23 16:29:06 +00:00
2020-08-11 15:34:27 +00:00
switch v . Kind {
case reflect . UnsafePointer :
2021-03-15 16:36:46 +00:00
// Skip child reference
2020-08-11 15:34:27 +00:00
case reflect . Ptr :
2021-03-15 16:36:46 +00:00
if v . DwarfType != nil && len ( v . Children ) > 0 && v . Children [ 0 ] . Addr != 0 && v . Children [ 0 ] . Kind != reflect . Invalid {
2021-05-04 19:53:42 +00:00
if v . Children [ 0 ] . OnlyAddr { // Not loaded
if v . Addr == 0 {
// This is equvalent to the following with the cli:
// (dlv) p &a7
// (**main.FooBar)(0xc0000a3918)
//
// TODO(polina): what is more appropriate?
// Option 1: leave it unloaded because it is a special case
// Option 2: load it, but then we have to load the child, not the parent, unlike all others
// TODO(polina): see if reloadVariable can be reused here
cTypeName := api . PrettyTypeName ( v . Children [ 0 ] . DwarfType )
cLoadExpr := fmt . Sprintf ( "*(*%q)(%#x)" , cTypeName , v . Children [ 0 ] . Addr )
s . log . Debugf ( "loading *(%s) (type %s) with %s" , qualifiedNameOrExpr , cTypeName , cLoadExpr )
cLoaded , err := s . debugger . EvalVariableInScope ( - 1 , 0 , 0 , cLoadExpr , DefaultLoadConfig )
if err != nil {
value += fmt . Sprintf ( " - FAILED TO LOAD: %s" , err )
} else {
cLoaded . Name = v . Children [ 0 ] . Name // otherwise, this will be the pointer expression
v . Children = [ ] proc . Variable { * cLoaded }
value = api . ConvertVar ( v ) . SinglelineString ( )
}
} else {
value = reloadVariable ( v , qualifiedNameOrExpr )
}
}
if ! v . Children [ 0 ] . OnlyAddr {
2021-02-23 16:29:06 +00:00
variablesReference = maybeCreateVariableHandle ( v )
}
2020-08-11 15:34:27 +00:00
}
2021-03-15 16:36:46 +00:00
case reflect . Slice , reflect . Array :
2021-02-23 16:29:06 +00:00
if v . Len > int64 ( len ( v . Children ) ) { // Not fully loaded
2021-05-04 19:53:42 +00:00
if v . Base != 0 && len ( v . Children ) == 0 { // Fully missing
value = reloadVariable ( v , qualifiedNameOrExpr )
2021-06-10 16:34:20 +00:00
} else if ! s . clientCapabilities . supportsVariablePaging {
2021-05-04 19:53:42 +00:00
value = fmt . Sprintf ( "(loaded %d/%d) " , len ( v . Children ) , v . Len ) + value
}
2021-02-23 16:29:06 +00:00
}
2021-03-15 16:36:46 +00:00
if v . Base != 0 && len ( v . Children ) > 0 {
2020-11-12 23:24:31 +00:00
variablesReference = maybeCreateVariableHandle ( v )
2020-08-11 15:34:27 +00:00
}
case reflect . Map :
2021-03-15 16:36:46 +00:00
if v . Len > int64 ( len ( v . Children ) / 2 ) { // Not fully loaded
2021-05-04 19:53:42 +00:00
if len ( v . Children ) == 0 { // Fully missing
value = reloadVariable ( v , qualifiedNameOrExpr )
2021-06-10 16:34:20 +00:00
} else if ! s . clientCapabilities . supportsVariablePaging {
2021-05-04 19:53:42 +00:00
value = fmt . Sprintf ( "(loaded %d/%d) " , len ( v . Children ) / 2 , v . Len ) + value
}
2020-08-11 15:34:27 +00:00
}
2021-03-15 16:36:46 +00:00
if v . Base != 0 && len ( v . Children ) > 0 {
2020-11-12 23:24:31 +00:00
variablesReference = maybeCreateVariableHandle ( v )
2020-08-11 15:34:27 +00:00
}
2021-03-15 16:36:46 +00:00
case reflect . String :
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
// TODO(polina): implement auto-loading here.
2020-08-11 15:34:27 +00:00
case reflect . Interface :
2021-03-15 16:36:46 +00:00
if v . Addr != 0 && len ( v . Children ) > 0 && v . Children [ 0 ] . Kind != reflect . Invalid && v . Children [ 0 ] . Addr != 0 {
2021-05-04 19:53:42 +00:00
if v . Children [ 0 ] . OnlyAddr { // Not loaded
value = reloadVariable ( v , qualifiedNameOrExpr )
2021-03-08 17:41:47 +00:00
}
if ! v . Children [ 0 ] . OnlyAddr {
2021-02-23 16:29:06 +00:00
variablesReference = maybeCreateVariableHandle ( v )
}
}
case reflect . Struct :
if v . Len > int64 ( len ( v . Children ) ) { // Not fully loaded
2021-05-04 19:53:42 +00:00
if len ( v . Children ) == 0 { // Fully missing
value = reloadVariable ( v , qualifiedNameOrExpr )
} else { // Partially missing (TODO)
value = fmt . Sprintf ( "(loaded %d/%d) " , len ( v . Children ) , v . Len ) + value
}
2021-02-23 16:29:06 +00:00
}
if len ( v . Children ) > 0 {
2020-11-12 23:24:31 +00:00
variablesReference = maybeCreateVariableHandle ( v )
2020-08-11 15:34:27 +00:00
}
2020-07-07 13:21:18 +00:00
case reflect . Complex64 , reflect . Complex128 :
v . Children = make ( [ ] proc . Variable , 2 )
v . Children [ 0 ] . Name = "real"
v . Children [ 0 ] . Value = constant . Real ( v . Value )
v . Children [ 1 ] . Name = "imaginary"
v . Children [ 1 ] . Value = constant . Imag ( v . Value )
if v . Kind == reflect . Complex64 {
v . Children [ 0 ] . Kind = reflect . Float32
v . Children [ 1 ] . Kind = reflect . Float32
} else {
v . Children [ 0 ] . Kind = reflect . Float64
v . Children [ 1 ] . Kind = reflect . Float64
}
fallthrough
2021-03-15 16:36:46 +00:00
default : // Complex, Scalar, Chan, Func
2020-08-11 15:34:27 +00:00
if len ( v . Children ) > 0 {
2020-11-12 23:24:31 +00:00
variablesReference = maybeCreateVariableHandle ( v )
2020-08-11 15:34:27 +00:00
}
}
2021-06-11 17:46:49 +00:00
// By default, only values of variables that have children can be truncated.
// If showFullValue is set, then all value strings are not truncated.
2021-06-04 07:27:57 +00:00
canTruncateValue := showFullValue & opts == 0
2021-06-22 15:14:47 +00:00
if len ( value ) > maxVarValueLen && canTruncateValue && canHaveRef {
value = value [ : maxVarValueLen ] + "..."
2021-06-04 07:27:57 +00:00
}
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
return value , variablesReference
2020-05-01 19:24:44 +00:00
}
2020-11-12 23:24:31 +00:00
// onEvaluateRequest handles 'evalute' requests.
2020-05-01 19:24:44 +00:00
// This is a mandatory request to support.
2020-11-12 23:24:31 +00:00
// Support the following expressions:
// -- {expression} - evaluates the expression and returns the result as a variable
// -- call {function} - injects a function call and returns the result as a variable
// TODO(polina): users have complained about having to click to expand multi-level
// variables, so consider also adding the following:
// -- print {expression} - return the result as a string like from dlv cli
func ( s * Server ) onEvaluateRequest ( request * dap . EvaluateRequest ) {
2021-05-19 18:17:36 +00:00
showErrorToUser := request . Arguments . Context != "watch" && request . Arguments . Context != "repl" && request . Arguments . Context != "hover"
2020-11-12 23:24:31 +00:00
if s . debugger == nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , "debugger is nil" , showErrorToUser )
return
}
2021-05-04 19:49:52 +00:00
2020-11-12 23:24:31 +00:00
// Default to the topmost stack frame of the current goroutine in case
// no frame is specified (e.g. when stopped on entry or no call stack frame is expanded)
goid , frame := - 1 , 0
if sf , ok := s . stackFrameHandles . get ( request . Arguments . FrameId ) ; ok {
goid = sf . ( stackFrame ) . goroutineID
frame = sf . ( stackFrame ) . frameIndex
}
response := & dap . EvaluateResponse { Response : * newResponse ( request . Request ) }
isCall , err := regexp . MatchString ( ` ^\s*call\s+\S+ ` , request . Arguments . Expression )
if err == nil && isCall { // call {expression}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
expr := strings . Replace ( request . Arguments . Expression , "call " , "" , 1 )
_ , retVars , err := s . doCall ( goid , frame , expr )
2020-11-12 23:24:31 +00:00
if err != nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , err . Error ( ) , showErrorToUser )
return
}
// The call completed and we can reply with its return values (if any)
if len ( retVars ) > 0 {
// Package one or more return values in a single scope-like nameless variable
// that preserves their names.
retVarsAsVar := & proc . Variable { Children : slicePtrVarToSliceVar ( retVars ) }
// As a shortcut also express the return values as a single string.
retVarsAsStr := ""
for _ , v := range retVars {
retVarsAsStr += s . convertVariableToString ( v ) + ", "
}
response . Body = dap . EvaluateResponseBody {
Result : strings . TrimRight ( retVarsAsStr , ", " ) ,
2021-06-10 16:34:20 +00:00
VariablesReference : s . variableHandles . create ( & fullyQualifiedVariable { retVarsAsVar , "" , false /*not a scope*/ , 0 } ) ,
2020-11-12 23:24:31 +00:00
}
}
} else { // {expression}
2021-02-23 16:29:06 +00:00
exprVar , err := s . debugger . EvalVariableInScope ( goid , frame , 0 , request . Arguments . Expression , DefaultLoadConfig )
2020-11-12 23:24:31 +00:00
if err != nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , err . Error ( ) , showErrorToUser )
return
}
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
2021-06-04 07:27:57 +00:00
ctxt := request . Arguments . Context
switch ctxt {
case "repl" , "variables" , "hover" , "clipboard" :
if exprVar . Kind == reflect . String {
if strVal := constant . StringVal ( exprVar . Value ) ; exprVar . Len > int64 ( len ( strVal ) ) {
2021-06-22 15:14:47 +00:00
// Reload the string value with a bigger limit.
2021-06-04 07:27:57 +00:00
loadCfg := DefaultLoadConfig
2021-06-22 15:14:47 +00:00
loadCfg . MaxStringLen = maxSingleStringLen
2021-06-04 07:27:57 +00:00
if v , err := s . debugger . EvalVariableInScope ( goid , frame , 0 , request . Arguments . Expression , loadCfg ) ; err != nil {
s . log . Debugf ( "Failed to load more for %v: %v" , request . Arguments . Expression , err )
} else {
exprVar = v
}
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
}
}
}
2021-06-04 07:27:57 +00:00
var opts convertVariableFlags
2021-06-11 17:46:49 +00:00
// Send the full value when the context is "clipboard" or "variables" since
// these contexts are used to copy the value.
if ctxt == "clipboard" || ctxt == "variables" {
2021-06-04 07:27:57 +00:00
opts |= showFullValue
}
exprVal , exprRef := s . convertVariableWithOpts ( exprVar , fmt . Sprintf ( "(%s)" , request . Arguments . Expression ) , opts )
2021-06-23 23:51:57 +00:00
response . Body = dap . EvaluateResponseBody { Result : exprVal , VariablesReference : exprRef , IndexedVariables : getIndexedVariableCount ( exprVar ) , NamedVariables : getNamedVariableCount ( exprVar ) }
2020-11-12 23:24:31 +00:00
}
s . send ( response )
2020-05-01 19:24:44 +00:00
}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
func ( s * Server ) doCall ( goid , frame int , expr string ) ( * api . DebuggerState , [ ] * proc . Variable , error ) {
// This call might be evaluated in the context of the frame that is not topmost
// if the editor is set to view the variables for one of the parent frames.
// If the call expression refers to any of these variables, unlike regular
// expressions, it will evaluate them in the context of the topmost frame,
// and the user will get an unexpected result or an unexpected symbol error.
// We prevent this but disallowing any frames other than topmost.
if frame > 0 {
return nil , nil , fmt . Errorf ( "call is only supported with topmost stack frame" )
}
stateBeforeCall , err := s . debugger . State ( /*nowait*/ true )
if err != nil {
return nil , nil , err
}
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
// The return values of injected function calls are volatile.
// Load as much useful data as possible.
// TODO: investigate whether we need to increase other limits. For example,
// the return value is a pointer to a temporary object, which can become
// invalid by other injected function calls. Do we care about such use cases?
loadCfg := DefaultLoadConfig
2021-06-22 15:14:47 +00:00
loadCfg . MaxStringLen = maxStringLenInCallRetVars
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
// TODO(polina): since call will resume execution of all goroutines,
// we should do this asynchronously and send a continued event to the
// editor, followed by a stop event when the call completes.
state , err := s . debugger . Command ( & api . DebuggerCommand {
Name : api . Call ,
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
ReturnInfoLoadConfig : api . LoadConfigFromProc ( & loadCfg ) ,
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
Expr : expr ,
UnsafeCall : false ,
GoroutineID : goid ,
} , nil )
if _ , isexited := err . ( proc . ErrProcessExited ) ; isexited || err == nil && state . Exited {
e := & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) }
s . send ( e )
return nil , nil , errors . New ( "terminated" )
}
if err != nil {
return nil , nil , err
}
// After the call is done, the goroutine where we injected the call should
// return to the original stopped line with return values. However,
// it is not guaranteed to be selected due to the possibility of the
// of simultaenous breakpoints. Therefore, we check all threads.
var retVars [ ] * proc . Variable
found := false
for _ , t := range state . Threads {
if t . GoroutineID == stateBeforeCall . SelectedGoroutine . ID &&
t . Line == stateBeforeCall . SelectedGoroutine . CurrentLoc . Line && t . CallReturn {
found = true
// The call completed. Get the return values.
dap: use larger string type variable load limits in 'repl', 'variables' context (#2418)
* dap: use larger variable load limits in 'repl', 'variables' context
When evaluate requests are triggered in the context of 'repl'
(DEBUG CONSOLE in VSCode) or 'variables' (copy values from VARIABLES
section in VSCode), they are the result of human action and have
more rooms to display. So it is not too bad to apply longer limits.
Variable auto-loading for strings or arrays is nice but currently
it's unclear to me how this should be integrated in the DEBUG
CONSOLE or with the Copy Value feature. Until we have better ideas
and tools, let's go with these larger limits.
Unfortunately, the "Copy Value" from WATCH section triggers evaluate
requests with "watch" context and we don't want to load large data
automatically for "watch". So, users who want to query a large value
should first copy the expression to DEBUG CONSOLE and evaluate it.
Not ideal but not the end of the world either.
Updates golang/vscode-go#1318
* dap: apply large limit only to the string type result
* dap: move string reload logic to convertVariable* where other reload logic is
Currently we are thinking string reload for evaluation as a temporary
workaround until we figure out an intutitive way to present long strings.
So, I hope moving this logic near other reload logic may be better.
And, use the address based expression when reloading - when handling the
function return values, we may not have an expression to use.
* dap: make deep source check happy
* dap: move string reevaluation logic back to onEvaluateRequest
Reloading string variables is tricky if they are in registers.
We don't attempt to reload them but for clarity, move this up
to the onEvaluateRequest handler.
For function call, use a generous limit for string load
since the results are volatile.
* dap: check variable isn't affected by evaluate in other context
2021-05-25 17:23:41 +00:00
retVars , err = s . debugger . FindThreadReturnValues ( t . ID , loadCfg )
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
if err != nil {
return nil , nil , err
}
break
}
}
// Normal function calls expect return values, but call commands
// used for variable assignments do not return a value when they succeed.
// In go '=' is not an operator. Check if go/parser complains.
// If the above Call command passed but the expression is not a valid
// go expression, we just handled a variable assignment request.
isAssignment := false
if _ , err := parser . ParseExpr ( expr ) ; err != nil {
isAssignment = true
}
// note: as described in https://github.com/golang/go/issues/25578, function call injection
// causes to resume the entire Go process. Due to this limitation, there is no guarantee
// that the process is in the same state even after the injected call returns normally
// without any surprises such as breakpoints or panic. To handle this correctly we need
// to reset all the handles (both variables and stack frames).
//
// We considered sending a stopped event after each call unconditionally, but a stopped
// event can be expensive and can interact badly with the client-side optimization
// to refresh information. For example, VS Code reissues scopes/evaluate (for watch) after
// completing a setVariable or evaluate request for repl context. Thus, for now, we
// do not trigger a stopped event and hope editors to refetch the updated state as soon
// as the user resumes debugging.
if ! found || ! isAssignment && retVars == nil {
// The call got interrupted by a stop (e.g. breakpoint in injected
// function call or in another goroutine).
s . resetHandlesForStoppedEvent ( )
s . sendStoppedEvent ( state )
// TODO(polina): once this is asynchronous, we could wait to reply until the user
// continues, call ends, original stop point is hit and return values are available
// instead of returning an error 'call stopped' here.
return nil , nil , errors . New ( "call stopped" )
}
return state , retVars , nil
}
func ( s * Server ) sendStoppedEvent ( state * api . DebuggerState ) {
stopped := & dap . StoppedEvent { Event : * newEvent ( "stopped" ) }
stopped . Body . AllThreadsStopped = true
stopped . Body . ThreadId = stoppedGoroutineID ( state )
stopped . Body . Reason = s . debugger . StopReason ( ) . String ( )
s . send ( stopped )
}
2020-05-01 19:24:44 +00:00
// onTerminateRequest sends a not-yet-implemented error response.
// Capability 'supportsTerminateRequest' is not set in 'initialize' response.
func ( s * Server ) onTerminateRequest ( request * dap . TerminateRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onRestartRequest sends a not-yet-implemented error response
// Capability 'supportsRestartRequest' is not set in 'initialize' response.
func ( s * Server ) onRestartRequest ( request * dap . RestartRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onStepBackRequest sends a not-yet-implemented error response.
// Capability 'supportsStepBack' is not set 'initialize' response.
func ( s * Server ) onStepBackRequest ( request * dap . StepBackRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onReverseContinueRequest sends a not-yet-implemented error response.
// Capability 'supportsStepBack' is not set 'initialize' response.
func ( s * Server ) onReverseContinueRequest ( request * dap . ReverseContinueRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
dap: handle SetVariable requests (#2440)
* dap: handle SetVariable requests
The handler invokes debugger.SetVariableInScope, except for
string type variables. For which, we rely on the `call` command.
Moved the call expression handling logic to the new `doCall`
function, so it can be reused by the SetVariable requenst
handler.
With this PR, every successful SetVariable request triggers
a StoppedEvent - that's a hack to reset the variablesHandle
map internally and notify the client of this change. It will
be nice if we can just update cached data corresponding to
the updated variable. But I cannot find an easy and safe way
to achieve this yet.
Also fixed a small bug in the call expression evaluation -
Previously, dlv dap returned an error "Unable to evaluate
expression: call stopped" if the call expression is for
variable assignment. (e.g. "call animal = "rabbit").
* dap: address comments from aarzilli
resetHandlesForStop & sendStoppedEvent unconditionally after
call command is left as a TODO - This is an existing code path
(just refactored) and an preexisting bug. Fixing it here
requires updates in TestEvaluateCallRequest and I prefer
addressing it in a separate cl.
Disabled call injection testing on arm64. Separated TestSetVariable
into two, one that doesn't involve call injection and another that
may involve call injection.
Fixed variableByName by removing unnecessary recursion.
* dap: address polina's comments
- removed the hard reset for every variable set
- added tests for various variable types
- added tests that involves interrupted function calls. (breakpoint/panic)
And,
- changed to utilize EvalVariableInScope to access the variable instead
of searching the children by name.
- changed to utilize evaluate requests when verifying whether the variable
is changed as expected in testing. Since now we avoid resetting the variable
handles after variable reset, either we need to trigger scope changes
explicitly, or stop depending on the variables request.
* dap: address comments
- Discuss the problem around the current doCall implementation
and the implication.
- Refine the description on how VS Code handles after setVariable
and evaluate request (there could be followup scopes/evaluate requests).
- Use the explicit line numbers for breakpoints in the SetVariable tests.
- Do not use errors.Is - we could've used golang.org/x/xerrors polyfill
but that's an additional dependency, and we will remove this check once
tests that depend on old behavior are fixed.
* dap: remove errTerminated and adjust the test
* dap: evaluate in the outer frame, instead of advancing to the next bp
2021-05-20 17:05:47 +00:00
// computeEvaluateName finds the named child, and computes its evaluate name.
func ( s * Server ) computeEvaluateName ( v * fullyQualifiedVariable , cname string ) ( string , error ) {
children , err := s . childrenToDAPVariables ( v )
if err != nil {
return "" , err
}
for _ , c := range children {
if c . Name == cname {
if c . EvaluateName != "" {
return c . EvaluateName , nil
}
return "" , errors . New ( "cannot set the variable without evaluate name" )
}
}
return "" , errors . New ( "failed to find the named variable" )
}
// onSetVariableRequest handles 'setVariable' requests.
func ( s * Server ) onSetVariableRequest ( request * dap . SetVariableRequest ) {
arg := request . Arguments
v , ok := s . variableHandles . get ( arg . VariablesReference )
if ! ok {
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to lookup variable" , fmt . Sprintf ( "unknown reference %d" , arg . VariablesReference ) )
return
}
// We need to translate the arg.Name to its evaluateName if the name
// refers to a field or element of a variable.
// https://github.com/microsoft/vscode/issues/120774
evaluateName , err := s . computeEvaluateName ( v , arg . Name )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to set variable" , err . Error ( ) )
return
}
// By running EvalVariableInScope, we get the type info of the variable
// that can be accessed with the evaluateName, and ensure the variable we are
// trying to update is valid and accessible from the top most frame & the
// current goroutine.
goid , frame := - 1 , 0
evaluated , err := s . debugger . EvalVariableInScope ( goid , frame , 0 , evaluateName , DefaultLoadConfig )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to lookup variable" , err . Error ( ) )
return
}
useFnCall := false
switch evaluated . Kind {
case reflect . String :
useFnCall = true
default :
// TODO(hyangah): it's possible to set a non-string variable using (`call i = fn()`)
// and we don't support it through the Set Variable request yet.
// If we want to support it for non-string types, we need to parse arg.Value.
}
if useFnCall {
// TODO(hyangah): function call injection currentlly allows to assign return values of
// a function call to variables. So, curious users would find set variable
// on string would accept expression like `fn()`.
if state , retVals , err := s . doCall ( goid , frame , fmt . Sprintf ( "%v=%v" , evaluateName , arg . Value ) ) ; err != nil {
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to set variable" , err . Error ( ) )
return
} else if retVals != nil {
// The assignment expression isn't supposed to return values, but we got them.
// That indicates something went wrong (e.g. panic).
// TODO: isn't it simpler to do this in s.doCall?
s . resetHandlesForStoppedEvent ( )
s . sendStoppedEvent ( state )
var r [ ] string
for _ , v := range retVals {
r = append ( r , s . convertVariableToString ( v ) )
}
msg := "interrupted"
if len ( r ) > 0 {
msg = "interrupted:" + strings . Join ( r , ", " )
}
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to set variable" , msg )
return
}
} else {
if err := s . debugger . SetVariableInScope ( goid , frame , 0 , evaluateName , arg . Value ) ; err != nil {
s . sendErrorResponse ( request . Request , UnableToSetVariable , "Unable to set variable" , err . Error ( ) )
return
}
}
// * Note on inconsistent state after set variable:
//
// The variable handles may be in inconsistent state - for example,
// let's assume there are two aliased variables pointing to the same
// memory and both are already loaded and cached in the variable handle.
// VSCode tries to locally update the UI when the set variable
// request succeeds, and may issue additional scopes or evaluate requests
// to update the variable/watch sections if necessary.
//
// More complicated situation is when the set variable involves call
// injection - after the injected call is completed, the debugee can
// be in a completely different state (see the note in doCall) due to
// how the call injection is implemented. Ideally, we need to also refresh
// the stack frames but that is complicated. For now we don't try to actively
// invalidate this state hoping that the editors will refetch the state
// as soon as the user resumes debugging.
response := & dap . SetVariableResponse { Response : * newResponse ( request . Request ) }
response . Body . Value = arg . Value
// TODO(hyangah): instead of arg.Value, reload the variable and return
// the presentation of the new value.
s . send ( response )
2020-05-01 19:24:44 +00:00
}
// onSetExpression sends a not-yet-implemented error response.
// Capability 'supportsSetExpression' is not set 'initialize' response.
func ( s * Server ) onSetExpressionRequest ( request * dap . SetExpressionRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onLoadedSourcesRequest sends a not-yet-implemented error response.
// Capability 'supportsLoadedSourcesRequest' is not set 'initialize' response.
func ( s * Server ) onLoadedSourcesRequest ( request * dap . LoadedSourcesRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onReadMemoryRequest sends a not-yet-implemented error response.
// Capability 'supportsReadMemoryRequest' is not set 'initialize' response.
func ( s * Server ) onReadMemoryRequest ( request * dap . ReadMemoryRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onDisassembleRequest sends a not-yet-implemented error response.
// Capability 'supportsDisassembleRequest' is not set 'initialize' response.
func ( s * Server ) onDisassembleRequest ( request * dap . DisassembleRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// onCancelRequest sends a not-yet-implemented error response.
// Capability 'supportsCancelRequest' is not set 'initialize' response.
func ( s * Server ) onCancelRequest ( request * dap . CancelRequest ) {
s . sendNotYetImplementedErrorResponse ( request . Request )
}
2021-05-17 16:25:41 +00:00
// onExceptionInfoRequest handles 'exceptionInfo' requests.
// Capability 'supportsExceptionInfoRequest' is set in 'initialize' response.
func ( s * Server ) onExceptionInfoRequest ( request * dap . ExceptionInfoRequest ) {
goroutineID := request . Arguments . ThreadId
var body dap . ExceptionInfoResponseBody
// Get the goroutine and the current state.
g , err := s . debugger . FindGoroutine ( goroutineID )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToGetExceptionInfo , "Unable to get exception info" , err . Error ( ) )
return
}
if g == nil {
s . sendErrorResponse ( request . Request , UnableToGetExceptionInfo , "Unable to get exception info" , fmt . Sprintf ( "could not find goroutine %d" , goroutineID ) )
return
}
var bpState * proc . BreakpointState
if g . Thread != nil {
bpState = g . Thread . Breakpoint ( )
}
// Check if this goroutine ID is stopped at a breakpoint.
if bpState != nil && bpState . Breakpoint != nil && ( bpState . Breakpoint . Name == proc . FatalThrow || bpState . Breakpoint . Name == proc . UnrecoveredPanic ) {
switch bpState . Breakpoint . Name {
case proc . FatalThrow :
body . ExceptionId = "fatal error"
2021-06-28 15:39:34 +00:00
// Attempt to get the value of the throw reason.
// This is not currently working for Go 1.16 or 1.17: https://github.com/golang/go/issues/46425.
handleError := func ( err error ) {
if err != nil {
body . Description = fmt . Sprintf ( "Error getting throw reason: %s" , err . Error ( ) )
}
if goversion . ProducerAfterOrEqual ( s . debugger . TargetGoVersion ( ) , 1 , 16 ) {
body . Description = "Throw reason unavailable, see https://github.com/golang/go/issues/46425"
}
}
exprVar , err := s . debugger . EvalVariableInScope ( goroutineID , 1 , 0 , "s" , DefaultLoadConfig )
if err == nil {
if exprVar . Value != nil {
body . Description = exprVar . Value . String ( )
} else {
handleError ( exprVar . Unreadable )
}
} else {
handleError ( err )
}
2021-05-17 16:25:41 +00:00
case proc . UnrecoveredPanic :
body . ExceptionId = "panic"
// Attempt to get the value of the panic message.
exprVar , err := s . debugger . EvalVariableInScope ( goroutineID , 0 , 0 , "(*msgs).arg.(data)" , DefaultLoadConfig )
if err == nil {
body . Description = exprVar . Value . String ( )
2021-06-28 15:39:34 +00:00
} else {
body . Description = fmt . Sprintf ( "Error getting panic message: %s" , err . Error ( ) )
2021-05-17 16:25:41 +00:00
}
}
} else {
// If this thread is not stopped on a breakpoint, then a runtime error must have occurred.
// If we do not have any error saved, or if this thread is not current thread,
// return an error.
if s . exceptionErr == nil {
s . sendErrorResponse ( request . Request , UnableToGetExceptionInfo , "Unable to get exception info" , "no runtime error found" )
return
}
state , err := s . debugger . State ( /*nowait*/ true )
if err != nil {
s . sendErrorResponse ( request . Request , UnableToGetExceptionInfo , "Unable to get exception info" , err . Error ( ) )
return
}
if state == nil || state . CurrentThread == nil || g . Thread == nil || state . CurrentThread . ID != g . Thread . ThreadID ( ) {
s . sendErrorResponse ( request . Request , UnableToGetExceptionInfo , "Unable to get exception info" , fmt . Sprintf ( "no exception found for goroutine %d" , goroutineID ) )
return
}
body . ExceptionId = "runtime error"
body . Description = s . exceptionErr . Error ( )
if body . Description == "bad access" {
body . Description = BetterBadAccessError
}
}
frames , err := s . debugger . Stacktrace ( goroutineID , s . args . stackTraceDepth , 0 )
2021-06-28 15:39:34 +00:00
if err == nil {
2021-05-17 16:25:41 +00:00
apiFrames , err := s . debugger . ConvertStacktrace ( frames , nil )
if err == nil {
var buf bytes . Buffer
fmt . Fprintln ( & buf , "Stack:" )
2021-06-10 17:59:24 +00:00
userLoc := g . UserCurrent ( )
userFuncPkg := fnPackageName ( & userLoc )
2021-06-16 20:05:17 +00:00
api . PrintStack ( s . toClientPath , & buf , apiFrames , "\t" , false , func ( s api . Stackframe ) bool {
2021-06-10 17:59:24 +00:00
// Include all stack frames if the stack trace is for a system goroutine,
// otherwise, skip runtime stack frames.
if userFuncPkg == "runtime" {
return true
}
return s . Location . Function != nil && ! strings . HasPrefix ( s . Location . Function . Name ( ) , "runtime." )
} )
2021-05-17 16:25:41 +00:00
body . Details . StackTrace = buf . String ( )
}
2021-06-28 15:39:34 +00:00
} else {
body . Details . StackTrace = fmt . Sprintf ( "Error getting stack trace: %s" , err . Error ( ) )
2021-05-17 16:25:41 +00:00
}
response := & dap . ExceptionInfoResponse {
Response : * newResponse ( request . Request ) ,
Body : body ,
}
s . send ( response )
}
2021-04-02 16:17:43 +00:00
// sendErrorResponseWithOpts offers configuration options.
2020-11-12 23:24:31 +00:00
// showUser - if true, the error will be shown to the user (e.g. via a visible pop-up)
func ( s * Server ) sendErrorResponseWithOpts ( request dap . Request , id int , summary , details string , showUser bool ) {
2020-02-15 19:52:53 +00:00
er := & dap . ErrorResponse { }
er . Type = "response"
er . Command = request . Command
er . RequestSeq = request . Seq
er . Success = false
er . Message = summary
er . Body . Error . Id = id
er . Body . Error . Format = fmt . Sprintf ( "%s: %s" , summary , details )
2020-11-12 23:24:31 +00:00
er . Body . Error . ShowUser = showUser
2021-04-02 16:17:43 +00:00
s . log . Debug ( er . Body . Error . Format )
2020-02-15 19:52:53 +00:00
s . send ( er )
}
2020-11-12 23:24:31 +00:00
// sendErrorResponse sends an error response with default visibility settings.
func ( s * Server ) sendErrorResponse ( request dap . Request , id int , summary , details string ) {
s . sendErrorResponseWithOpts ( request , id , summary , details , false /*showUser*/ )
}
2020-02-26 05:00:54 +00:00
// sendInternalErrorResponse sends an "internal error" response back to the client.
// We only take a seq here because we don't want to make assumptions about the
// kind of message received by the server that this error is a reply to.
func ( s * Server ) sendInternalErrorResponse ( seq int , details string ) {
er := & dap . ErrorResponse { }
er . Type = "response"
er . RequestSeq = seq
er . Success = false
er . Message = "Internal Error"
er . Body . Error . Id = InternalError
er . Body . Error . Format = fmt . Sprintf ( "%s: %s" , er . Message , details )
2021-04-02 16:17:43 +00:00
s . log . Debug ( er . Body . Error . Format )
2020-02-26 05:00:54 +00:00
s . send ( er )
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) sendUnsupportedErrorResponse ( request dap . Request ) {
s . sendErrorResponse ( request , UnsupportedCommand , "Unsupported command" ,
2021-04-02 16:17:43 +00:00
fmt . Sprintf ( "cannot process %q request" , request . Command ) )
2020-02-15 19:52:53 +00:00
}
2020-05-01 19:24:44 +00:00
func ( s * Server ) sendNotYetImplementedErrorResponse ( request dap . Request ) {
s . sendErrorResponse ( request , NotYetImplemented , "Not yet implemented" ,
2021-04-02 16:17:43 +00:00
fmt . Sprintf ( "cannot process %q request" , request . Command ) )
2020-05-01 19:24:44 +00:00
}
2020-02-15 19:52:53 +00:00
func newResponse ( request dap . Request ) * dap . Response {
return & dap . Response {
ProtocolMessage : dap . ProtocolMessage {
Seq : 0 ,
Type : "response" ,
} ,
Command : request . Command ,
RequestSeq : request . Seq ,
Success : true ,
}
}
func newEvent ( event string ) * dap . Event {
return & dap . Event {
ProtocolMessage : dap . ProtocolMessage {
Seq : 0 ,
Type : "event" ,
} ,
Event : event ,
}
}
2020-08-24 17:21:51 +00:00
const BetterBadAccessError = ` invalid memory address or nil pointer dereference [ signal SIGSEGV : segmentation violation ]
2021-02-21 09:30:04 +00:00
Unable to propagate EXC_BAD_ACCESS signal to target process and panic ( see https : //github.com/go-delve/delve/issues/852)`
2020-08-24 17:21:51 +00:00
2021-04-21 20:28:15 +00:00
func ( s * Server ) resetHandlesForStoppedEvent ( ) {
2020-11-12 23:24:31 +00:00
s . stackFrameHandles . reset ( )
s . variableHandles . reset ( )
2021-05-17 16:25:41 +00:00
s . exceptionErr = nil
2020-11-12 23:24:31 +00:00
}
2021-05-17 16:17:00 +00:00
// doRunCommand runs a debugger command until it stops on
2020-08-24 17:21:51 +00:00
// termination, error, breakpoint, etc, when an appropriate
2021-05-04 19:49:52 +00:00
// event needs to be sent to the client. asyncSetupDone is
// a channel that will be closed to signal that an
// asynchornous command has completed setup or was interrupted
// due to an error, so the server is ready to receive new requests.
2021-05-17 16:17:00 +00:00
func ( s * Server ) doRunCommand ( command string , asyncSetupDone chan struct { } ) {
// TODO(polina): it appears that debugger.Command doesn't always close
2021-05-04 19:49:52 +00:00
// asyncSetupDone (e.g. when having an error next while nexting).
// So we should always close it ourselves just in case.
defer s . asyncCommandDone ( asyncSetupDone )
state , err := s . debugger . Command ( & api . DebuggerCommand { Name : command } , asyncSetupDone )
2020-08-24 17:21:51 +00:00
if _ , isexited := err . ( proc . ErrProcessExited ) ; isexited || err == nil && state . Exited {
2021-05-04 19:49:52 +00:00
s . send ( & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) } )
2020-02-15 19:52:53 +00:00
return
}
2020-08-24 17:21:51 +00:00
2021-05-17 16:17:00 +00:00
stopReason := s . debugger . StopReason ( )
2021-05-17 17:37:15 +00:00
file , line := "?" , - 1
if state != nil && state . CurrentThread != nil {
file , line = state . CurrentThread . File , state . CurrentThread . Line
}
s . log . Debugf ( "%q command stopped - reason %q, location %s:%d" , command , stopReason , file , line )
2021-05-17 16:17:00 +00:00
2021-04-21 20:28:15 +00:00
s . resetHandlesForStoppedEvent ( )
2020-08-24 17:21:51 +00:00
stopped := & dap . StoppedEvent { Event : * newEvent ( "stopped" ) }
stopped . Body . AllThreadsStopped = true
if err == nil {
2021-05-17 16:25:41 +00:00
// TODO(suzmue): If stopped.Body.ThreadId is not a valid goroutine
// then the stopped reason does not show up anywhere in the
// vscode ui.
2021-04-12 22:00:26 +00:00
stopped . Body . ThreadId = stoppedGoroutineID ( state )
2020-10-07 15:24:40 +00:00
2021-05-17 16:17:00 +00:00
switch stopReason {
2020-10-07 15:24:40 +00:00
case proc . StopNextFinished :
2020-08-24 17:21:51 +00:00
stopped . Body . Reason = "step"
2021-05-04 19:49:52 +00:00
case proc . StopManual : // triggered by halt
stopped . Body . Reason = "pause"
2021-05-17 17:37:15 +00:00
case proc . StopUnknown : // can happen while terminating
2021-05-04 19:49:52 +00:00
stopped . Body . Reason = "unknown"
2021-05-06 17:33:56 +00:00
case proc . StopWatchpoint :
stopped . Body . Reason = "data breakpoint"
2020-08-24 17:21:51 +00:00
default :
stopped . Body . Reason = "breakpoint"
}
2021-04-12 22:00:26 +00:00
if state . CurrentThread != nil && state . CurrentThread . Breakpoint != nil {
2020-10-07 15:24:40 +00:00
switch state . CurrentThread . Breakpoint . Name {
case proc . FatalThrow :
2021-05-17 16:25:41 +00:00
stopped . Body . Reason = "exception"
2021-06-04 15:35:50 +00:00
stopped . Body . Description = "fatal error"
2020-10-07 15:24:40 +00:00
case proc . UnrecoveredPanic :
2021-05-17 16:25:41 +00:00
stopped . Body . Reason = "exception"
2021-06-04 15:35:50 +00:00
stopped . Body . Description = "panic"
2020-10-07 15:24:40 +00:00
}
2021-05-18 17:25:16 +00:00
if strings . HasPrefix ( state . CurrentThread . Breakpoint . Name , functionBpPrefix ) {
stopped . Body . Reason = "function breakpoint"
}
2020-10-07 15:24:40 +00:00
}
2020-02-15 19:52:53 +00:00
} else {
2021-05-17 16:25:41 +00:00
s . exceptionErr = err
2020-08-24 17:21:51 +00:00
s . log . Error ( "runtime error: " , err )
2021-05-17 16:25:41 +00:00
stopped . Body . Reason = "exception"
2021-06-04 15:35:50 +00:00
stopped . Body . Description = "runtime error"
2020-08-24 17:21:51 +00:00
stopped . Body . Text = err . Error ( )
// Special case in the spirit of https://github.com/microsoft/vscode-go/issues/1903
if stopped . Body . Text == "bad access" {
stopped . Body . Text = BetterBadAccessError
}
state , err := s . debugger . State ( /*nowait*/ true )
if err == nil {
2021-04-12 22:00:26 +00:00
stopped . Body . ThreadId = stoppedGoroutineID ( state )
2020-08-24 17:21:51 +00:00
}
2020-02-15 19:52:53 +00:00
}
2021-05-04 19:49:52 +00:00
// NOTE: If we happen to be responding to another request with an is-running
// error while this one completes, it is possible that the error response
// will arrive after this stopped event.
s . send ( stopped )
2020-02-15 19:52:53 +00:00
}
2021-04-15 23:35:37 +00:00
func ( s * Server ) toClientPath ( path string ) string {
if len ( s . args . substitutePathServerToClient ) == 0 {
return path
}
clientPath := locspec . SubstitutePath ( path , s . args . substitutePathServerToClient )
if clientPath != path {
s . log . Debugf ( "server path=%s converted to client path=%s\n" , path , clientPath )
}
return clientPath
}
func ( s * Server ) toServerPath ( path string ) string {
if len ( s . args . substitutePathClientToServer ) == 0 {
return path
}
serverPath := locspec . SubstitutePath ( path , s . args . substitutePathClientToServer )
if serverPath != path {
s . log . Debugf ( "client path=%s converted to server path=%s\n" , path , serverPath )
}
return serverPath
}