Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/dlv: add --client-addr flag to run dap with a predefined client #2568

Merged
merged 11 commits into from
Oct 13, 2021
7 changes: 6 additions & 1 deletion Documentation/usage/dlv_dap.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ The server does not yet accept multiple client connections (--accept-multiclient
While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
execution is resumed at the start of the debug session.

The --client-addr flag is a special flag that makes the server initiate a debug session
by dialing in to the host:port where a DAP client is waiting. This server process
will exit when the debug session ends.

```
dlv dap [flags]
```

### Options

```
-h, --help help for dap
--client-addr string host:port where the DAP client is waiting for the DAP server to dial in
-h, --help help for dap
```

### Options inherited from parent commands
Expand Down
42 changes: 32 additions & 10 deletions cmd/dlv/cmds/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ var (
// disableASLR is used to disable ASLR
disableASLR bool

// dapClientAddr is dap subcommand's flag that specifies the address of a DAP client.
// If it is specified, the dap server starts a debug session by dialing to the client.
// The dap server will serve only for the debug session.
dapClientAddr string

// backend selection
backend string

Expand Down Expand Up @@ -191,9 +196,15 @@ Program and output binary paths will be interpreted relative to dlv's working di

The server does not yet accept multiple client connections (--accept-multiclient).
While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
execution is resumed at the start of the debug session.`,
execution is resumed at the start of the debug session.

The --client-addr flag is a special flag that makes the server initiate a debug session
by dialing in to the host:port where a DAP client is waiting. This server process
will exit when the debug session ends.`,
Run: dapCmd,
}
dapCommand.Flags().StringVar(&dapClientAddr, "client-addr", "", "host:port where the DAP client is waiting for the DAP server to dial in")
polinasok marked this conversation as resolved.
Show resolved Hide resolved

// TODO(polina): support --tty when dlv dap allows to launch a program from command-line
rootCommand.AddCommand(dapCommand)

Expand Down Expand Up @@ -451,14 +462,8 @@ func dapCmd(cmd *cobra.Command, args []string) {
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,
config := &service.Config{
DisconnectChan: disconnectChan,
Debugger: debugger.Config{
Backend: backend,
Expand All @@ -467,9 +472,26 @@ func dapCmd(cmd *cobra.Command, args []string) {
CheckGoVersion: checkGoVersion,
},
CheckLocalConnUser: checkLocalConnUser,
})
defer server.Stop()
}
var conn net.Conn
if dapClientAddr == "" {
listener, err := net.Listen("tcp", addr)
if err != nil {
fmt.Printf("couldn't start listener: %s\n", err)
return 1
}
config.Listener = listener
} else { // with a predetermined client.
var err error
conn, err = net.Dial("tcp", dapClientAddr)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to the DAP client: %v\n", err)
return 1
}
}

server := dap.NewServer(config, conn)
defer server.Stop()
server.Run()
waitForDisconnectSignal(disconnectChan)
return 0
Expand Down
43 changes: 43 additions & 0 deletions cmd/dlv/dlv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go/types"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"os/user"
Expand Down Expand Up @@ -672,6 +673,48 @@ func TestDap(t *testing.T) {
cmd.Wait()
}

// TestDapWithClient tests dlv dap --client-addr can be started and shut down.
func TestDapWithClient(t *testing.T) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("cannot setup listener required for testing: %v", err)
}
defer listener.Close()

dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)

cmd := exec.Command(dlvbin, "dap", "--log-output=dap", "--log", "--client-addr", listener.Addr().String())
buf := &bytes.Buffer{}
cmd.Stdin = buf
cmd.Stdout = buf
assertNoError(cmd.Start(), t, "start dlv dap process with --client-addr flag")

// Wait for the connection.
conn, err := listener.Accept()
if err != nil {
cmd.Process.Kill() // release the port
t.Fatalf("Failed to get connection: %v", err)
}
t.Log("dlv dap process dialed in successfully")

client := daptest.NewClientFromConn(conn)
client.InitializeRequest()
client.ExpectInitializeResponse(t)

// Close the connection.
if err := conn.Close(); err != nil {
cmd.Process.Kill()
t.Fatalf("Failed to get connection: %v", err)
}

// Connection close should trigger dlv-reverse command's normal exit.
if err := cmd.Wait(); err != nil {
cmd.Process.Kill()
t.Fatalf("command failed: %v\n%s\n%v", err, buf.Bytes(), cmd.Process.Pid)
}
}

func TestTrace(t *testing.T) {
dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)
Expand Down
6 changes: 6 additions & 0 deletions service/dap/daptest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ func NewClient(addr string) *Client {
if err != nil {
log.Fatal("dialing:", err)
}
return NewClientFromConn(conn)
}

// NewClientFromConn creates a new Client with the given TCP connection.
// Call Close to close the connection.
func NewClientFromConn(conn net.Conn) *Client {
c := &Client{conn: conn, reader: bufio.NewReader(conn)}
c.seq = 1 // match VS Code numbering
return c
Expand Down
47 changes: 41 additions & 6 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ 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.
// When working with a predetermined client, this is nil.
listener net.Listener
// stopTriggered is closed when the server is Stop()-ed.
stopTriggered chan struct{}
Expand Down Expand Up @@ -202,15 +203,34 @@ var (
maxGoroutines = 1 << 10
)

// NewServer creates a new DAP Server. It takes an opened Listener
// via config and assumes its ownership. config.DisconnectChan has to be set;
// it will be closed by the server when the client fails to connect,
// NewServer creates a new DAP Server.
// If the provided config has a net.Listener, the server waits
// for client's connection using the Listener. The server takes
// ownership of the Listener object. In this case, conn must be nil.
//
// If conn is provided, the server operates only with the
// supplied Conn. It takes ownership of the Conn object.
// The config's Listner must be nil.
//
// config.DisconnectChan has to be set; it will be closed by
// the server when the client fails to connect,
// disconnects or requests shutdown. Once config.DisconnectChan is closed,
// Server.Stop() must be called to shutdown this single-user server.
func NewServer(config *service.Config) *Server {
func NewServer(config *service.Config, conn net.Conn) *Server {
logger := logflags.DAPLogger()
logflags.WriteDAPListeningMessage(config.Listener.Addr().String())
if config.Listener == nil && conn == nil {
logger.Fatal("Cannot set up a DAP server without network configuration")
}
if config.Listener != nil && conn != nil {
logger.Fatal("Cannot set up a DAP server with both config.Listener and net.Conn")
}
if config.Listener != nil {
logflags.WriteDAPListeningMessage(config.Listener.Addr().String())
} else { // conn != nil
logger.Debugf("DAP server connected with client at %s", conn.RemoteAddr())
hyangah marked this conversation as resolved.
Show resolved Hide resolved
}
logger.Debug("DAP server pid = ", os.Getpid())

return &Server{
config: config,
listener: config.Listener,
Expand All @@ -220,6 +240,7 @@ func NewServer(config *service.Config) *Server {
variableHandles: newVariablesHandlesMap(),
args: defaultArgs,
exceptionErr: nil,
conn: conn,
}
}

Expand Down Expand Up @@ -251,7 +272,10 @@ func (s *Server) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
func (s *Server) Stop() {
s.log.Debug("DAP server stopping...")
close(s.stopTriggered)
_ = s.listener.Close()

if s.listener != nil {
_ = s.listener.Close()
}

s.mu.Lock()
defer s.mu.Unlock()
Expand Down Expand Up @@ -312,6 +336,17 @@ func (s *Server) triggerServerStop() {
// TODO(polina): allow new client connections for new debug sessions,
// so the editor needs to launch delve only once?
func (s *Server) Run() {
if s.listener == nil {
// Server works with a predetermined client
// over the network connection this process started.
// The same user check is unnecessary because the point of
// the same user check is to prevent an unexpected user
// from connecting to the server through the open port.
// There is no open port in the process running in this mode.
go s.serveDAPCodec()
hyangah marked this conversation as resolved.
Show resolved Hide resolved
return
polinasok marked this conversation as resolved.
Show resolved Hide resolved
}

go func() {
conn, err := s.listener.Accept() // listener is closed in Stop()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion service/dap/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func startDapServer(t *testing.T, serverStopped chan struct{}) (listener net.Lis
server := NewServer(&service.Config{
Listener: listener,
DisconnectChan: disconnectChan,
})
}, nil)
server.Run()
// Give server time to start listening for clients
time.Sleep(100 * time.Millisecond)
Expand Down