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"
|
2020-02-15 19:52:53 +00:00
|
|
|
|
"path/filepath"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
"reflect"
|
2020-09-15 20:14:55 +00:00
|
|
|
|
"strings"
|
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-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.
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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-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() {
|
|
|
|
|
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.
|
|
|
|
|
s.conn.Close()
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
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
|
|
|
|
|
// TODO: implement this request in V0
|
|
|
|
|
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-05-01 19:24:44 +00:00
|
|
|
|
// Required - TODO
|
|
|
|
|
// TODO: implement this request in V0
|
|
|
|
|
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))
|
|
|
|
|
dap.WriteProtocolMessage(s.conn, message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-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)
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-04 17:22:51 +00:00
|
|
|
|
// Output path for the compiled binary in debug or test modes.
|
|
|
|
|
const debugBinary string = "./__debug_bin"
|
|
|
|
|
|
2020-02-15 19:52:53 +00:00
|
|
|
|
func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mode, ok := request.Arguments["mode"]
|
|
|
|
|
if !ok || mode == "" {
|
|
|
|
|
mode = "debug"
|
|
|
|
|
}
|
2020-03-04 17:22:51 +00:00
|
|
|
|
|
|
|
|
|
if mode == "debug" || mode == "test" {
|
|
|
|
|
output, ok := request.Arguments["output"].(string)
|
|
|
|
|
if !ok || output == "" {
|
|
|
|
|
output = debugBinary
|
|
|
|
|
}
|
|
|
|
|
debugname, err := filepath.Abs(output)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.sendInternalErrorResponse(request.Seq, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch mode {
|
|
|
|
|
case "debug":
|
|
|
|
|
err = gobuild.GoBuild(debugname, []string{program}, buildFlags)
|
|
|
|
|
case "test":
|
|
|
|
|
err = gobuild.GoTestBuild(debugname, []string{program}, buildFlags)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.sendErrorResponse(request.Request,
|
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
|
|
|
|
|
}
|
|
|
|
|
program = debugname
|
|
|
|
|
s.binaryToRemove = debugname
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(polina): support "remote" mode
|
|
|
|
|
if mode != "exec" && mode != "debug" && mode != "test" {
|
2020-02-15 19:52:53 +00:00
|
|
|
|
s.sendErrorResponse(request.Request,
|
2020-07-09 21:39:04 +00:00
|
|
|
|
FailedToLaunch, "Failed to launch",
|
2020-02-21 17:05:30 +00:00
|
|
|
|
fmt.Sprintf("Unsupported 'mode' value %q in debug configuration.", mode))
|
2020-02-15 19:52:53 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 20:14:55 +00:00
|
|
|
|
// If user-specified, overwrite the defaults for optional args.
|
|
|
|
|
stop, ok := request.Arguments["stopOnEntry"].(bool)
|
|
|
|
|
if ok {
|
|
|
|
|
s.args.stopOnEntry = stop
|
|
|
|
|
}
|
2020-07-08 17:20:05 +00:00
|
|
|
|
depth, ok := request.Arguments["stackTraceDepth"].(float64)
|
|
|
|
|
if ok && depth > 0 {
|
|
|
|
|
s.args.stackTraceDepth = int(depth)
|
|
|
|
|
}
|
2020-09-15 20:14:55 +00:00
|
|
|
|
globals, ok := request.Arguments["showGlobalVariables"].(bool)
|
|
|
|
|
if ok {
|
|
|
|
|
s.args.showGlobalVariables = globals
|
|
|
|
|
}
|
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
|
|
|
|
|
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)})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
|
_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt})
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.log.Error(err)
|
|
|
|
|
}
|
2020-04-13 18:07:15 +00:00
|
|
|
|
kill := s.config.Debugger.AttachPid == 0
|
2020-02-15 19:52:53 +00:00
|
|
|
|
err = s.debugger.Detach(kill)
|
|
|
|
|
if err != nil {
|
|
|
|
|
s.log.Error(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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 {
|
|
|
|
|
response.Body.Breakpoints[i].Line = got.Line
|
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-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
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-07 13:21:18 +00:00
|
|
|
|
s.debugger.LockTarget()
|
|
|
|
|
defer s.debugger.UnlockTarget()
|
|
|
|
|
|
2020-03-10 19:29:06 +00:00
|
|
|
|
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 {
|
|
|
|
|
for i, g := range gs {
|
|
|
|
|
threads[i].Id = g.ID
|
2020-07-07 13:21:18 +00:00
|
|
|
|
loc := g.UserCurrent()
|
|
|
|
|
if loc.Fn != nil {
|
|
|
|
|
threads[i].Name = loc.Fn.Name
|
2020-03-10 19:29:06 +00:00
|
|
|
|
} else {
|
|
|
|
|
threads[i].Name = fmt.Sprintf("%s@%d", loc.File, loc.Line)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
response := &dap.ThreadsResponse{
|
|
|
|
|
Response: *newResponse(request.Request),
|
|
|
|
|
Body: dap.ThreadsResponseBody{Threads: threads},
|
|
|
|
|
}
|
|
|
|
|
s.send(response)
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 19:24:44 +00:00
|
|
|
|
// onAttachRequest sends a not-yet-implemented error response.
|
|
|
|
|
// This is a mandatory request to support.
|
|
|
|
|
func (s *Server) onAttachRequest(request *dap.AttachRequest) { // TODO V0
|
|
|
|
|
s.sendNotYetImplementedErrorResponse(request.Request)
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
|
|
// This ingores threadId argument to match the original vscode-go implementation.
|
|
|
|
|
// 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) {
|
|
|
|
|
// This ingores threadId argument to match the original vscode-go implementation.
|
|
|
|
|
// 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) {
|
|
|
|
|
// This ingores threadId argument to match the original vscode-go implementation.
|
|
|
|
|
// 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})
|
|
|
|
|
stackFrames[i] = dap.StackFrame{Id: uniqueStackFrameID, Line: loc.Line}
|
2020-07-07 13:21:18 +00:00
|
|
|
|
if loc.Fn == nil {
|
|
|
|
|
stackFrames[i].Name = "???"
|
|
|
|
|
} else {
|
|
|
|
|
stackFrames[i].Name = loc.Fn.Name
|
|
|
|
|
}
|
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
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
// TODO(polina): Support setting config via launch/attach args
|
|
|
|
|
cfg := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
|
|
|
|
|
|
|
|
|
|
// Retrieve arguments
|
2020-07-07 13:21:18 +00:00
|
|
|
|
args, err := s.debugger.FunctionArguments(goid, frame, 0, cfg)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-07-07 13:21:18 +00:00
|
|
|
|
argScope := &proc.Variable{Name: "Arguments", Children: slicePtrVarToSliceVar(args)}
|
2020-08-11 15:34:27 +00:00
|
|
|
|
|
|
|
|
|
// Retrieve local variables
|
2020-07-07 13:21:18 +00:00
|
|
|
|
locals, err := s.debugger.LocalVariables(goid, frame, 0, cfg)
|
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
|
|
|
|
|
}
|
2020-07-07 13:21:18 +00:00
|
|
|
|
locScope := &proc.Variable{Name: "Locals", Children: slicePtrVarToSliceVar(locals)}
|
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)
|
2020-09-24 15:02:29 +00:00
|
|
|
|
globals, err := s.debugger.PackageVariables(currPkgFilter, cfg)
|
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+".")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
globScope := &proc.Variable{
|
|
|
|
|
Name: fmt.Sprintf("Globals (package %s)", currPkg),
|
|
|
|
|
Children: slicePtrVarToSliceVar(globals),
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
// TODO(polina): check and handle if variable loaded incompletely
|
|
|
|
|
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables
|
|
|
|
|
|
|
|
|
|
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.
|
2020-07-07 13:21:18 +00:00
|
|
|
|
key, keyref := s.convertVariable(&v.Children[i])
|
|
|
|
|
val, valref := s.convertVariable(&v.Children[i+1])
|
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),
|
|
|
|
|
Value: key,
|
|
|
|
|
VariablesReference: keyref,
|
|
|
|
|
}
|
|
|
|
|
valvar := dap.Variable{
|
|
|
|
|
Name: fmt.Sprintf("[val %d]", kvIndex),
|
|
|
|
|
Value: val,
|
|
|
|
|
VariablesReference: valref,
|
|
|
|
|
}
|
|
|
|
|
children = append(children, keyvar, valvar)
|
|
|
|
|
} else { // At least one is a scalar
|
|
|
|
|
kvvar := dap.Variable{
|
|
|
|
|
Name: key,
|
|
|
|
|
Value: val,
|
|
|
|
|
}
|
|
|
|
|
if keyref != 0 { // key is a type to be expanded
|
|
|
|
|
kvvar.Name = fmt.Sprintf("%s[%d]", kvvar.Name, kvIndex) // Make the name unique
|
|
|
|
|
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 {
|
|
|
|
|
c := &v.Children[i]
|
2020-08-11 15:34:27 +00:00
|
|
|
|
value, varref := s.convertVariable(c)
|
|
|
|
|
children[i] = dap.Variable{
|
|
|
|
|
Name: fmt.Sprintf("[%d]", i),
|
|
|
|
|
Value: value,
|
|
|
|
|
VariablesReference: varref,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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]
|
2020-08-11 15:34:27 +00:00
|
|
|
|
value, variablesReference := s.convertVariable(c)
|
|
|
|
|
children[i] = dap.Variable{
|
|
|
|
|
Name: c.Name,
|
|
|
|
|
Value: value,
|
|
|
|
|
VariablesReference: variablesReference,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
response := &dap.VariablesResponse{
|
|
|
|
|
Response: *newResponse(request.Request),
|
|
|
|
|
Body: dap.VariablesResponseBody{Variables: children},
|
|
|
|
|
// TODO(polina): support evaluateName field
|
|
|
|
|
}
|
|
|
|
|
s.send(response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convertVariable converts api.Variable to dap.Variable value and reference.
|
|
|
|
|
// Variable reference is used to keep track of the children associated with each
|
|
|
|
|
// variable. It is shared with the host via a scopes response and is an index to
|
|
|
|
|
// the s.variableHandles map, so it can be referenced from a subsequent variables
|
|
|
|
|
// request. 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).
|
2020-07-07 13:21:18 +00:00
|
|
|
|
func (s *Server) convertVariable(v *proc.Variable) (value string, variablesReference int) {
|
|
|
|
|
if v.Unreadable != nil {
|
|
|
|
|
value = fmt.Sprintf("unreadable <%v>", v.Unreadable)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2020-07-07 13:21:18 +00:00
|
|
|
|
typeName := api.PrettyTypeName(v.DwarfType)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
switch v.Kind {
|
|
|
|
|
case reflect.UnsafePointer:
|
|
|
|
|
if len(v.Children) == 0 {
|
|
|
|
|
value = "unsafe.Pointer(nil)"
|
|
|
|
|
} else {
|
|
|
|
|
value = fmt.Sprintf("unsafe.Pointer(%#x)", v.Children[0].Addr)
|
|
|
|
|
}
|
|
|
|
|
case reflect.Ptr:
|
2020-07-07 13:21:18 +00:00
|
|
|
|
if v.DwarfType == nil || len(v.Children) == 0 {
|
2020-08-11 15:34:27 +00:00
|
|
|
|
value = "nil"
|
|
|
|
|
} else if v.Children[0].Addr == 0 {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "nil <" + typeName + ">"
|
|
|
|
|
} else if v.Children[0].Kind == reflect.Invalid {
|
2020-08-11 15:34:27 +00:00
|
|
|
|
value = "void"
|
|
|
|
|
} else {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = fmt.Sprintf("<%s>(%#x)", typeName, v.Children[0].Addr)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
case reflect.Array:
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "<" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
if len(v.Children) > 0 {
|
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
case reflect.Slice:
|
|
|
|
|
if v.Base == 0 {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "nil <" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
} else {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = fmt.Sprintf("<%s> (length: %d, cap: %d)", typeName, v.Len, v.Cap)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
if len(v.Children) > 0 {
|
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case reflect.Map:
|
|
|
|
|
if v.Base == 0 {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "nil <" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
} else {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = fmt.Sprintf("<%s> (length: %d)", typeName, v.Len)
|
2020-08-11 15:34:27 +00:00
|
|
|
|
if len(v.Children) > 0 {
|
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case reflect.String:
|
2020-07-07 13:21:18 +00:00
|
|
|
|
vvalue := constant.StringVal(v.Value)
|
|
|
|
|
lenNotLoaded := v.Len - int64(len(vvalue))
|
2020-08-11 15:34:27 +00:00
|
|
|
|
if lenNotLoaded > 0 {
|
|
|
|
|
vvalue += fmt.Sprintf("...+%d more", lenNotLoaded)
|
|
|
|
|
}
|
|
|
|
|
value = fmt.Sprintf("%q", vvalue)
|
|
|
|
|
case reflect.Chan:
|
|
|
|
|
if len(v.Children) == 0 {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "nil <" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
} else {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "<" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
case reflect.Interface:
|
2020-09-09 19:47:18 +00:00
|
|
|
|
if v.Addr == 0 {
|
|
|
|
|
// An escaped interface variable that points to nil, this shouldn't
|
|
|
|
|
// happen in normal code but can happen if the variable is out of scope,
|
|
|
|
|
// such as if an interface variable has been captured by a
|
|
|
|
|
// closure and replaced by a pointer to interface, and the pointer
|
|
|
|
|
// happens to contain 0.
|
|
|
|
|
value = "nil"
|
|
|
|
|
} else if len(v.Children) == 0 || v.Children[0].Kind == reflect.Invalid && v.Children[0].Addr == 0 {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "nil <" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
} else {
|
2020-09-29 14:03:53 +00:00
|
|
|
|
value = "<" + typeName + "(" + v.Children[0].TypeString() + ")" + ">"
|
|
|
|
|
// TODO(polina): should we remove one level of indirection and skip "data"?
|
|
|
|
|
// Then we will have:
|
|
|
|
|
// Before:
|
|
|
|
|
// i: <interface{}(int)>
|
|
|
|
|
// data: 123
|
|
|
|
|
// After:
|
|
|
|
|
// i: <interface{}(int)> 123
|
|
|
|
|
// Before:
|
|
|
|
|
// i: <interface{}(main.MyStruct)>
|
|
|
|
|
// data: <main.MyStruct>
|
|
|
|
|
// field1: ...
|
|
|
|
|
// After:
|
|
|
|
|
// i: <interface{}(main.MyStruct)>
|
|
|
|
|
// field1: ...
|
2020-08-11 15:34:27 +00:00
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
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
|
2020-08-11 15:34:27 +00:00
|
|
|
|
default: // Struct, complex, scalar
|
2020-07-07 13:21:18 +00:00
|
|
|
|
vvalue := api.VariableValueAsString(v)
|
|
|
|
|
if vvalue != "" {
|
|
|
|
|
value = vvalue
|
2020-08-11 15:34:27 +00:00
|
|
|
|
} else {
|
2020-07-07 13:21:18 +00:00
|
|
|
|
value = "<" + typeName + ">"
|
2020-08-11 15:34:27 +00:00
|
|
|
|
}
|
|
|
|
|
if len(v.Children) > 0 {
|
|
|
|
|
variablesReference = s.variableHandles.create(v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
2020-05-01 19:24:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// onEvaluateRequest sends a not-yet-implemented error response.
|
|
|
|
|
// This is a mandatory request to support.
|
|
|
|
|
func (s *Server) onEvaluateRequest(request *dap.EvaluateRequest) { // TODO V0
|
|
|
|
|
s.sendNotYetImplementedErrorResponse(request.Request)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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-05-11 16:52:26 +00:00
|
|
|
|
func (s *Server) sendErrorResponse(request dap.Request, id int, summary, details string) {
|
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)
|
|
|
|
|
s.log.Error(er.Body.Error.Format)
|
|
|
|
|
s.send(er)
|
|
|
|
|
}
|
|
|
|
|
|
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]
|
|
|
|
|
Unable to propogate EXC_BAD_ACCESS signal to target process and panic (see https://github.com/go-delve/delve/issues/852)`
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
state, err := s.debugger.Command(&api.DebuggerCommand{Name: command})
|
|
|
|
|
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-07-01 18:01:17 +00:00
|
|
|
|
s.stackFrameHandles.reset()
|
2020-08-11 15:34:27 +00:00
|
|
|
|
s.variableHandles.reset()
|
2020-08-24 17:21:51 +00:00
|
|
|
|
|
|
|
|
|
stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
|
|
|
|
|
stopped.Body.AllThreadsStopped = true
|
|
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
stopped.Body.ThreadId = state.SelectedGoroutine.ID
|
|
|
|
|
switch command {
|
|
|
|
|
case api.Next, api.Step, api.StepOut:
|
|
|
|
|
stopped.Body.Reason = "step"
|
|
|
|
|
default:
|
|
|
|
|
stopped.Body.Reason = "breakpoint"
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
}
|