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"
"encoding/json"
"fmt"
2020-07-07 13:21:18 +00:00
"go/constant"
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"
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"
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"
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
// a single debug session. It does not support restarting.
// The server operates via two goroutines:
// (1) Main goroutine where the server is created via NewServer(),
// started via Run() and stopped via Stop().
// (2) Run goroutine started from Run() that accepts a client connection,
// reads, decodes and processes each request, issuing commands to the
// underlying debugger and sending back events and responses.
// TODO(polina): make it asynchronous (i.e. launch goroutine per request)
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
// conn is the accepted client connection.
conn net . Conn
2020-02-27 04:45:48 +00:00
// stopChan is closed when the server is Stop()-ed. This can be used to signal
// to goroutines run by the server that it's time to quit.
stopChan chan struct { }
2020-02-15 19:52:53 +00:00
// reader is used to read requests from the connection.
reader * bufio . Reader
// debugger is the underlying debugger service.
debugger * debugger . Debugger
// log is used for structured logging.
log * logrus . Entry
2020-03-04 17:22:51 +00:00
// binaryToRemove is the compiled binary to be removed on disconnect.
binaryToRemove string
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-03-23 19:10:21 +00:00
mu sync . Mutex
// noDebugProcess is set for the noDebug launch process.
noDebugProcess * exec . Cmd
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
2020-07-08 17:20:05 +00:00
}
// defaultArgs borrows the defaults for the arguments from the original vscode-go adapter.
var defaultArgs = launchAttachArgs {
2020-09-15 20:14:55 +00:00
stopOnEntry : false ,
stackTraceDepth : 50 ,
showGlobalVariables : false ,
2020-02-15 19:52:53 +00:00
}
2021-02-23 16:29:06 +00:00
// DefaultLoadConfig controls how variables are loaded from the target's memory, borrowing the
// default value from the original vscode-go debug adapter and rpc server.
// TODO(polina): Support setting config via launch/attach args or only rely on on-demand loading?
var DefaultLoadConfig = proc . LoadConfig {
FollowPointers : true ,
MaxVariableRecurse : 1 ,
MaxStringLen : 64 ,
MaxArrayValues : 64 ,
MaxStructFields : - 1 ,
}
2020-02-15 19:52:53 +00:00
// NewServer creates a new DAP Server. It takes an opened Listener
2020-02-27 04:45:48 +00:00
// via config and assumes its ownership. config.disconnectChan has to be set;
// it will be closed by the server when the client disconnects or requests
// shutdown. Once disconnectChan is closed, Server.Stop() must be called.
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 ,
stopChan : make ( chan struct { } ) ,
log : logger ,
stackFrameHandles : newHandlesMap ( ) ,
2020-07-07 13:21:18 +00:00
variableHandles : newVariablesHandlesMap ( ) ,
2020-07-08 17:20:05 +00:00
args : defaultArgs ,
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.
func ( s * Server ) setLaunchAttachArgs ( request dap . LaunchAttachRequest ) {
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
}
}
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
// process if it was launched by it. This method mustn't be called more than
// once.
2020-02-15 19:52:53 +00:00
func ( s * Server ) Stop ( ) {
2021-03-15 16:36:46 +00:00
_ = s . listener . Close ( )
2020-02-27 04:45:48 +00:00
close ( s . stopChan )
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
}
if s . debugger != nil {
2020-04-13 18:07:15 +00:00
kill := s . config . Debugger . AttachPid == 0
2020-02-15 19:52:53 +00:00
if err := s . debugger . Detach ( kill ) ; err != nil {
s . log . Error ( err )
}
}
}
// signalDisconnect closes config.DisconnectChan if not nil, which
// signals that the client disconnected or there was a client
// connection failure. Since the server currently services only one
// client, this can be used as a signal to the entire server via
// Stop(). The function safeguards agaist closing the channel more
// than once and can be called multiple times. It is not thread-safe
// and is currently only called from the run goroutine.
// TODO(polina): lock this when we add more goroutines that could call
// this when we support asynchronous request-response communication.
func ( s * Server ) signalDisconnect ( ) {
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()
// -- run goroutine: calls signalDisconnect()
// -- main goroutine: calls Stop()
// -- main goroutine: Stop() closes client connection
// -- run goroutine: serveDAPCodec() gets "closed network connection"
// -- run goroutine: serveDAPCodec() returns
// -- run goroutine: serveDAPCodec calls signalDisconnect()
2020-02-15 19:52:53 +00:00
if s . config . DisconnectChan != nil {
close ( s . config . DisconnectChan )
s . config . DisconnectChan = nil
}
2020-03-04 17:22:51 +00:00
if s . binaryToRemove != "" {
gobuild . Remove ( s . binaryToRemove )
2021-02-24 16:19:07 +00:00
s . binaryToRemove = ""
2020-03-04 17:22:51 +00:00
}
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 ( ) {
conn , err := s . listener . Accept ( )
if err != nil {
2020-02-27 04:45:48 +00:00
select {
case <- s . stopChan :
default :
s . log . Errorf ( "Error accepting client connection: %s\n" , err )
}
2020-02-15 19:52:53 +00:00
s . signalDisconnect ( )
return
}
s . conn = conn
s . serveDAPCodec ( )
} ( )
}
// serveDAPCodec reads and decodes requests from the client
// until it encounters an error or EOF, when it sends
// the disconnect signal and returns.
func ( s * Server ) serveDAPCodec ( ) {
defer s . signalDisconnect ( )
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
stopRequested := false
select {
case <- s . stopChan :
stopRequested = true
default :
}
if err != io . EOF && ! stopRequested {
2020-02-15 19:52:53 +00:00
s . log . Error ( "DAP error: " , err )
}
return
}
s . handleRequest ( request )
}
}
func ( s * Server ) handleRequest ( request dap . Message ) {
2020-02-26 05:00:54 +00:00
defer func ( ) {
// In case a handler panics, we catch the panic and send an error response
// back to the client.
if ierr := recover ( ) ; ierr != nil {
s . sendInternalErrorResponse ( request . GetSeq ( ) , fmt . Sprintf ( "%v" , ierr ) )
}
} ( )
2020-02-15 19:52:53 +00:00
jsonmsg , _ := json . Marshal ( request )
s . log . Debug ( "[<- from client]" , string ( jsonmsg ) )
switch request := request . ( type ) {
case * dap . InitializeRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onInitializeRequest ( request )
case * dap . LaunchRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onLaunchRequest ( request )
case * dap . AttachRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onAttachRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . DisconnectRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onDisconnectRequest ( request )
case * dap . TerminateRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsTerminateRequest‘ )
// TODO: implement this request in V1
s . onTerminateRequest ( request )
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 )
2020-02-15 19:52:53 +00:00
case * dap . SetBreakpointsRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onSetBreakpointsRequest ( request )
case * dap . SetFunctionBreakpointsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsFunctionBreakpoints’ )
// TODO: implement this request in V1
s . onSetFunctionBreakpointsRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . SetExceptionBreakpointsRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ exceptionBreakpointFilters’ )
2020-02-15 19:52:53 +00:00
s . onSetExceptionBreakpointsRequest ( request )
case * dap . ConfigurationDoneRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsConfigurationDoneRequest’ )
// Supported by vscode-go
2020-02-15 19:52:53 +00:00
s . onConfigurationDoneRequest ( request )
case * dap . ContinueRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-02-15 19:52:53 +00:00
s . onContinueRequest ( request )
case * dap . NextRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onNextRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . StepInRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onStepInRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . StepOutRequest :
2020-05-01 19:24:44 +00:00
// Required
s . onStepOutRequest ( request )
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 )
2020-02-15 19:52:53 +00:00
case * dap . RestartFrameRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ’ supportsRestartFrame’ )
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . GotoRequest :
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 . PauseRequest :
2020-05-01 19:24:44 +00:00
// Required
// TODO: implement this request in V0
s . onPauseRequest ( 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 )
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 )
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 . ThreadsRequest :
2020-05-01 19:24:44 +00:00
// Required
2020-03-10 19:29:06 +00:00
s . onThreadsRequest ( request )
2020-02-15 19:52:53 +00:00
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 . EvaluateRequest :
2020-11-12 23:24:31 +00:00
// Required
2020-05-01 19:24:44 +00:00
s . onEvaluateRequest ( request )
2020-02-15 19:52:53 +00:00
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 . ExceptionInfoRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsExceptionInfoRequest’ )
// TODO: does this request make sense for delve?
2020-02-15 19:52:53 +00:00
s . sendUnsupportedErrorResponse ( request . Request )
case * dap . LoadedSourcesRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsLoadedSourcesRequest’ )
// TODO: implement this request in V1
s . onLoadedSourcesRequest ( request )
2020-02-15 19:52:53 +00:00
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 . ReadMemoryRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsReadMemoryRequest‘ )
// TODO: implement this request in V1
s . onReadMemoryRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . DisassembleRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsDisassembleRequest’ )
// TODO: implement this request in V1
s . onDisassembleRequest ( request )
2020-02-15 19:52:53 +00:00
case * dap . CancelRequest :
2020-05-01 19:24:44 +00:00
// Optional (capability ‘ supportsCancelRequest’ )
// TODO: does this request make sense for delve?
s . onCancelRequest ( request )
2020-02-15 19:52:53 +00:00
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-03-15 16:36:46 +00:00
_ = dap . WriteProtocolMessage ( s . conn , message )
2020-02-15 19:52:53 +00:00
}
func ( s * Server ) onInitializeRequest ( request * dap . InitializeRequest ) {
// 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
2020-02-15 19:52:53 +00:00
// TODO(polina): support this to match vscode-go functionality
response . Body . SupportsSetVariable = false
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 . SupportsFunctionBreakpoints = 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-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-03-24 18:02:22 +00:00
output = cleanExeName ( defaultDebugBinary )
2020-03-04 17:22:51 +00:00
}
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 )
2020-03-04 17:22:51 +00:00
switch mode {
case "debug" :
2021-03-24 18:02:22 +00:00
err = gobuild . GoBuild ( debugbinary , [ ] string { program } , buildFlags )
2020-03-04 17:22:51 +00:00
case "test" :
2021-03-24 18:02:22 +00:00
err = gobuild . GoTestBuild ( debugbinary , [ ] string { program } , buildFlags )
2020-03-04 17:22:51 +00:00
}
if err != nil {
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" ,
2020-03-04 17:22:51 +00:00
fmt . Sprintf ( "Build error: %s" , err . Error ( ) ) )
return
}
2021-03-24 18:02:22 +00:00
program = debugbinary
s . binaryToRemove = debugbinary
2020-03-04 17:22:51 +00:00
}
2020-12-28 17:14:15 +00:00
s . setLaunchAttachArgs ( request )
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.
wd , ok := request . Arguments [ "wd" ]
if ok {
wdParsed , ok := wd . ( string )
if ! ok {
s . sendErrorResponse ( request . Request ,
FailedToLaunch , "Failed to launch" ,
fmt . Sprintf ( "'wd' attribute '%v' in debug configuration is not a string." , wd ) )
return
}
s . config . Debugger . WorkingDir = wdParsed
}
2021-03-23 19:10:21 +00:00
noDebug , ok := request . Arguments [ "noDebug" ]
if ok {
if v , ok := noDebug . ( bool ) ; ok && v { // noDebug == true
if err := s . runWithoutDebug ( program , targetArgs , s . config . Debugger . WorkingDir ) ; err != nil {
s . sendErrorResponse ( request . Request , FailedToLaunch , "Failed to launch" , err . Error ( ) )
} else { // program terminated.
s . send ( & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) } )
}
return
}
}
2020-02-15 19:52:53 +00:00
var err error
2020-05-11 16:52:26 +00:00
if s . debugger , err = debugger . New ( & s . config . Debugger , s . config . ProcessArgs ) ; err != nil {
2020-02-15 19:52:53 +00:00
s . sendErrorResponse ( request . Request ,
2020-07-09 21:39:04 +00:00
FailedToLaunch , "Failed to launch" , err . Error ( ) )
2020-02-15 19:52:53 +00:00
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 . LaunchResponse { Response : * newResponse ( request . Request ) } )
}
2021-03-23 19:10:21 +00:00
func ( s * Server ) runWithoutDebug ( program string , targetArgs [ ] string , wd string ) error {
s . log . Println ( "Running without debug: " , program )
cmd := exec . Command ( program , targetArgs ... )
// TODO: send stdin/out/err as OutputEvent messages
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
cmd . Stdin = os . Stdin
cmd . Dir = s . config . Debugger . WorkingDir
s . mu . Lock ( )
defer s . mu . Unlock ( )
if s . noDebugProcess != nil {
return fmt . Errorf ( "previous process (pid=%v) is still active" , s . noDebugProcess . Process . Pid )
}
if err := cmd . Start ( ) ; err != nil {
return err
}
s . noDebugProcess = cmd
s . mu . Unlock ( ) // allow disconnect or restart requests to call stopNoDebugProcess.
// block until the process terminates.
if err := cmd . Wait ( ) ; err != nil {
s . log . Errorf ( "process exited with %v" , err )
}
s . mu . Lock ( )
if s . noDebugProcess == cmd {
s . noDebugProcess = nil
}
return nil // Program ran and terminated.
}
func ( s * Server ) stopNoDebugProcess ( ) {
s . mu . Lock ( )
defer s . mu . Unlock ( )
if s . noDebugProcess == nil {
return
}
// TODO(hyangah): gracefully terminate the process and its children processes.
if err := s . noDebugProcess . Process . Kill ( ) ; err != nil {
s . log . Errorf ( "killing process (pid=%v) failed: %v" , s . noDebugProcess . Process . Pid , err )
}
}
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 ) {
s . send ( & dap . DisconnectResponse { Response : * newResponse ( request . Request ) } )
if s . debugger != nil {
2021-03-08 18:05:10 +00:00
_ , err := s . debugger . Command ( & api . DebuggerCommand { Name : api . Halt } , nil )
2020-02-15 19:52:53 +00:00
if err != nil {
s . log . Error ( err )
}
2020-12-28 17:14:15 +00:00
// We always kill launched programs
2020-04-13 18:07:15 +00:00
kill := s . config . Debugger . AttachPid == 0
2020-12-28 17:14:15 +00:00
// In case of attach, we leave the program
// running but default, which can be
// overridden by an explicit request to terminate.
if request . Arguments . TerminateDebuggee {
kill = true
}
2020-02-15 19:52:53 +00:00
err = s . debugger . Detach ( kill )
if err != nil {
s . log . Error ( err )
}
2021-03-23 19:10:21 +00:00
} else {
s . stopNoDebugProcess ( )
2020-02-15 19:52:53 +00:00
}
// TODO(polina): make thread-safe when handlers become asynchronous.
s . signalDisconnect ( )
}
func ( s * Server ) onSetBreakpointsRequest ( request * dap . SetBreakpointsRequest ) {
2020-10-02 16:18:33 +00:00
// TODO(polina): handle this while running by halting first.
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
}
// 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
// to clear all and then set all.
//
// TODO(polina): should we optimize this as follows?
// 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
// Clear all existing breakpoints in the file.
existing := s . debugger . Breakpoints ( )
for _ , bp := range existing {
// Skip special breakpoints such as for panic.
if bp . ID < 0 {
continue
}
// Skip other source files.
// TODO(polina): should this be normalized because of different OSes?
if bp . File != request . Arguments . Source . Path {
continue
}
_ , err := s . debugger . ClearBreakpoint ( bp )
if err != nil {
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
// Set all requested breakpoints.
2020-02-15 19:52:53 +00:00
response := & dap . SetBreakpointsResponse { Response : * newResponse ( request . Request ) }
response . Body . Breakpoints = make ( [ ] dap . Breakpoint , len ( request . Arguments . Breakpoints ) )
2020-10-02 16:18:33 +00:00
for i , want := range request . Arguments . Breakpoints {
got , err := s . debugger . CreateBreakpoint (
& api . Breakpoint { File : request . Arguments . Source . Path , Line : want . Line , Cond : want . Condition } )
response . Body . Breakpoints [ i ] . Verified = ( err == nil )
2020-02-15 19:52:53 +00:00
if err != nil {
2020-10-02 16:18:33 +00:00
response . Body . Breakpoints [ i ] . Line = want . Line
response . Body . Breakpoints [ i ] . Message = err . Error ( )
} else {
2021-02-21 16:02:42 +00:00
response . Body . Breakpoints [ i ] . Id = got . ID
2020-10-02 16:18:33 +00:00
response . Body . Breakpoints [ i ] . Line = got . Line
2021-02-21 16:02:42 +00:00
response . Body . Breakpoints [ i ] . Source = dap . Source { Name : request . Arguments . Source . Name , Path : request . Arguments . Source . Path }
2020-02-15 19:52:53 +00:00
}
}
s . send ( response )
}
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 ) } )
}
func ( s * Server ) onConfigurationDoneRequest ( request * dap . ConfigurationDoneRequest ) {
2020-07-08 17:20:05 +00:00
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 ) } )
2020-07-08 17:20:05 +00:00
if ! s . args . stopOnEntry {
2020-08-24 17:21:51 +00:00
s . doCommand ( api . Continue )
2020-02-15 19:52:53 +00:00
}
}
func ( s * Server ) onContinueRequest ( request * dap . ContinueRequest ) {
2020-08-24 17:21:51 +00:00
s . send ( & dap . ContinueResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . ContinueResponseBody { AllThreadsContinued : true } } )
s . doCommand ( api . Continue )
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
}
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
}
gs , _ , err := s . debugger . Goroutines ( 0 , 0 )
if err != nil {
switch err . ( type ) {
case * proc . ErrProcessExited :
// 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
}
}
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 )
s . setLaunchAttachArgs ( request )
var err error
if s . debugger , err = debugger . New ( & s . config . Debugger , nil ) ; err != nil {
s . sendErrorResponse ( request . Request ,
FailedToAttach , "Failed to attach" , err . Error ( ) )
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.
2020-08-24 17:21:51 +00:00
func ( s * Server ) onNextRequest ( request * dap . NextRequest ) {
2020-11-12 23:24:31 +00:00
// This ignores threadId argument to match the original vscode-go implementation.
2020-08-24 17:21:51 +00:00
// TODO(polina): use SwitchGoroutine to change the current goroutine.
s . send ( & dap . NextResponse { Response : * newResponse ( request . Request ) } )
s . doCommand ( api . Next )
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.
2020-08-24 17:21:51 +00:00
func ( s * Server ) onStepInRequest ( request * dap . StepInRequest ) {
2020-11-12 23:24:31 +00:00
// This ignores threadId argument to match the original vscode-go implementation.
2020-08-24 17:21:51 +00:00
// TODO(polina): use SwitchGoroutine to change the current goroutine.
s . send ( & dap . StepInResponse { Response : * newResponse ( request . Request ) } )
s . doCommand ( api . Step )
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.
2020-08-24 17:21:51 +00:00
func ( s * Server ) onStepOutRequest ( request * dap . StepOutRequest ) {
2020-11-12 23:24:31 +00:00
// This ignores threadId argument to match the original vscode-go implementation.
2020-08-24 17:21:51 +00:00
// TODO(polina): use SwitchGoroutine to change the current goroutine.
s . send ( & dap . StepOutResponse { Response : * newResponse ( request . Request ) } )
s . doCommand ( api . StepOut )
2020-05-01 19:24:44 +00:00
}
// onPauseRequest sends a not-yet-implemented error response.
// This is a mandatory request to support.
func ( s * Server ) onPauseRequest ( request * dap . PauseRequest ) { // TODO V0
s . sendNotYetImplementedErrorResponse ( request . Request )
}
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.
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
}
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>" {
stackFrames [ i ] . Source = dap . Source { Name : filepath . Base ( loc . File ) , Path : loc . File }
}
stackFrames [ i ] . Column = 0
}
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.
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
// 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-01-14 18:53:12 +00:00
argScope := & fullyQualifiedVariable { & proc . Variable { Name : "Arguments" , Children : slicePtrVarToSliceVar ( args ) } , "" , true }
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-01-14 18:53:12 +00:00
locScope := & fullyQualifiedVariable { & proc . Variable { Name : "Locals" , Children : slicePtrVarToSliceVar ( locals ) } , "" , true }
2020-08-11 15:34:27 +00:00
// TODO(polina): Annotate shadowed variables
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-01-14 18:53:12 +00:00
} , currPkg , true }
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 ) {
2020-07-07 13:21:18 +00:00
v , ok := s . variableHandles . get ( request . Arguments . VariablesReference )
2020-08-11 15:34:27 +00:00
if ! ok {
s . sendErrorResponse ( request . Request , UnableToLookupVariable , "Unable to lookup variable" , fmt . Sprintf ( "unknown reference %d" , request . Arguments . VariablesReference ) )
return
}
children := make ( [ ] dap . Variable , 0 )
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 )
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 {
Name : fmt . Sprintf ( "[key %d]" , kvIndex ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : keyexpr ,
2020-08-11 15:34:27 +00:00
Value : key ,
VariablesReference : keyref ,
}
valvar := dap . Variable {
Name : fmt . Sprintf ( "[val %d]" , kvIndex ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : valexpr ,
2020-08-11 15:34:27 +00:00
Value : val ,
VariablesReference : valref ,
}
children = append ( children , keyvar , valvar )
} else { // At least one is a scalar
kvvar := dap . Variable {
2021-01-14 18:53:12 +00:00
Name : key ,
EvaluateName : valexpr ,
Value : val ,
2020-08-11 15:34:27 +00:00
}
if keyref != 0 { // key is a type to be expanded
2021-01-08 17:17:54 +00:00
kvvar . Name = fmt . Sprintf ( "%s(%#x)" , kvvar . Name , keyv . Addr ) // Make the name unique
2020-08-11 15:34:27 +00:00
kvvar . VariablesReference = keyref
} else if valref != 0 { // val is a type to be expanded
kvvar . VariablesReference = valref
}
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-01-14 18:53:12 +00:00
cfqname := fmt . Sprintf ( "%s[%d]" , v . fullyQualifiedNameOrExpr , i )
cvalue , cvarref := s . convertVariable ( & v . Children [ i ] , cfqname )
2020-08-11 15:34:27 +00:00
children [ i ] = dap . Variable {
Name : fmt . Sprintf ( "[%d]" , i ) ,
2021-01-14 18:53:12 +00:00
EvaluateName : cfqname ,
Value : cvalue ,
VariablesReference : cvarref ,
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 )
2020-08-11 15:34:27 +00:00
children [ i ] = dap . Variable {
Name : c . Name ,
2021-01-14 18:53:12 +00:00
EvaluateName : cfqname ,
Value : cvalue ,
VariablesReference : cvarref ,
2020-08-11 15:34:27 +00:00
}
}
}
response := & dap . VariablesResponse {
Response : * newResponse ( request . Request ) ,
Body : dap . VariablesResponseBody { Variables : children } ,
}
s . send ( response )
}
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 ) {
return s . convertVariableWithOpts ( v , qualifiedNameOrExpr , false )
2020-11-12 23:24:31 +00:00
}
func ( s * Server ) convertVariableToString ( v * proc . Variable ) string {
2021-01-14 18:53:12 +00:00
val , _ := s . convertVariableWithOpts ( v , "" , true )
2020-11-12 23:24:31 +00:00
return val
}
2021-01-14 18:53:12 +00:00
// convertVariableWithOpts allows to skip reference generation in case all we need is
2020-11-12 23:24:31 +00:00
// a string representation of the variable.
2021-01-14 18:53:12 +00:00
func ( s * Server ) convertVariableWithOpts ( v * proc . Variable , qualifiedNameOrExpr string , skipRef bool ) ( value string , variablesReference int ) {
2020-11-12 23:24:31 +00:00
maybeCreateVariableHandle := func ( v * proc . Variable ) int {
if skipRef {
return 0
}
2021-01-14 18:53:12 +00:00
return s . variableHandles . create ( & fullyQualifiedVariable { v , qualifiedNameOrExpr , false /*not a scope*/ } )
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 {
2020-08-11 15:34:27 +00:00
return
}
2020-07-07 13:21:18 +00:00
typeName := api . PrettyTypeName ( v . DwarfType )
2021-02-23 16:29:06 +00:00
// As per https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables,
// some of the types might be fully or partially not loaded based on LoadConfig. For now, clearly
// communicate when values are not fully loaded. TODO(polina): look into loading on demand.
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 {
if v . Children [ 0 ] . OnlyAddr {
value = "(not loaded) " + value
2021-02-23 16:29:06 +00:00
} else {
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-03-15 16:36:46 +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
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 :
// 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-02-23 16:29:06 +00:00
if v . Children [ 0 ] . OnlyAddr { // Not fully loaded
2021-03-08 17:41:47 +00:00
// We might be loading variables from the frame that's not topmost, so use
2021-03-15 16:36:46 +00:00
// frame-independent address-based expression, not fully-qualified name.
loadExpr := fmt . Sprintf ( "*(*%q)(%#x)" , typeName , v . Addr )
2021-03-08 17:41:47 +00:00
s . log . Debugf ( "loading %s (type %s) with %s" , qualifiedNameOrExpr , typeName , loadExpr )
vLoaded , err := s . debugger . EvalVariableInScope ( - 1 , 0 , 0 , loadExpr , DefaultLoadConfig )
if err != nil {
value += fmt . Sprintf ( " - FAILED TO LOAD: %s" , err )
} else {
v . Children = vLoaded . Children
2021-03-15 16:36:46 +00:00
value = api . ConvertVar ( v ) . SinglelineString ( )
2021-03-08 17:41:47 +00:00
}
}
// Provide a reference to the child only if it fully loaded
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-03-15 16:36:46 +00:00
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
}
}
return
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 ) {
showErrorToUser := request . Arguments . Context != "watch"
if s . debugger == nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , "debugger is nil" , showErrorToUser )
return
}
// 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}
// 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 {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , "call is only supported with topmost stack frame" , showErrorToUser )
return
}
stateBeforeCall , err := s . debugger . State ( /*nowait*/ true )
if err != nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , err . Error ( ) , showErrorToUser )
return
}
state , err := s . debugger . Command ( & api . DebuggerCommand {
Name : api . Call ,
2021-02-23 16:29:06 +00:00
ReturnInfoLoadConfig : api . LoadConfigFromProc ( & DefaultLoadConfig ) ,
2020-11-12 23:24:31 +00:00
Expr : strings . Replace ( request . Arguments . Expression , "call " , "" , 1 ) ,
UnsafeCall : false ,
GoroutineID : goid ,
2021-03-08 18:05:10 +00:00
} , nil )
2020-11-12 23:24:31 +00:00
if _ , isexited := err . ( proc . ErrProcessExited ) ; isexited || err == nil && state . Exited {
e := & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) }
s . send ( e )
return
}
if err != nil {
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , err . Error ( ) , showErrorToUser )
return
}
// 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
for _ , t := range state . Threads {
if t . GoroutineID == stateBeforeCall . SelectedGoroutine . ID &&
2020-12-10 16:57:50 +00:00
t . Line == stateBeforeCall . SelectedGoroutine . CurrentLoc . Line && t . CallReturn {
2020-11-12 23:24:31 +00:00
// The call completed. Get the return values.
2021-02-23 16:29:06 +00:00
retVars , err = s . debugger . FindThreadReturnValues ( t . ID , 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
}
break
}
}
if retVars == nil {
// The call got interrupted by a stop (e.g. breakpoint in injected
// function call or in another goroutine)
s . resetHandlesForStop ( )
stopped := & dap . StoppedEvent { Event : * newEvent ( "stopped" ) }
stopped . Body . AllThreadsStopped = true
if state . SelectedGoroutine != nil {
stopped . Body . ThreadId = state . SelectedGoroutine . ID
}
stopped . Body . Reason = s . debugger . StopReason ( ) . String ( )
s . send ( stopped )
// 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.
s . sendErrorResponseWithOpts ( request . Request , UnableToEvaluateExpression , "Unable to evaluate expression" , "call stopped" , 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-01-14 18:53:12 +00:00
VariablesReference : s . variableHandles . create ( & fullyQualifiedVariable { retVarsAsVar , "" , false /*not a scope*/ } ) ,
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
}
2021-01-14 18:53:12 +00:00
// TODO(polina): as far as I can tell, evaluateName is ignored by vscode for expression variables.
// Should it be skipped alltogether for all levels?
exprVal , exprRef := s . convertVariable ( exprVar , fmt . Sprintf ( "(%s)" , request . Arguments . Expression ) )
2020-11-12 23:24:31 +00:00
response . Body = dap . EvaluateResponseBody { Result : exprVal , VariablesReference : exprRef }
}
s . send ( response )
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 )
}
// onSetFunctionBreakpointsRequest sends a not-yet-implemented error response.
// Capability 'supportsFunctionBreakpoints' is not set 'initialize' response.
func ( s * Server ) onSetFunctionBreakpointsRequest ( request * dap . SetFunctionBreakpointsRequest ) {
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 )
}
// onSetVariableRequest sends a not-yet-implemented error response.
// Capability 'supportsSetVariable' is not set 'initialize' response.
func ( s * Server ) onSetVariableRequest ( request * dap . SetVariableRequest ) { // TODO V0
s . sendNotYetImplementedErrorResponse ( request . Request )
}
// 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 )
}
2020-11-12 23:24:31 +00:00
// sendERrorResponseWithOpts offers configuration options.
// 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
2020-02-15 19:52:53 +00:00
s . log . Error ( er . Body . Error . Format )
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 )
s . log . Error ( er . Body . Error . Format )
s . send ( er )
}
2020-02-15 19:52:53 +00:00
func ( s * Server ) sendUnsupportedErrorResponse ( request dap . Request ) {
s . sendErrorResponse ( request , UnsupportedCommand , "Unsupported command" ,
fmt . Sprintf ( "cannot process '%s' request" , request . Command ) )
}
2020-05-01 19:24:44 +00:00
func ( s * Server ) sendNotYetImplementedErrorResponse ( request dap . Request ) {
s . sendErrorResponse ( request , NotYetImplemented , "Not yet implemented" ,
fmt . Sprintf ( "cannot process '%s' request" , request . Command ) )
}
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
2020-11-12 23:24:31 +00:00
func ( s * Server ) resetHandlesForStop ( ) {
s . stackFrameHandles . reset ( )
s . variableHandles . reset ( )
}
2020-08-24 17:21:51 +00:00
// doCommand runs a debugger command until it stops on
// termination, error, breakpoint, etc, when an appropriate
// event needs to be sent to the client.
func ( s * Server ) doCommand ( command string ) {
2020-02-15 19:52:53 +00:00
if s . debugger == nil {
return
}
2020-08-24 17:21:51 +00:00
2021-03-08 18:05:10 +00:00
state , err := s . debugger . Command ( & api . DebuggerCommand { Name : command } , nil )
2020-08-24 17:21:51 +00:00
if _ , isexited := err . ( proc . ErrProcessExited ) ; isexited || err == nil && state . Exited {
e := & dap . TerminatedEvent { Event : * newEvent ( "terminated" ) }
s . send ( e )
2020-02-15 19:52:53 +00:00
return
}
2020-08-24 17:21:51 +00:00
2020-11-12 23:24:31 +00:00
s . resetHandlesForStop ( )
2020-08-24 17:21:51 +00:00
stopped := & dap . StoppedEvent { Event : * newEvent ( "stopped" ) }
stopped . Body . AllThreadsStopped = true
if err == nil {
2020-10-07 15:24:40 +00:00
if state . SelectedGoroutine != nil {
stopped . Body . ThreadId = state . SelectedGoroutine . ID
}
switch s . debugger . StopReason ( ) {
case proc . StopNextFinished :
2020-08-24 17:21:51 +00:00
stopped . Body . Reason = "step"
default :
stopped . Body . Reason = "breakpoint"
}
2020-10-07 15:24:40 +00:00
if state . CurrentThread . Breakpoint != nil {
switch state . CurrentThread . Breakpoint . Name {
case proc . FatalThrow :
stopped . Body . Reason = "fatal error"
case proc . UnrecoveredPanic :
stopped . Body . Reason = "panic"
}
}
2020-08-24 17:21:51 +00:00
s . send ( stopped )
2020-02-15 19:52:53 +00:00
} else {
2020-08-24 17:21:51 +00:00
s . log . Error ( "runtime error: " , err )
stopped . Body . Reason = "runtime error"
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 {
stopped . Body . ThreadId = state . CurrentThread . GoroutineID
}
s . send ( stopped )
// TODO(polina): according to the spec, the extra 'text' is supposed to show up in the UI (e.g. on hover),
// but so far I am unable to get this to work in vscode - see https://github.com/microsoft/vscode/issues/104475.
// Options to explore:
// - supporting ExceptionInfo request
// - virtual variable scope for Exception that shows the message (details here: https://github.com/microsoft/vscode/issues/3101)
// In the meantime, provide the extra details by outputing an error message.
s . send ( & dap . OutputEvent {
Event : * newEvent ( "output" ) ,
Body : dap . OutputEventBody {
Output : fmt . Sprintf ( "ERROR: %s\n" , stopped . Body . Text ) ,
Category : "stderr" ,
} } )
2020-02-15 19:52:53 +00:00
}
}