service/dap: Initial implementation for 'dlv dap' (#1858)
* Initial implementation for 'dlv dap' * Fix Travis and AppVeyor failures * Address review comments * Address review comments * Regenrate documentation * Replace dap server printfs with log.Error * Update 'dap log' * Fix typos * Revert logflags changes that got mixed in by accident
This commit is contained in:
parent
8fa6d82177
commit
fbc4623c08
@ -38,6 +38,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
* [dlv attach](dlv_attach.md) - Attach to running process and begin debugging.
|
||||
* [dlv connect](dlv_connect.md) - Connect to a headless debug server.
|
||||
* [dlv core](dlv_core.md) - Examine a core dump.
|
||||
* [dlv dap](dlv_dap.md) - [EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
|
||||
* [dlv debug](dlv_debug.md) - Compile and begin debugging main package in current directory, or the package specified.
|
||||
* [dlv exec](dlv_exec.md) - Execute a precompiled binary, and begin a debug session.
|
||||
* [dlv replay](dlv_replay.md) - Replays a rr trace.
|
||||
|
||||
41
Documentation/usage/dlv_dap.md
Normal file
41
Documentation/usage/dlv_dap.md
Normal file
@ -0,0 +1,41 @@
|
||||
## dlv dap
|
||||
|
||||
[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
|
||||
|
||||
The server supports debugging of a precompiled binary akin to 'dlv exec' via a launch request.
|
||||
It does not yet support support specification of program arguments.
|
||||
It does not yet support launch requests with 'debug' and 'test' modes that require compilation.
|
||||
It does not yet support attach requests to debug a running process like with 'dlv attach'.
|
||||
It does not yet support asynchronous request-response communication.
|
||||
The server does not accept multiple client connections.
|
||||
|
||||
```
|
||||
dlv dap
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--api-version int Selects API version when headless. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
|
||||
--headless Run debug server only, in headless mode.
|
||||
--init string Init file, executed by the terminal client.
|
||||
-l, --listen string Debugging server listen address. (default "127.0.0.1:0")
|
||||
--log Enable debugging server logging.
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
--wd string Working directory for running the program. (default ".")
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
|
||||
|
||||
@ -17,6 +17,7 @@ names selected from this list:
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
dap Log all DAP messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
|
||||
@ -24,8 +25,8 @@ Additionally --log-dest can be used to specify where the logs should be
|
||||
written.
|
||||
If the argument is a number it will be interpreted as a file descriptor,
|
||||
otherwise as a file path.
|
||||
This option will also redirect the \"API listening\" message in headless
|
||||
mode.
|
||||
This option will also redirect the "server listening at" message in headless
|
||||
and dap modes.
|
||||
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import (
|
||||
"github.com/go-delve/delve/pkg/version"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/api"
|
||||
"github.com/go-delve/delve/service/dap"
|
||||
"github.com/go-delve/delve/service/rpc2"
|
||||
"github.com/go-delve/delve/service/rpccommon"
|
||||
"github.com/spf13/cobra"
|
||||
@ -152,6 +153,22 @@ option to let the process continue or kill it.
|
||||
}
|
||||
RootCommand.AddCommand(connectCommand)
|
||||
|
||||
// 'dap' subcommand.
|
||||
dapCommand := &cobra.Command{
|
||||
Use: "dap",
|
||||
Short: "[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).",
|
||||
Long: `[EXPERIMENTAL] Starts a TCP server communicating via Debug Adaptor Protocol (DAP).
|
||||
|
||||
The server supports debugging of a precompiled binary akin to 'dlv exec' via a launch request.
|
||||
It does not yet support support specification of program arguments.
|
||||
It does not yet support launch requests with 'debug' and 'test' modes that require compilation.
|
||||
It does not yet support attach requests to debug a running process like with 'dlv attach'.
|
||||
It does not yet support asynchronous request-response communication.
|
||||
The server does not accept multiple client connections.`,
|
||||
Run: dapCmd,
|
||||
}
|
||||
RootCommand.AddCommand(dapCommand)
|
||||
|
||||
// 'debug' subcommand.
|
||||
debugCommand := &cobra.Command{
|
||||
Use: "debug [package]",
|
||||
@ -318,6 +335,7 @@ names selected from this list:
|
||||
lldbout Copy output from debugserver/lldb to standard output
|
||||
debuglineerr Log recoverable errors reading .debug_line
|
||||
rpc Log all RPC messages
|
||||
dap Log all DAP messages
|
||||
fncall Log function call protocol
|
||||
minidump Log minidump loading
|
||||
|
||||
@ -325,8 +343,8 @@ Additionally --log-dest can be used to specify where the logs should be
|
||||
written.
|
||||
If the argument is a number it will be interpreted as a file descriptor,
|
||||
otherwise as a file path.
|
||||
This option will also redirect the \"API listening\" message in headless
|
||||
mode.
|
||||
This option will also redirect the "server listening at" message in headless
|
||||
and dap modes.
|
||||
|
||||
`,
|
||||
})
|
||||
@ -344,6 +362,63 @@ func remove(path string) {
|
||||
}
|
||||
}
|
||||
|
||||
func dapCmd(cmd *cobra.Command, args []string) {
|
||||
status := func() int {
|
||||
if err := logflags.Setup(Log, LogOutput, LogDest); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer logflags.Close()
|
||||
|
||||
if Headless {
|
||||
fmt.Fprintf(os.Stderr, "Warning: headless mode not supported with dap\n")
|
||||
}
|
||||
if AcceptMulti {
|
||||
fmt.Fprintf(os.Stderr, "Warning: accept multiclient mode not supported with dap\n")
|
||||
}
|
||||
if InitFile != "" {
|
||||
fmt.Fprint(os.Stderr, "Warning: init file ignored with dap\n")
|
||||
}
|
||||
if ContinueOnStart {
|
||||
fmt.Fprintf(os.Stderr, "Warning: continue ignored with dap; specify via launch/attach request instead\n")
|
||||
}
|
||||
if BuildFlags != "" {
|
||||
fmt.Fprintf(os.Stderr, "Warning: build flags ignored with dap; specify via launch/attach request instead\n")
|
||||
}
|
||||
if WorkingDir != "" {
|
||||
fmt.Fprintf(os.Stderr, "Warning: working directory ignored with dap; launch requests must specify full program path\n")
|
||||
}
|
||||
dlvArgs, targetArgs := splitArgs(cmd, args)
|
||||
if len(dlvArgs) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: debug arguments ignored with dap; specify via launch/attach request instead\n")
|
||||
}
|
||||
if len(targetArgs) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: program flags ignored with dap; specify via launch/attach request instead\n")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", Addr)
|
||||
if err != nil {
|
||||
fmt.Printf("couldn't start listener: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
disconnectChan := make(chan struct{})
|
||||
server := dap.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
Backend: Backend,
|
||||
Foreground: true, // always headless
|
||||
DebugInfoDirectories: conf.DebugInfoDirectories,
|
||||
CheckGoVersion: CheckGoVersion,
|
||||
DisconnectChan: disconnectChan,
|
||||
})
|
||||
defer server.Stop()
|
||||
|
||||
server.Run()
|
||||
waitForDisconnectSignal(disconnectChan)
|
||||
return 0
|
||||
}()
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func debugCmd(cmd *cobra.Command, args []string) {
|
||||
status := func() int {
|
||||
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
|
||||
@ -535,6 +610,37 @@ func connectCmd(cmd *cobra.Command, args []string) {
|
||||
os.Exit(connect(addr, nil, conf, executingOther))
|
||||
}
|
||||
|
||||
// waitForDisconnectSignal is a blocking function that waits for either
|
||||
// a SIGINT (Ctrl-C) signal from the OS or for disconnectChan to be closed
|
||||
// by the server when the client disconnects.
|
||||
// Note that in headless mode, the debugged process is foregrounded
|
||||
// (to have control of the tty for debugging interactive programs),
|
||||
// so SIGINT gets sent to the debuggee and not to delve.
|
||||
func waitForDisconnectSignal(disconnectChan chan struct{}) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT)
|
||||
if runtime.GOOS == "windows" {
|
||||
// On windows Ctrl-C sent to inferior process is delivered
|
||||
// as SIGINT to delve. Ignore it instead of stopping the server
|
||||
// in order to be able to debug signal handlers.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-disconnectChan:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-ch:
|
||||
case <-disconnectChan:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) {
|
||||
if cmd.ArgsLenAtDash() >= 0 {
|
||||
return args[:cmd.ArgsLenAtDash()], args[cmd.ArgsLenAtDash():]
|
||||
@ -678,28 +784,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
||||
client = rpc2.NewClient(listener.Addr().String())
|
||||
client.Disconnect(true) // true = continue after disconnect
|
||||
}
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT)
|
||||
if runtime.GOOS == "windows" {
|
||||
// On windows Ctrl-C sent to inferior process is delivered
|
||||
// as SIGINT to delve. Ignore it instead of stopping the server
|
||||
// in order to be able to debug signal handlers.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-disconnectChan:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-ch:
|
||||
case <-disconnectChan:
|
||||
}
|
||||
}
|
||||
waitForDisconnectSignal(disconnectChan)
|
||||
err = server.Stop()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.11
|
||||
require (
|
||||
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5
|
||||
github.com/cpuguy83/go-md2man v1.0.8 // indirect
|
||||
github.com/google/go-dap v0.1.0
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561
|
||||
|
||||
2
go.sum
2
go.sum
@ -8,6 +8,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-dap v0.1.0 h1:UzVzngq6yBR+bJVnTM8mjcdKrQnYR7m7U7JpNSnEo48=
|
||||
github.com/google/go-dap v0.1.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
|
||||
@ -21,6 +21,7 @@ var gdbWire = false
|
||||
var lldbServerOutput = false
|
||||
var debugLineErrors = false
|
||||
var rpc = false
|
||||
var dap = false
|
||||
var fnCall = false
|
||||
var minidump = false
|
||||
|
||||
@ -82,6 +83,16 @@ func RPCLogger() *logrus.Entry {
|
||||
return makeLogger(rpc, logrus.Fields{"layer": "rpc"})
|
||||
}
|
||||
|
||||
// DAP returns true if dap package should log.
|
||||
func DAP() bool {
|
||||
return dap
|
||||
}
|
||||
|
||||
// DAPLogger returns a logger for dap package.
|
||||
func DAPLogger() *logrus.Entry {
|
||||
return makeLogger(dap, logrus.Fields{"layer": "dap"})
|
||||
}
|
||||
|
||||
// FnCall returns true if the function call protocol should be logged.
|
||||
func FnCall() bool {
|
||||
return fnCall
|
||||
@ -100,12 +111,21 @@ func MinidumpLogger() *logrus.Entry {
|
||||
return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"})
|
||||
}
|
||||
|
||||
// WriteDAPListeningMessage writes the "DAP server listening" message in dap mode.
|
||||
func WriteDAPListeningMessage(addr string) {
|
||||
writeListeningMessage("DAP", addr)
|
||||
}
|
||||
|
||||
// WriteAPIListeningMessage writes the "API server listening" message in headless mode.
|
||||
func WriteAPIListeningMessage(addr string) {
|
||||
writeListeningMessage("API", addr)
|
||||
}
|
||||
|
||||
func writeListeningMessage(server string, addr string) {
|
||||
if logOut != nil {
|
||||
fmt.Fprintf(logOut, "API server listening at: %s\n", addr)
|
||||
fmt.Fprintf(logOut, "%s server listening at: %s\n", server, addr)
|
||||
} else {
|
||||
fmt.Printf("API server listening at: %s\n", addr)
|
||||
fmt.Printf("%s server listening at: %s\n", server, addr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,10 +171,14 @@ func Setup(logFlag bool, logstr string, logDest string) error {
|
||||
debugLineErrors = true
|
||||
case "rpc":
|
||||
rpc = true
|
||||
case "dap":
|
||||
dap = true
|
||||
case "fncall":
|
||||
fnCall = true
|
||||
case "minidump":
|
||||
minidump = true
|
||||
// If adding another value, do make sure to
|
||||
// update "Help about logging flags" in commands.go.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
189
service/dap/daptest/client.go
Normal file
189
service/dap/daptest/client.go
Normal file
@ -0,0 +1,189 @@
|
||||
package daptest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
// Package daptest provides a sample client with utilities
|
||||
// for DAP mode testing.
|
||||
|
||||
// Client is a debugger service client that uses Debug Adaptor Protocol.
|
||||
// It does not (yet?) implement service.Client interface.
|
||||
// All client methods are synchronous.
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
reader *bufio.Reader
|
||||
// seq is used to track the sequence number of each
|
||||
// requests that the client sends to the server
|
||||
seq int
|
||||
}
|
||||
|
||||
// NewClient creates a new Client over a TCP connection.
|
||||
// Call Close() to close the connection.
|
||||
func NewClient(addr string) *Client {
|
||||
fmt.Println("Connecting to server at:", addr)
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
c := &Client{conn: conn, reader: bufio.NewReader(conn)}
|
||||
return c
|
||||
}
|
||||
|
||||
// Close closes the client connection
|
||||
func (c *Client) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *Client) send(request dap.Message) {
|
||||
jsonmsg, _ := json.Marshal(request)
|
||||
fmt.Println("[client -> server]", string(jsonmsg))
|
||||
dap.WriteProtocolMessage(c.conn, request)
|
||||
}
|
||||
|
||||
// ReadBaseMessage reads and returns a json-encoded DAP message.
|
||||
func (c *Client) ReadBaseMessage() ([]byte, error) {
|
||||
message, err := dap.ReadBaseMessage(c.reader)
|
||||
if err != nil {
|
||||
fmt.Println("DAP client error:", err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Println("[client <- server]", string(message))
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// ReadErrorResponse reads, decodes and validates the result
|
||||
// to be an error response. Returns the response or an error.
|
||||
func (c *Client) ReadErrorResponse() (dap.Message, error) {
|
||||
response, err := dap.ReadProtocolMessage(c.reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch response.(type) {
|
||||
case *dap.ErrorResponse:
|
||||
return response, nil
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("not an ErrorResponse: %#v", response))
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeRequest sends an 'initialize' request.
|
||||
func (c *Client) InitializeRequest() {
|
||||
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
|
||||
request.Arguments = dap.InitializeRequestArguments{
|
||||
AdapterID: "go",
|
||||
PathFormat: "path",
|
||||
LinesStartAt1: true,
|
||||
ColumnsStartAt1: true,
|
||||
SupportsVariableType: true,
|
||||
SupportsVariablePaging: true,
|
||||
SupportsRunInTerminalRequest: true,
|
||||
Locale: "en-us",
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// LaunchRequest sends a 'launch' request.
|
||||
func (c *Client) LaunchRequest(program string, stopOnEntry bool) {
|
||||
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
|
||||
request.Arguments = map[string]interface{}{
|
||||
"request": "launch",
|
||||
"mode": "exec",
|
||||
"program": program,
|
||||
"stopOnEntry": stopOnEntry,
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// DisconnectRequest sends a 'disconnect' request.
|
||||
func (c *Client) DisconnectRequest() {
|
||||
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// SetBreakpointsRequest sends a 'setBreakpoints' request.
|
||||
func (c *Client) SetBreakpointsRequest(file string, lines []int) {
|
||||
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setBreakpoints")}
|
||||
request.Arguments = dap.SetBreakpointsArguments{
|
||||
Source: dap.Source{
|
||||
Name: filepath.Base(file),
|
||||
Path: file,
|
||||
},
|
||||
Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
|
||||
//sourceModified: false,
|
||||
}
|
||||
for i, l := range lines {
|
||||
request.Arguments.Breakpoints[i].Line = l
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// SetExceptionBreakpointsRequest sends a 'setExceptionBreakpoints' request.
|
||||
func (c *Client) SetExceptionBreakpointsRequest() {
|
||||
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setExceptionBreakpoints")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ConfigurationDoneRequest sends a 'configurationDone' request.
|
||||
func (c *Client) ConfigurationDoneRequest() {
|
||||
request := &dap.ConfigurationDoneRequest{Request: *c.newRequest("configurationDone")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ContinueRequest sends a 'continue' request.
|
||||
func (c *Client) ContinueRequest(thread int) {
|
||||
request := &dap.ContinueRequest{Request: *c.newRequest("continue")}
|
||||
request.Arguments.ThreadId = thread
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
|
||||
func (c *Client) UnkownRequest() {
|
||||
request := c.newRequest("unknown")
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// UnkownProtocolMessage triggers dap.DecodeProtocolMessageFieldError.
|
||||
func (c *Client) UnkownProtocolMessage() {
|
||||
m := &dap.ProtocolMessage{}
|
||||
m.Seq = -1
|
||||
m.Type = "unknown"
|
||||
c.send(m)
|
||||
}
|
||||
|
||||
// UnknownEvent triggers dap.DecodeProtocolMessageFieldError.
|
||||
func (c *Client) UnknownEvent() {
|
||||
event := &dap.Event{}
|
||||
event.Type = "event"
|
||||
event.Seq = -1
|
||||
event.Event = "unknown"
|
||||
c.send(event)
|
||||
}
|
||||
|
||||
// KnownEvent passes decode checks, but delve has no 'case' to
|
||||
// handle it. This behaves the same way a new request type
|
||||
// added to go-dap, but not to delve.
|
||||
func (c *Client) KnownEvent() {
|
||||
event := &dap.Event{}
|
||||
event.Type = "event"
|
||||
event.Seq = -1
|
||||
event.Event = "terminated"
|
||||
c.send(event)
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(command string) *dap.Request {
|
||||
request := &dap.Request{}
|
||||
request.Type = "request"
|
||||
request.Command = command
|
||||
request.Seq = c.seq
|
||||
c.seq++
|
||||
return request
|
||||
}
|
||||
15
service/dap/error_ids.go
Normal file
15
service/dap/error_ids.go
Normal file
@ -0,0 +1,15 @@
|
||||
package dap
|
||||
|
||||
// Unique identifiers for messages returned for errors from requests.
|
||||
const (
|
||||
UnsupportedCommand int = 9999
|
||||
|
||||
// The values below come from the vscode-go debug adaptor.
|
||||
// Although the spec says they should be unique, the adaptor
|
||||
// reuses 3000 for launch, attach and program exit failures.
|
||||
// TODO(polina): confirm if the extension expects specific ids
|
||||
// for specific cases, and we must match the existing adaptor
|
||||
// or if these codes can evolve.
|
||||
FailedToContinue = 3000
|
||||
// Add more codes as we support more requests
|
||||
)
|
||||
466
service/dap/server.go
Normal file
466
service/dap/server.go
Normal file
@ -0,0 +1,466 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
"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"
|
||||
"github.com/google/go-dap"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// stopOnEntry is set to automatically stop the debugee after start.
|
||||
stopOnEntry bool
|
||||
}
|
||||
|
||||
// NewServer creates a new DAP Server. It takes an opened Listener
|
||||
// via config and assumes its ownership. Optionally takes DisconnectChan
|
||||
// via config, which can be used to detect when the client disconnects
|
||||
// and the server is ready to be shut down. The caller must call
|
||||
// Stop() on shutdown.
|
||||
func NewServer(config *service.Config) *Server {
|
||||
logger := logflags.DAPLogger()
|
||||
logflags.WriteDAPListeningMessage(config.Listener.Addr().String())
|
||||
return &Server{
|
||||
config: config,
|
||||
listener: config.Listener,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *Server) Stop() {
|
||||
s.listener.Close()
|
||||
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 {
|
||||
kill := s.config.AttachPid == 0
|
||||
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() {
|
||||
// DisconnectChan might be nil at server creation if the
|
||||
// caller does not want to rely on the disconnect signal.
|
||||
if s.config.DisconnectChan != nil {
|
||||
close(s.config.DisconnectChan)
|
||||
// Take advantage of the nil check above to 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()
|
||||
s.config.DisconnectChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// This will print if the server is killed with Ctrl+C
|
||||
// before client connection is accepted.
|
||||
s.log.Errorf("Error accepting client connection: %s\n", err)
|
||||
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,
|
||||
// -- "use of closed network connection" means client connection
|
||||
// was closed via Stop() in response to a disconnect request.
|
||||
// -- "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 {
|
||||
if err != io.EOF {
|
||||
s.log.Error("DAP error: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// TODO(polina) Add a panic guard,
|
||||
// so we do not kill user's process when delve panics.
|
||||
s.handleRequest(request)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleRequest(request dap.Message) {
|
||||
jsonmsg, _ := json.Marshal(request)
|
||||
s.log.Debug("[<- from client]", string(jsonmsg))
|
||||
|
||||
switch request := request.(type) {
|
||||
case *dap.InitializeRequest:
|
||||
s.onInitializeRequest(request)
|
||||
case *dap.LaunchRequest:
|
||||
s.onLaunchRequest(request)
|
||||
case *dap.AttachRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.DisconnectRequest:
|
||||
s.onDisconnectRequest(request)
|
||||
case *dap.TerminateRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.RestartRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SetBreakpointsRequest:
|
||||
s.onSetBreakpointsRequest(request)
|
||||
case *dap.SetFunctionBreakpointsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SetExceptionBreakpointsRequest:
|
||||
s.onSetExceptionBreakpointsRequest(request)
|
||||
case *dap.ConfigurationDoneRequest:
|
||||
s.onConfigurationDoneRequest(request)
|
||||
case *dap.ContinueRequest:
|
||||
s.onContinueRequest(request)
|
||||
case *dap.NextRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.StepInRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.StepOutRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.StepBackRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.ReverseContinueRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.RestartFrameRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.GotoRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.PauseRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.StackTraceRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.ScopesRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.VariablesRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SetVariableRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SetExpressionRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SourceRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.ThreadsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.TerminateThreadsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.EvaluateRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.StepInTargetsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.GotoTargetsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.CompletionsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.ExceptionInfoRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.LoadedSourcesRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.DataBreakpointInfoRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.SetDataBreakpointsRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.ReadMemoryRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.DisassembleRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.CancelRequest:
|
||||
s.sendUnsupportedErrorResponse(request.Request)
|
||||
case *dap.BreakpointLocationsRequest:
|
||||
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
|
||||
// to handle. We should be sending an ErrorResponse, but
|
||||
// we cannot get to Seq and other fields from dap.Message.
|
||||
// TODO(polina): figure out how to handle this better.
|
||||
// Consider adding GetSeq() method to dap.Message interface.
|
||||
s.log.Errorf("Unable to process %#v\n", request)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// TODO(polina): support this to match vscode-go functionality
|
||||
response.Body.SupportsSetVariable = false
|
||||
s.send(response)
|
||||
}
|
||||
|
||||
func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
|
||||
// TODO(polina): Respond with an error if debug session is in progress?
|
||||
program, ok := request.Arguments["program"]
|
||||
if !ok || program == "" {
|
||||
s.sendErrorResponse(request.Request,
|
||||
FailedToContinue, "Failed to launch",
|
||||
"The program attribute is missing in debug configuration.")
|
||||
return
|
||||
}
|
||||
s.config.ProcessArgs = []string{program.(string)}
|
||||
s.config.WorkingDir = filepath.Dir(program.(string))
|
||||
// TODO: support program args
|
||||
|
||||
stop, ok := request.Arguments["stopOnEntry"]
|
||||
s.stopOnEntry = (ok && stop == true)
|
||||
|
||||
mode, ok := request.Arguments["mode"]
|
||||
if !ok || mode == "" {
|
||||
mode = "debug"
|
||||
}
|
||||
// TODO(polina): support "debug", "test" and "remote" modes
|
||||
if mode != "exec" {
|
||||
s.sendErrorResponse(request.Request,
|
||||
FailedToContinue, "Failed to launch",
|
||||
fmt.Sprintf("Unsupported 'mode' value '%s' in debug configuration.", mode))
|
||||
return
|
||||
}
|
||||
|
||||
config := &debugger.Config{
|
||||
WorkingDir: s.config.WorkingDir,
|
||||
AttachPid: 0,
|
||||
CoreFile: "",
|
||||
Backend: s.config.Backend,
|
||||
Foreground: s.config.Foreground,
|
||||
DebugInfoDirectories: s.config.DebugInfoDirectories,
|
||||
CheckGoVersion: s.config.CheckGoVersion,
|
||||
}
|
||||
var err error
|
||||
if s.debugger, err = debugger.New(config, s.config.ProcessArgs); err != nil {
|
||||
s.sendErrorResponse(request.Request,
|
||||
FailedToContinue, "Failed to launch", err.Error())
|
||||
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)
|
||||
}
|
||||
kill := s.config.AttachPid == 0
|
||||
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) {
|
||||
if request.Arguments.Source.Path == "" {
|
||||
s.log.Error("ERROR: Unable to set breakpoint for empty file path")
|
||||
}
|
||||
response := &dap.SetBreakpointsResponse{Response: *newResponse(request.Request)}
|
||||
response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints))
|
||||
// Only verified breakpoints will be set and reported back in the
|
||||
// response. All breakpoints resulting in errors (e.g. duplicates
|
||||
// or lines that do not have statements) will be skipped.
|
||||
i := 0
|
||||
for _, b := range request.Arguments.Breakpoints {
|
||||
bp, err := s.debugger.CreateBreakpoint(
|
||||
&api.Breakpoint{File: request.Arguments.Source.Path, Line: b.Line})
|
||||
if err != nil {
|
||||
s.log.Error("ERROR:", err)
|
||||
continue
|
||||
}
|
||||
response.Body.Breakpoints[i].Verified = true
|
||||
response.Body.Breakpoints[i].Line = bp.Line
|
||||
i++
|
||||
}
|
||||
response.Body.Breakpoints = response.Body.Breakpoints[:i]
|
||||
s.send(response)
|
||||
}
|
||||
|
||||
func (s *Server) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) {
|
||||
// Unlike what DAP documentation claims, this request is always sent
|
||||
// even though we specified no filters at initializatin. Handle as no-op.
|
||||
s.send(&dap.SetExceptionBreakpointsResponse{Response: *newResponse(request.Request)})
|
||||
}
|
||||
|
||||
func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) {
|
||||
if s.stopOnEntry {
|
||||
e := &dap.StoppedEvent{
|
||||
Event: *newEvent("stopped"),
|
||||
Body: dap.StoppedEventBody{Reason: "breakpoint", ThreadId: 1, AllThreadsStopped: true},
|
||||
}
|
||||
s.send(e)
|
||||
}
|
||||
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
|
||||
if !s.stopOnEntry {
|
||||
s.doContinue()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) onContinueRequest(request *dap.ContinueRequest) {
|
||||
s.send(&dap.ContinueResponse{Response: *newResponse(request.Request)})
|
||||
s.doContinue()
|
||||
}
|
||||
|
||||
func (s *Server) sendErrorResponse(request dap.Request, id int, summary string, details string) {
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *Server) sendUnsupportedErrorResponse(request dap.Request) {
|
||||
s.sendErrorResponse(request, UnsupportedCommand, "Unsupported command",
|
||||
fmt.Sprintf("cannot process '%s' request", request.Command))
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) doContinue() {
|
||||
if s.debugger == nil {
|
||||
return
|
||||
}
|
||||
state, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Continue})
|
||||
if err != nil {
|
||||
s.log.Error(err)
|
||||
switch err.(type) {
|
||||
case proc.ErrProcessExited:
|
||||
e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
|
||||
s.send(e)
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
if state.Exited {
|
||||
e := &dap.TerminatedEvent{Event: *newEvent("terminated")}
|
||||
s.send(e)
|
||||
} else {
|
||||
e := &dap.StoppedEvent{Event: *newEvent("stopped")}
|
||||
// TODO(polina): differentiate between breakpoint and pause on halt.
|
||||
e.Body.Reason = "breakpoint"
|
||||
e.Body.AllThreadsStopped = true
|
||||
e.Body.ThreadId = state.SelectedGoroutine.ID
|
||||
s.send(e)
|
||||
}
|
||||
}
|
||||
160
service/dap/server_test.go
Normal file
160
service/dap/server_test.go
Normal file
@ -0,0 +1,160 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-delve/delve/pkg/logflags"
|
||||
protest "github.com/go-delve/delve/pkg/proc/test"
|
||||
"github.com/go-delve/delve/service"
|
||||
"github.com/go-delve/delve/service/dap/daptest"
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var logOutput string
|
||||
flag.StringVar(&logOutput, "log-output", "", "configures log output")
|
||||
flag.Parse()
|
||||
logflags.Setup(logOutput != "", logOutput, "")
|
||||
os.Exit(protest.RunTestsWithFixtures(m))
|
||||
}
|
||||
|
||||
func startDAPServer(t *testing.T) (server *Server, addr string) {
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server = NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
Backend: "default",
|
||||
DisconnectChan: nil,
|
||||
})
|
||||
server.Run()
|
||||
// Give server time to start listening for clients
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return server, listener.Addr().String()
|
||||
}
|
||||
|
||||
func expectMessage(t *testing.T, client *daptest.Client, want []byte) {
|
||||
got, err := client.ReadBaseMessage()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("\ngot %q\nwant %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// name is for _fixtures/<name>.go
|
||||
func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
|
||||
var buildFlags protest.BuildFlags
|
||||
fixture := protest.BuildFixture(name, buildFlags)
|
||||
|
||||
server, addr := startDAPServer(t)
|
||||
client := daptest.NewClient(addr)
|
||||
defer client.Close()
|
||||
defer server.Stop()
|
||||
|
||||
test(client, fixture)
|
||||
}
|
||||
|
||||
// TODO(polina): instead of hardcoding message bytes,
|
||||
// add methods to client to receive, decode and verify responses.
|
||||
|
||||
func TestStopOnEntry(t *testing.T) {
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
client.InitializeRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))
|
||||
|
||||
client.LaunchRequest(fixture.Path, true /*stopOnEntry*/)
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))
|
||||
|
||||
client.SetExceptionBreakpointsRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setExceptionBreakpoints"}`))
|
||||
|
||||
client.ConfigurationDoneRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"configurationDone"}`))
|
||||
|
||||
client.ContinueRequest(1)
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"continue","body":{}}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))
|
||||
|
||||
client.DisconnectRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"disconnect"}`))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetBreakpoint(t *testing.T) {
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
client.InitializeRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":0,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true}}`))
|
||||
|
||||
client.LaunchRequest(fixture.Path, false /*stopOnEntry*/)
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"initialized"}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":1,"success":true,"command":"launch"}`))
|
||||
|
||||
client.SetBreakpointsRequest(fixture.Source, []int{8, 100})
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":2,"success":true,"command":"setBreakpoints","body":{"breakpoints":[{"verified":true,"source":{},"line":8}]}}`))
|
||||
|
||||
client.SetExceptionBreakpointsRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"setExceptionBreakpoints"}`))
|
||||
|
||||
client.ConfigurationDoneRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":4,"success":true,"command":"configurationDone"}`))
|
||||
|
||||
client.ContinueRequest(1)
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"stopped","body":{"reason":"breakpoint","threadId":1,"allThreadsStopped":true}}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":5,"success":true,"command":"continue","body":{}}`))
|
||||
|
||||
client.ContinueRequest(1)
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"event","event":"terminated","body":{}}`))
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":6,"success":true,"command":"continue","body":{}}`))
|
||||
|
||||
client.DisconnectRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":7,"success":true,"command":"disconnect"}`))
|
||||
})
|
||||
}
|
||||
|
||||
func expectErrorResponse(t *testing.T, client *daptest.Client, requestSeq int, command string, message string, id int) *dap.ErrorResponse {
|
||||
response, err := client.ReadErrorResponse()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return nil
|
||||
}
|
||||
got := response.(*dap.ErrorResponse)
|
||||
if got.RequestSeq != requestSeq || got.Command != command || got.Message != message || got.Body.Error.Id != id {
|
||||
want := fmt.Sprintf("{RequestSeq: %d, Command: %q, Message: %q, Id: %d}", requestSeq, command, message, id)
|
||||
t.Errorf("\ngot %#v\nwant %s", got, want)
|
||||
return nil
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
func TestBadLaunchRequests(t *testing.T) {
|
||||
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
client.LaunchRequest("", true)
|
||||
// Test for the DAP-specific detailed error message.
|
||||
want := "Failed to launch: The program attribute is missing in debug configuration."
|
||||
if got := expectErrorResponse(t, client, 0, "launch", "Failed to launch", 3000); got != nil && got.Body.Error.Format != want {
|
||||
t.Errorf("got %q, want %q", got.Body.Error.Format, want)
|
||||
}
|
||||
|
||||
// Skip detailed message checks for potentially different OS-specific errors.
|
||||
client.LaunchRequest(fixture.Path+"_does_not_exist", false)
|
||||
expectErrorResponse(t, client, 1, "launch", "Failed to launch", 3000)
|
||||
|
||||
client.LaunchRequest(fixture.Source, true) // Not an executable
|
||||
expectErrorResponse(t, client, 2, "launch", "Failed to launch", 3000)
|
||||
|
||||
// We failed to launch the program. Make sure shutdown still works.
|
||||
client.DisconnectRequest()
|
||||
expectMessage(t, client, []byte(`{"seq":0,"type":"response","request_seq":3,"success":true,"command":"disconnect"}`))
|
||||
})
|
||||
}
|
||||
12
vendor/github.com/google/go-dap/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/google/go-dap/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.13.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOPROXY=https://proxy.golang.org
|
||||
- GO111MODULE=on
|
||||
|
||||
script:
|
||||
- 'internal/test.sh'
|
||||
202
vendor/github.com/google/go-dap/LICENSE
generated
vendored
Normal file
202
vendor/github.com/google/go-dap/LICENSE
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
19
vendor/github.com/google/go-dap/README.md
generated
vendored
Normal file
19
vendor/github.com/google/go-dap/README.md
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# go-dap: Go implementation of the Debug Adapter Protocol
|
||||
|
||||
[](https://travis-ci.org/google/go-dap)
|
||||
[](https://goreportcard.com/report/github.com/google/go-dap)
|
||||
|
||||
For an overview of DAP, see
|
||||
https://microsoft.github.io/debug-adapter-protocol/overview
|
||||
|
||||
## Contributing
|
||||
|
||||
We'd love to accept your patches and contributions to this project. See
|
||||
[docs/contributing](https://github.com/google/go-dap/blob/master/docs/contributing.md)
|
||||
for more details.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0
|
||||
|
||||
This is not an officially supported Google product.
|
||||
219
vendor/github.com/google/go-dap/codec.go
generated
vendored
Normal file
219
vendor/github.com/google/go-dap/codec.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file contains utilities for decoding JSON-encoded bytes into DAP message.
|
||||
|
||||
package dap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DecodeProtocolMessageFieldError describes which JSON attribute
|
||||
// has an unsupported value that the decoding cannot handle.
|
||||
type DecodeProtocolMessageFieldError struct {
|
||||
SubType string
|
||||
FieldName string
|
||||
FieldValue string
|
||||
}
|
||||
|
||||
func (e *DecodeProtocolMessageFieldError) Error() string {
|
||||
return fmt.Sprintf("%s %s '%s' is not supported", e.SubType, e.FieldName, e.FieldValue)
|
||||
}
|
||||
|
||||
// DecodeProtocolMessage parses the JSON-encoded data and returns the result of
|
||||
// the appropriate type within the ProtocolMessage hierarchy. If message type,
|
||||
// command, etc cannot be cast, returns DecodeProtocolMessageFieldError.
|
||||
// See also godoc for json.Unmarshal, which is used for underlying decoding.
|
||||
func DecodeProtocolMessage(data []byte) (Message, error) {
|
||||
var protomsg ProtocolMessage
|
||||
if err := json.Unmarshal(data, &protomsg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch protomsg.Type {
|
||||
case "request":
|
||||
return decodeRequest(data)
|
||||
case "response":
|
||||
return decodeResponse(data)
|
||||
case "event":
|
||||
return decodeEvent(data)
|
||||
default:
|
||||
return nil, &DecodeProtocolMessageFieldError{"ProtocolMessage", "type", protomsg.Type}
|
||||
}
|
||||
}
|
||||
|
||||
type messageCtor func() Message
|
||||
|
||||
// decodeRequest determines what request type in the ProtocolMessage hierarchy
|
||||
// data corresponds to and uses json.Unmarshal to populate the corresponding
|
||||
// struct to be returned.
|
||||
func decodeRequest(data []byte) (Message, error) {
|
||||
var r Request
|
||||
if err := json.Unmarshal(data, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctor, ok := requestCtor[r.Command]; ok {
|
||||
requestPtr := ctor()
|
||||
err := json.Unmarshal(data, requestPtr)
|
||||
return requestPtr, err
|
||||
}
|
||||
return nil, &DecodeProtocolMessageFieldError{"Request", "command", r.Command}
|
||||
}
|
||||
|
||||
// Mapping of request commands and corresponding struct constructors that
|
||||
// can be passed to json.Unmarshal.
|
||||
var requestCtor = map[string]messageCtor{
|
||||
"cancel": func() Message { return &CancelRequest{} },
|
||||
"runInTerminal": func() Message { return &RunInTerminalRequest{} },
|
||||
"initialize": func() Message { return &InitializeRequest{} },
|
||||
"configurationDone": func() Message { return &ConfigurationDoneRequest{} },
|
||||
"launch": func() Message { return &LaunchRequest{} },
|
||||
"attach": func() Message { return &AttachRequest{} },
|
||||
"restart": func() Message { return &RestartRequest{} },
|
||||
"disconnect": func() Message { return &DisconnectRequest{} },
|
||||
"terminate": func() Message { return &TerminateRequest{} },
|
||||
"breakpointLocations": func() Message { return &BreakpointLocationsRequest{} },
|
||||
"setBreakpoints": func() Message { return &SetBreakpointsRequest{} },
|
||||
"setFunctionBreakpoints": func() Message { return &SetFunctionBreakpointsRequest{} },
|
||||
"setExceptionBreakpoints": func() Message { return &SetExceptionBreakpointsRequest{} },
|
||||
"dataBreakpointInfo": func() Message { return &DataBreakpointInfoRequest{} },
|
||||
"setDataBreakpoints": func() Message { return &SetDataBreakpointsRequest{} },
|
||||
"continue": func() Message { return &ContinueRequest{} },
|
||||
"next": func() Message { return &NextRequest{} },
|
||||
"stepIn": func() Message { return &StepInRequest{} },
|
||||
"stepOut": func() Message { return &StepOutRequest{} },
|
||||
"stepBack": func() Message { return &StepBackRequest{} },
|
||||
"reverseContinue": func() Message { return &ReverseContinueRequest{} },
|
||||
"restartFrame": func() Message { return &RestartFrameRequest{} },
|
||||
"goto": func() Message { return &GotoRequest{} },
|
||||
"pause": func() Message { return &PauseRequest{} },
|
||||
"stackTrace": func() Message { return &StackTraceRequest{} },
|
||||
"scopes": func() Message { return &ScopesRequest{} },
|
||||
"variables": func() Message { return &VariablesRequest{} },
|
||||
"setVariable": func() Message { return &SetVariableRequest{} },
|
||||
"source": func() Message { return &SourceRequest{} },
|
||||
"threads": func() Message { return &ThreadsRequest{} },
|
||||
"terminateThreads": func() Message { return &TerminateThreadsRequest{} },
|
||||
"modules": func() Message { return &ModulesRequest{} },
|
||||
"loadedSources": func() Message { return &LoadedSourcesRequest{} },
|
||||
"evaluate": func() Message { return &EvaluateRequest{} },
|
||||
"setExpression": func() Message { return &SetExpressionRequest{} },
|
||||
"stepInTargets": func() Message { return &StepInTargetsRequest{} },
|
||||
"gotoTargets": func() Message { return &GotoTargetsRequest{} },
|
||||
"completions": func() Message { return &CompletionsRequest{} },
|
||||
"exceptionInfo": func() Message { return &ExceptionInfoRequest{} },
|
||||
"readMemory": func() Message { return &ReadMemoryRequest{} },
|
||||
"disassemble": func() Message { return &DisassembleRequest{} },
|
||||
}
|
||||
|
||||
// decodeResponse determines what response type in the ProtocolMessage hierarchy
|
||||
// data corresponds to and uses json.Unmarshal to populate the corresponding
|
||||
// struct to be returned.
|
||||
func decodeResponse(data []byte) (Message, error) {
|
||||
var r Response
|
||||
if err := json.Unmarshal(data, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !r.Success {
|
||||
var er ErrorResponse
|
||||
err := json.Unmarshal(data, &er)
|
||||
return &er, err
|
||||
}
|
||||
if ctor, ok := responseCtor[r.Command]; ok {
|
||||
responsePtr := ctor()
|
||||
err := json.Unmarshal(data, responsePtr)
|
||||
return responsePtr, err
|
||||
}
|
||||
return nil, &DecodeProtocolMessageFieldError{"Response", "command", r.Command}
|
||||
}
|
||||
|
||||
// Mapping of response commands and corresponding struct constructors that
|
||||
// can be passed to json.Unmarshal.
|
||||
var responseCtor = map[string]messageCtor{
|
||||
"cancel": func() Message { return &CancelResponse{} },
|
||||
"runInTerminal": func() Message { return &RunInTerminalResponse{} },
|
||||
"initialize": func() Message { return &InitializeResponse{} },
|
||||
"configurationDone": func() Message { return &ConfigurationDoneResponse{} },
|
||||
"launch": func() Message { return &LaunchResponse{} },
|
||||
"attach": func() Message { return &AttachResponse{} },
|
||||
"restart": func() Message { return &RestartResponse{} },
|
||||
"disconnect": func() Message { return &DisconnectResponse{} },
|
||||
"terminate": func() Message { return &TerminateResponse{} },
|
||||
"breakpointLocations": func() Message { return &BreakpointLocationsResponse{} },
|
||||
"setBreakpoints": func() Message { return &SetBreakpointsResponse{} },
|
||||
"setFunctionBreakpoints": func() Message { return &SetFunctionBreakpointsResponse{} },
|
||||
"setExceptionBreakpoints": func() Message { return &SetExceptionBreakpointsResponse{} },
|
||||
"dataBreakpointInfo": func() Message { return &DataBreakpointInfoResponse{} },
|
||||
"setDataBreakpoints": func() Message { return &SetDataBreakpointsResponse{} },
|
||||
"continue": func() Message { return &ContinueResponse{} },
|
||||
"next": func() Message { return &NextResponse{} },
|
||||
"stepIn": func() Message { return &StepInResponse{} },
|
||||
"stepOut": func() Message { return &StepOutResponse{} },
|
||||
"stepBack": func() Message { return &StepBackResponse{} },
|
||||
"reverseContinue": func() Message { return &ReverseContinueResponse{} },
|
||||
"restartFrame": func() Message { return &RestartFrameResponse{} },
|
||||
"goto": func() Message { return &GotoResponse{} },
|
||||
"pause": func() Message { return &PauseResponse{} },
|
||||
"stackTrace": func() Message { return &StackTraceResponse{} },
|
||||
"scopes": func() Message { return &ScopesResponse{} },
|
||||
"variables": func() Message { return &VariablesResponse{} },
|
||||
"setVariable": func() Message { return &SetVariableResponse{} },
|
||||
"source": func() Message { return &SourceResponse{} },
|
||||
"threads": func() Message { return &ThreadsResponse{} },
|
||||
"terminateThreads": func() Message { return &TerminateThreadsResponse{} },
|
||||
"modules": func() Message { return &ModulesResponse{} },
|
||||
"loadedSources": func() Message { return &LoadedSourcesResponse{} },
|
||||
"evaluate": func() Message { return &EvaluateResponse{} },
|
||||
"setExpression": func() Message { return &SetExpressionResponse{} },
|
||||
"stepInTargets": func() Message { return &StepInTargetsResponse{} },
|
||||
"gotoTargets": func() Message { return &GotoTargetsResponse{} },
|
||||
"completions": func() Message { return &CompletionsResponse{} },
|
||||
"exceptionInfo": func() Message { return &ExceptionInfoResponse{} },
|
||||
"readMemory": func() Message { return &ReadMemoryResponse{} },
|
||||
"disassemble": func() Message { return &DisassembleResponse{} },
|
||||
}
|
||||
|
||||
// decodeEvent determines what event type in the ProtocolMessage hierarchy
|
||||
// data corresponds to and uses json.Unmarshal to populate the corresponding
|
||||
// struct to be returned.
|
||||
func decodeEvent(data []byte) (Message, error) {
|
||||
var e Event
|
||||
if err := json.Unmarshal(data, &e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctor, ok := eventCtor[e.Event]; ok {
|
||||
eventPtr := ctor()
|
||||
err := json.Unmarshal(data, eventPtr)
|
||||
return eventPtr, err
|
||||
}
|
||||
return nil, &DecodeProtocolMessageFieldError{"Event", "event", e.Event}
|
||||
}
|
||||
|
||||
// Mapping of event ids and corresponding struct constructors that
|
||||
// can be passed to json.Unmarshal.
|
||||
var eventCtor = map[string]messageCtor{
|
||||
"initialized": func() Message { return &InitializedEvent{} },
|
||||
"stopped": func() Message { return &StoppedEvent{} },
|
||||
"continued": func() Message { return &ContinuedEvent{} },
|
||||
"exited": func() Message { return &ExitedEvent{} },
|
||||
"terminated": func() Message { return &TerminatedEvent{} },
|
||||
"thread": func() Message { return &ThreadEvent{} },
|
||||
"output": func() Message { return &OutputEvent{} },
|
||||
"breakpoint": func() Message { return &BreakpointEvent{} },
|
||||
"module": func() Message { return &ModuleEvent{} },
|
||||
"loadedSource": func() Message { return &LoadedSourceEvent{} },
|
||||
"process": func() Message { return &ProcessEvent{} },
|
||||
"capabilities": func() Message { return &CapabilitiesEvent{} },
|
||||
}
|
||||
3
vendor/github.com/google/go-dap/go.mod
generated
vendored
Normal file
3
vendor/github.com/google/go-dap/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/google/go-dap
|
||||
|
||||
go 1.13
|
||||
137
vendor/github.com/google/go-dap/io.go
generated
vendored
Normal file
137
vendor/github.com/google/go-dap/io.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file contains utilities for DAP Base protocol I/O.
|
||||
// For additional information, see "Base protocol" section in
|
||||
// https://microsoft.github.io/debug-adapter-protocol/overview.
|
||||
|
||||
package dap
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BaseProtocolError represents base protocol error, which occurs when the raw
|
||||
// message does not conform to the header+content format of the base protocol.
|
||||
type BaseProtocolError struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (bpe *BaseProtocolError) Error() string { return bpe.Err }
|
||||
|
||||
var (
|
||||
// ErrHeaderDelimiterNotCrLfCrLf is returned when only partial header
|
||||
// delimiter \r\n\r\n is encountered.
|
||||
ErrHeaderDelimiterNotCrLfCrLf = &BaseProtocolError{fmt.Sprintf("header delimiter is not %q", crLfcrLf)}
|
||||
|
||||
// ErrHeaderNotContentLength is returned when the parsed header is
|
||||
// not of valid Content-Length format.
|
||||
ErrHeaderNotContentLength = &BaseProtocolError{fmt.Sprintf("header format is not %q", contentLengthHeaderRegex)}
|
||||
|
||||
// ErrHeaderContentTooLong is returned when the content length specified in
|
||||
// the header is above contentMaxLength.
|
||||
ErrHeaderContentTooLong = &BaseProtocolError{fmt.Sprintf("content length over %v bytes", contentMaxLength)}
|
||||
)
|
||||
|
||||
const (
|
||||
crLfcrLf = "\r\n\r\n"
|
||||
contentLengthHeaderFmt = "Content-Length: %d\r\n\r\n"
|
||||
contentMaxLength = 4 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
contentLengthHeaderRegex = regexp.MustCompile("^Content-Length: ([0-9]+)$")
|
||||
)
|
||||
|
||||
// WriteBaseMessage formats content with Content-Length header and delimiters
|
||||
// as per the base protocol and writes the resulting message to w.
|
||||
func WriteBaseMessage(w io.Writer, content []byte) error {
|
||||
header := fmt.Sprintf(contentLengthHeaderFmt, len(content))
|
||||
if _, err := w.Write([]byte(header)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := w.Write(content)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadBaseMessage reads one message from r consisting of a Content-Length
|
||||
// header and a content part. It parses the header to determine the size of
|
||||
// the content part and extracts and returns the actual content of the message.
|
||||
// Returns nil bytes on error, which can be one of the standard IO errors or
|
||||
// a BaseProtocolError defined in this package.
|
||||
func ReadBaseMessage(r *bufio.Reader) ([]byte, error) {
|
||||
contentLength, err := readContentLengthHeader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if contentLength > contentMaxLength {
|
||||
return nil, ErrHeaderContentTooLong
|
||||
}
|
||||
content := make([]byte, contentLength)
|
||||
if _, err = io.ReadFull(r, content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// readContentLengthHeader looks for the only header field that is supported
|
||||
// and required:
|
||||
// Content-Length: [0-9]+\r\n\r\n
|
||||
// Extracts and returns the content length.
|
||||
func readContentLengthHeader(r *bufio.Reader) (contentLength int, err error) {
|
||||
// Look for <some header>\r\n\r\n
|
||||
headerWithCr, err := r.ReadString('\r')
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nextThree := make([]byte, 3)
|
||||
if _, err = io.ReadFull(r, nextThree); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if string(nextThree) != "\n\r\n" {
|
||||
return 0, ErrHeaderDelimiterNotCrLfCrLf
|
||||
}
|
||||
|
||||
// If header is in the right format, get the length
|
||||
header := strings.TrimSuffix(headerWithCr, "\r")
|
||||
headerAndLength := contentLengthHeaderRegex.FindStringSubmatch(header)
|
||||
if len(headerAndLength) < 2 {
|
||||
return 0, ErrHeaderNotContentLength
|
||||
}
|
||||
return strconv.Atoi(headerAndLength[1])
|
||||
}
|
||||
|
||||
// WriteProtocolMessage encodes message and writes it to w.
|
||||
func WriteProtocolMessage(w io.Writer, message Message) error {
|
||||
b, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteBaseMessage(w, b)
|
||||
}
|
||||
|
||||
// ReadProtocolMessage reads a message from r, decodes and returns it.
|
||||
func ReadProtocolMessage(r *bufio.Reader) (Message, error) {
|
||||
content, err := ReadBaseMessage(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecodeProtocolMessage(content)
|
||||
}
|
||||
1589
vendor/github.com/google/go-dap/schematypes.go
generated
vendored
Normal file
1589
vendor/github.com/google/go-dap/schematypes.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user