mirror of
https://github.com/go-delve/delve.git
synced 2025-10-30 10:17:03 +08:00
service/dap: support JSON-RPC and DAP on the same port from "dlv debug/exec/test/attach" (#2755)
* Support JSON-RPC and DAP on the same port * Fix test Co-authored-by: Polina Sokolova <polinasok@users.noreply.github.com>
This commit is contained in:
@ -24,8 +24,10 @@ import (
|
||||
"github.com/go-delve/delve/pkg/goversion"
|
||||
protest "github.com/go-delve/delve/pkg/proc/test"
|
||||
"github.com/go-delve/delve/pkg/terminal"
|
||||
"github.com/go-delve/delve/service/dap"
|
||||
"github.com/go-delve/delve/service/dap/daptest"
|
||||
"github.com/go-delve/delve/service/rpc2"
|
||||
godap "github.com/google/go-dap"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
@ -52,6 +54,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func assertNoError(err error, t testing.TB, s string) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
fname := filepath.Base(file)
|
||||
@ -647,8 +650,8 @@ func TestTypecheckRPC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestDap verifies that a dap server can be started and shut down.
|
||||
func TestDap(t *testing.T) {
|
||||
// TestDAPCmd verifies that a dap server can be started and shut down.
|
||||
func TestDAPCmd(t *testing.T) {
|
||||
const listenAddr = "127.0.0.1:40575"
|
||||
|
||||
dlvbin, tmpdir := getDlvBin(t)
|
||||
@ -691,8 +694,179 @@ func TestDap(t *testing.T) {
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
// TestDapWithClient tests dlv dap --client-addr can be started and shut down.
|
||||
func TestDapWithClient(t *testing.T) {
|
||||
func newDAPRemoteClient(t *testing.T, addr string) *daptest.Client {
|
||||
c := daptest.NewClient(addr)
|
||||
c.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
|
||||
c.ExpectInitializedEvent(t)
|
||||
c.ExpectAttachResponse(t)
|
||||
c.ConfigurationDoneRequest()
|
||||
c.ExpectStoppedEvent(t)
|
||||
c.ExpectConfigurationDoneResponse(t)
|
||||
return c
|
||||
}
|
||||
|
||||
func TestRemoteDAPClient(t *testing.T) {
|
||||
const listenAddr = "127.0.0.1:40576"
|
||||
|
||||
dlvbin, tmpdir := getDlvBin(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
|
||||
cmd := exec.Command(dlvbin, "debug", "--headless", "--log-output=dap", "--log", "--listen", listenAddr)
|
||||
cmd.Dir = buildtestdir
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
assertNoError(err, t, "stdout pipe")
|
||||
defer stdout.Close()
|
||||
stderr, err := cmd.StderrPipe()
|
||||
assertNoError(err, t, "stderr pipe")
|
||||
defer stderr.Close()
|
||||
assertNoError(cmd.Start(), t, "start headless instance")
|
||||
|
||||
scanOut := bufio.NewScanner(stdout)
|
||||
scanErr := bufio.NewScanner(stderr)
|
||||
// Wait for the debug server to start
|
||||
scanOut.Scan()
|
||||
t.Log(scanOut.Text())
|
||||
go func() { // Capture logging
|
||||
for scanErr.Scan() {
|
||||
t.Log(scanErr.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
client := newDAPRemoteClient(t, listenAddr)
|
||||
client.ContinueRequest(1)
|
||||
client.ExpectContinueResponse(t)
|
||||
client.ExpectTerminatedEvent(t)
|
||||
|
||||
client.DisconnectRequest()
|
||||
client.ExpectOutputEventProcessExited(t, 0)
|
||||
client.ExpectOutputEventDetaching(t)
|
||||
client.ExpectDisconnectResponse(t)
|
||||
client.ExpectTerminatedEvent(t)
|
||||
if _, err := client.ReadMessage(); err == nil {
|
||||
t.Error("expected read error upon shutdown")
|
||||
}
|
||||
client.Close()
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
func closeDAPRemoteMultiClient(t *testing.T, c *daptest.Client) {
|
||||
c.DisconnectRequest()
|
||||
c.ExpectOutputEventClosingClient(t)
|
||||
c.ExpectDisconnectResponse(t)
|
||||
c.ExpectTerminatedEvent(t)
|
||||
c.Close()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestRemoteDAPClientMulti(t *testing.T) {
|
||||
const listenAddr = "127.0.0.1:40577"
|
||||
|
||||
dlvbin, tmpdir := getDlvBin(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
|
||||
cmd := exec.Command(dlvbin, "debug", "--headless", "--accept-multiclient", "--log-output=debugger", "--log", "--listen", listenAddr)
|
||||
cmd.Dir = buildtestdir
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
assertNoError(err, t, "stdout pipe")
|
||||
defer stdout.Close()
|
||||
stderr, err := cmd.StderrPipe()
|
||||
assertNoError(err, t, "stderr pipe")
|
||||
defer stderr.Close()
|
||||
assertNoError(cmd.Start(), t, "start headless instance")
|
||||
|
||||
scanOut := bufio.NewScanner(stdout)
|
||||
scanErr := bufio.NewScanner(stderr)
|
||||
// Wait for the debug server to start
|
||||
scanOut.Scan()
|
||||
t.Log(scanOut.Text())
|
||||
go func() { // Capture logging
|
||||
for scanErr.Scan() {
|
||||
t.Log(scanErr.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
// Client 1 connects and continues to main.main
|
||||
dapclient := newDAPRemoteClient(t, listenAddr)
|
||||
dapclient.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}})
|
||||
dapclient.ExpectSetFunctionBreakpointsResponse(t)
|
||||
dapclient.ContinueRequest(1)
|
||||
dapclient.ExpectContinueResponse(t)
|
||||
dapclient.ExpectStoppedEvent(t)
|
||||
dapclient.CheckStopLocation(t, 1, "main.main", 5)
|
||||
closeDAPRemoteMultiClient(t, dapclient)
|
||||
|
||||
// Client 2 reconnects at main.main and continues to process exit
|
||||
dapclient2 := newDAPRemoteClient(t, listenAddr)
|
||||
dapclient2.CheckStopLocation(t, 1, "main.main", 5)
|
||||
dapclient2.ContinueRequest(1)
|
||||
dapclient2.ExpectContinueResponse(t)
|
||||
dapclient2.ExpectTerminatedEvent(t)
|
||||
closeDAPRemoteMultiClient(t, dapclient2)
|
||||
|
||||
// Attach to exited processs is an error
|
||||
dapclient3 := daptest.NewClient(listenAddr)
|
||||
dapclient3.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true})
|
||||
dapclient3.ExpectErrorResponseWith(t, dap.FailedToAttach, `Process \d+ has exited with status 0`, true)
|
||||
closeDAPRemoteMultiClient(t, dapclient3)
|
||||
|
||||
// But rpc clients can still connect and restart
|
||||
rpcclient := rpc2.NewClient(listenAddr)
|
||||
if _, err := rpcclient.Restart(false); err != nil {
|
||||
t.Errorf("error restarting with rpc client: %v", err)
|
||||
}
|
||||
if err := rpcclient.Detach(true); err != nil {
|
||||
t.Fatalf("error detaching from headless instance: %v", err)
|
||||
}
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
func TestRemoteDAPClientAfterContinue(t *testing.T) {
|
||||
const listenAddr = "127.0.0.1:40578"
|
||||
|
||||
dlvbin, tmpdir := getDlvBin(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
fixture := protest.BuildFixture("loopprog", 0)
|
||||
cmd := exec.Command(dlvbin, "exec", fixture.Path, "--headless", "--continue", "--accept-multiclient", "--log-output=debugger,dap", "--log", "--listen", listenAddr)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
assertNoError(err, t, "stdout pipe")
|
||||
defer stdout.Close()
|
||||
stderr, err := cmd.StderrPipe()
|
||||
assertNoError(err, t, "stderr pipe")
|
||||
defer stderr.Close()
|
||||
assertNoError(cmd.Start(), t, "start headless instance")
|
||||
|
||||
scanOut := bufio.NewScanner(stdout)
|
||||
scanErr := bufio.NewScanner(stderr)
|
||||
// Wait for the debug server to start
|
||||
scanOut.Scan() // "API server listening...""
|
||||
t.Log(scanOut.Text())
|
||||
// Wait for the program to start
|
||||
scanOut.Scan() // "past main"
|
||||
t.Log(scanOut.Text())
|
||||
|
||||
go func() { // Capture logging
|
||||
for scanErr.Scan() {
|
||||
t.Log(scanErr.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
c := newDAPRemoteClient(t, listenAddr)
|
||||
c.DisconnectRequestWithKillOption(true)
|
||||
c.ExpectOutputEventDetachingKill(t)
|
||||
c.ExpectDisconnectResponse(t)
|
||||
c.ExpectTerminatedEvent(t)
|
||||
if _, err := c.ReadMessage(); err == nil {
|
||||
t.Error("expected read error upon shutdown")
|
||||
}
|
||||
c.Close()
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
// TestDAPCmdWithClient tests dlv dap --client-addr can be started and shut down.
|
||||
func TestDAPCmdWithClient(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot setup listener required for testing: %v", err)
|
||||
|
||||
@ -88,11 +88,11 @@ func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
return er
|
||||
}
|
||||
|
||||
func (c *Client) expectErrorResponse(t *testing.T, id int, message string) *dap.ErrorResponse {
|
||||
func (c *Client) ExpectErrorResponseWith(t *testing.T, id int, message string, showUser bool) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
er := c.ExpectErrorResponse(t)
|
||||
if er.Body.Error.Id != id || er.Message != message {
|
||||
t.Errorf("\ngot %#v\nwant Id=%d Message=%q", er, id, message)
|
||||
if matched, _ := regexp.MatchString(message, er.Body.Error.Format); !matched || er.Body.Error.Id != id || er.Body.Error.ShowUser != showUser {
|
||||
t.Errorf("got %#v, want Id=%d Format=%q ShowUser=%v", er, id, message, showUser)
|
||||
}
|
||||
return er
|
||||
}
|
||||
@ -129,12 +129,12 @@ func pretty(v interface{}) string {
|
||||
|
||||
func (c *Client) ExpectNotYetImplementedErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
return c.expectErrorResponse(t, 7777, "Not yet implemented")
|
||||
return c.ExpectErrorResponseWith(t, 7777, "Not yet implemented", false)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectUnsupportedCommandErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
return c.expectErrorResponse(t, 9999, "Unsupported command")
|
||||
return c.ExpectErrorResponseWith(t, 9999, "Unsupported command", false)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventRegex(t *testing.T, want string) *dap.OutputEvent {
|
||||
@ -173,6 +173,27 @@ func (c *Client) ExpectOutputEventTerminating(t *testing.T) *dap.OutputEvent {
|
||||
return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventClosingClient(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, `Closing client session, but leaving multi-client DAP server running at .+:[0-9]+\n`)
|
||||
}
|
||||
|
||||
func (c *Client) CheckStopLocation(t *testing.T, thread int, name string, line int) {
|
||||
t.Helper()
|
||||
c.StackTraceRequest(thread, 0, 20)
|
||||
st := c.ExpectStackTraceResponse(t)
|
||||
if len(st.Body.StackFrames) < 1 {
|
||||
t.Errorf("\ngot %#v\nwant len(stackframes) => 1", st)
|
||||
} else {
|
||||
if line != -1 && st.Body.StackFrames[0].Line != line {
|
||||
t.Errorf("\ngot %#v\nwant Line=%d", st, line)
|
||||
}
|
||||
if st.Body.StackFrames[0].Name != name {
|
||||
t.Errorf("\ngot %#v\nwant Name=%q", st, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeRequest sends an 'initialize' request.
|
||||
func (c *Client) InitializeRequest() {
|
||||
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
|
||||
|
||||
@ -166,9 +166,9 @@ type Config struct {
|
||||
|
||||
// log is used for structured logging.
|
||||
log *logrus.Entry
|
||||
// stopTriggered is closed when the server is Stop()-ed.
|
||||
// StopTriggered is closed when the server is Stop()-ed.
|
||||
// Can be used to safeguard against duplicate shutdown sequences.
|
||||
stopTriggered chan struct{}
|
||||
StopTriggered chan struct{}
|
||||
}
|
||||
|
||||
type process struct {
|
||||
@ -277,7 +277,7 @@ func NewServer(config *service.Config) *Server {
|
||||
config: &Config{
|
||||
Config: config,
|
||||
log: logger,
|
||||
stopTriggered: make(chan struct{}),
|
||||
StopTriggered: make(chan struct{}),
|
||||
},
|
||||
listener: config.Listener,
|
||||
}
|
||||
@ -286,7 +286,7 @@ func NewServer(config *service.Config) *Server {
|
||||
// NewSession creates a new client session that can handle DAP traffic.
|
||||
// It takes an open connection and provides a Close() method to shut it
|
||||
// down when the DAP session disconnects or a connection error occurs.
|
||||
func NewSession(conn io.ReadWriteCloser, config *Config) *Session {
|
||||
func NewSession(conn io.ReadWriteCloser, config *Config, debugger *debugger.Debugger) *Session {
|
||||
if config.log == nil {
|
||||
config.log = logflags.DAPLogger()
|
||||
}
|
||||
@ -298,6 +298,7 @@ func NewSession(conn io.ReadWriteCloser, config *Config) *Session {
|
||||
variableHandles: newVariablesHandlesMap(),
|
||||
args: defaultArgs,
|
||||
exceptionErr: nil,
|
||||
debugger: debugger,
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,11 +329,11 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
||||
// connection. It shuts down the underlying debugger and kills the target
|
||||
// process if it was launched by it or stops the noDebug process.
|
||||
// This method mustn't be called more than once.
|
||||
// stopTriggered notifies other goroutines that stop is in progreess.
|
||||
// StopTriggered notifies other goroutines that stop is in progreess.
|
||||
func (s *Server) Stop() {
|
||||
s.config.log.Debug("DAP server stopping...")
|
||||
defer s.config.log.Debug("DAP server stopped")
|
||||
close(s.config.stopTriggered)
|
||||
close(s.config.StopTriggered)
|
||||
|
||||
if s.listener != nil {
|
||||
// If run goroutine is blocked on accept, this will unblock it.
|
||||
@ -365,7 +366,7 @@ func (s *Session) Close() {
|
||||
}
|
||||
// Close client connection last, so other shutdown stages
|
||||
// can send client notifications.
|
||||
// Unless Stop() was called after read loop in serveDAPCodec()
|
||||
// Unless Stop() was called after read loop in ServeDAPCodec()
|
||||
// returned, this will result in a closed connection error
|
||||
// on next read, breaking out the read loop andd
|
||||
// allowing the run goroutinee to exit.
|
||||
@ -387,8 +388,8 @@ func (c *Config) triggerServerStop() {
|
||||
// -- run goroutine: calls triggerServerStop()
|
||||
// -- main goroutine: calls Stop()
|
||||
// -- main goroutine: Stop() closes client connection (or client closed it)
|
||||
// -- run goroutine: serveDAPCodec() gets "closed network connection"
|
||||
// -- run goroutine: serveDAPCodec() returns and calls triggerServerStop()
|
||||
// -- run goroutine: ServeDAPCodec() gets "closed network connection"
|
||||
// -- run goroutine: ServeDAPCodec() returns and calls triggerServerStop()
|
||||
if c.DisconnectChan != nil {
|
||||
close(c.DisconnectChan)
|
||||
c.DisconnectChan = nil
|
||||
@ -418,7 +419,7 @@ func (s *Server) Run() {
|
||||
conn, err := s.listener.Accept() // listener is closed in Stop()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-s.config.stopTriggered:
|
||||
case <-s.config.StopTriggered:
|
||||
default:
|
||||
s.config.log.Errorf("Error accepting client connection: %s\n", err)
|
||||
s.config.triggerServerStop()
|
||||
@ -438,9 +439,9 @@ func (s *Server) Run() {
|
||||
|
||||
func (s *Server) runSession(conn io.ReadWriteCloser) {
|
||||
s.sessionMu.Lock()
|
||||
s.session = NewSession(conn, s.config) // closed in Stop()
|
||||
s.session = NewSession(conn, s.config, nil) // closed in Stop()
|
||||
s.sessionMu.Unlock()
|
||||
s.session.serveDAPCodec()
|
||||
s.session.ServeDAPCodec()
|
||||
}
|
||||
|
||||
// RunWithClient is similar to Run but works only with an already established
|
||||
@ -456,10 +457,10 @@ func (s *Server) RunWithClient(conn net.Conn) {
|
||||
go s.runSession(conn)
|
||||
}
|
||||
|
||||
// serveDAPCodec reads and decodes requests from the client
|
||||
// ServeDAPCodec reads and decodes requests from the client
|
||||
// until it encounters an error or EOF, when it sends
|
||||
// a disconnect signal and returns.
|
||||
func (s *Session) serveDAPCodec() {
|
||||
func (s *Session) ServeDAPCodec() {
|
||||
// TODO(polina): defer-close conn/session like in serveJSONCodec
|
||||
reader := bufio.NewReader(s.conn)
|
||||
for {
|
||||
@ -475,7 +476,7 @@ func (s *Session) serveDAPCodec() {
|
||||
if err != nil {
|
||||
s.config.log.Debug("DAP error: ", err)
|
||||
select {
|
||||
case <-s.config.stopTriggered:
|
||||
case <-s.config.StopTriggered:
|
||||
default:
|
||||
if err != io.EOF { // EOF means client closed connection
|
||||
if decodeErr, ok := err.(*dap.DecodeProtocolMessageFieldError); ok {
|
||||
@ -1238,10 +1239,12 @@ func (s *Session) stopDebugSession(killProcess bool) error {
|
||||
// halt sends a halt request if the debuggee is running.
|
||||
// changeStateMu should be held when calling (*Server).halt.
|
||||
func (s *Session) halt() (*api.DebuggerState, error) {
|
||||
s.config.log.Debug("halting")
|
||||
// Only send a halt request if the debuggee is running.
|
||||
if s.debugger.IsRunning() {
|
||||
return s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
|
||||
}
|
||||
s.config.log.Debug("process not running")
|
||||
return s.debugger.State(false)
|
||||
}
|
||||
|
||||
|
||||
@ -4598,23 +4598,6 @@ func TestFatalThrowBreakpoint(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func verifyStopLocation(t *testing.T, client *daptest.Client, thread int, name string, line int) {
|
||||
t.Helper()
|
||||
|
||||
client.StackTraceRequest(thread, 0, 20)
|
||||
st := client.ExpectStackTraceResponse(t)
|
||||
if len(st.Body.StackFrames) < 1 {
|
||||
t.Errorf("\ngot %#v\nwant len(stackframes) => 1", st)
|
||||
} else {
|
||||
if line != -1 && st.Body.StackFrames[0].Line != line {
|
||||
t.Errorf("\ngot %#v\nwant Line=%d", st, line)
|
||||
}
|
||||
if st.Body.StackFrames[0].Name != name {
|
||||
t.Errorf("\ngot %#v\nwant Name=%q", st, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkStop covers the standard sequence of requests issued by
|
||||
// a client at a breakpoint or another non-terminal stop event.
|
||||
// The details have been tested by other tests,
|
||||
@ -4625,7 +4608,7 @@ func checkStop(t *testing.T, client *daptest.Client, thread int, fname string, l
|
||||
client.ThreadsRequest()
|
||||
client.ExpectThreadsResponse(t)
|
||||
|
||||
verifyStopLocation(t, client, thread, fname, line)
|
||||
client.CheckStopLocation(t, thread, fname, line)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
client.ExpectScopesResponse(t)
|
||||
@ -5150,7 +5133,7 @@ func TestPauseAndContinue(t *testing.T) {
|
||||
fixture.Source, []int{6},
|
||||
[]onBreakpoint{{
|
||||
execute: func() {
|
||||
verifyStopLocation(t, client, 1, "main.loop", 6)
|
||||
client.CheckStopLocation(t, 1, "main.loop", 6)
|
||||
|
||||
// Continue resumes all goroutines, so thread id is ignored
|
||||
client.ContinueRequest(12345)
|
||||
@ -5967,7 +5950,6 @@ func runTestWithDebugger(t *testing.T, dbg *debugger.Debugger, test func(c *dapt
|
||||
server, _ := startDAPServer(t, serverStopped)
|
||||
client := daptest.NewClient(server.listener.Addr().String())
|
||||
time.Sleep(100 * time.Millisecond) // Give time for connection to be set as dap.Session
|
||||
// TODO(polina): update once the server interface is refactored to take debugger as arg
|
||||
server.sessionMu.Lock()
|
||||
if server.session == nil {
|
||||
t.Fatal("DAP session is not ready")
|
||||
@ -6085,7 +6067,6 @@ func TestAttachRemoteMultiClient(t *testing.T) {
|
||||
// DAP server doesn't support accept-multiclient, but we can use this
|
||||
// hack to test the inner connection logic that can be used by a server that does.
|
||||
server.session.config.AcceptMulti = true
|
||||
// TODO(polina): update once the server interface is refactored to take debugger as arg
|
||||
_, server.session.debugger = launchDebuggerWithTargetHalted(t, "increment")
|
||||
server.sessionMu.Unlock()
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package rpccommon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -19,6 +20,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/debugger"
|
||||
"github.com/go-delve/delve/service/internal/sameuser"
|
||||
"github.com/go-delve/delve/service/rpc1"
|
||||
@ -156,7 +158,7 @@ func (s *ServerImpl) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
go s.serveJSONCodec(c)
|
||||
go s.serveConnectionDemux(c)
|
||||
if !s.config.AcceptMulti {
|
||||
break
|
||||
}
|
||||
@ -165,6 +167,28 @@ func (s *ServerImpl) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type bufReadWriteCloser struct {
|
||||
*bufio.Reader
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
func (s *ServerImpl) serveConnectionDemux(c io.ReadWriteCloser) {
|
||||
conn := &bufReadWriteCloser{bufio.NewReader(c), c}
|
||||
b, err := conn.Peek(1)
|
||||
if err != nil {
|
||||
s.log.Warnf("error determining new connection protocol: %v", err)
|
||||
return
|
||||
}
|
||||
if b[0] == 'C' { // C is for DAP's Content-Length
|
||||
s.log.Debugf("serving DAP on new connection")
|
||||
ds := dap.NewSession(conn, &dap.Config{Config: s.config, StopTriggered: s.stopChan}, s.debugger)
|
||||
go ds.ServeDAPCodec()
|
||||
} else {
|
||||
s.log.Debugf("serving JSON-RPC on new connection")
|
||||
go s.serveJSONCodec(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Precompute the reflect type for error. Can't use error directly
|
||||
// because Typeof takes an empty interface value. This is annoying.
|
||||
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
Reference in New Issue
Block a user