-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
server.go
4023 lines (3708 loc) · 146 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 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.
package dap
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"go/constant"
"go/parser"
"io"
"math"
"net"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"runtime/debug"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/locspec"
"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/go-delve/delve/service/internal/sameuser"
"github.com/google/go-dap"
)
// Server implements a DAP server that can accept a single client for
// a single debug session (for now). It does not yet support restarting.
// That means that in addition to explicit shutdown requests,
// program termination and failed or closed client connection
// would also result in stopping this single-use server.
//
// The DAP server operates via the following goroutines:
//
// (1) Main goroutine where the server is created via NewServer(),
// started via Run() and stopped via Stop(). Once the server is
// started, this goroutine blocks until it receives a stop-server
// signal that can come from an OS interrupt (such as Ctrl-C) or
// config.DisconnectChan (passed to NewServer()) as a result of
// client connection failure or closure or a DAP disconnect request.
//
// (2) Run goroutine started from Run() that serves as both
// a listener and a client goroutine. It accepts a client connection,
// reads, decodes and dispatches each request from the client.
// For synchronous requests, it issues commands to the
// underlying debugger and sends back events and responses.
// These requests block while the debuggee is running, so,
// where applicable, the handlers need to check if debugging
// state is running, so there is a need for a halt request or
// a dummy/error response to avoid blocking.
//
// This is the only goroutine that sends a stop-server signal
// via config.DisconnectChan when encountering a client connection
// error or responding to a (synchronous) DAP disconnect request.
// Once stop is triggered, the goroutine exits.
//
// Unlike rpccommon, there is not another layer of per-client
// goroutines here because the dap server does not support
// multiple clients.
//
// (3) Per-request goroutine is started for each asynchronous request
// that resumes execution. We check if target is running already, so
// there should be no more than one pending asynchronous request at
// a time. This goroutine issues commands to the underlying debugger
// and sends back events and responses. It takes a setup-done channel
// as an argument and temporarily blocks the request loop until setup
// for asynchronous execution is complete and target is running.
// Once done, it unblocks processing of parallel requests unblocks
// (e.g. disconnecting while the program is running).
//
// These per-request goroutines never send a stop-server signal.
// They block on running debugger commands that are interrupted
// when halt is issued while stopping. At that point these goroutines
// wrap-up and exit.
type Server struct {
// config is all the information necessary to start the debugger and server.
config *Config
// listener is used to accept the client connection.
// When working with a predetermined client, this is nil.
listener net.Listener
// session is the debug session that comes with a client connection.
session *Session
sessionMu sync.Mutex
}
// Session is an abstraction for serving and shutting down
// a DAP debug session with a pre-connected client.
// TODO(polina): move this to a different file/package
type Session struct {
config *Config
id int
// stackFrameHandles maps frames of each goroutine to unique ids across all goroutines.
// Reset at every stop.
stackFrameHandles *handlesMap[stackFrame]
// variableHandles maps compound variables to unique references within their stack frame.
// Reset at every stop.
// See also comment for convertVariable.
variableHandles *handlesMap[*fullyQualifiedVariable]
// args tracks special settings for handling debug session requests.
args launchAttachArgs
// exceptionErr tracks the runtime error that last occurred.
exceptionErr error
// clientCapabilities tracks special settings for handling debug session requests.
clientCapabilities dapClientCapabilities
// mu synchronizes access to objects set on start-up (from run goroutine)
// and stopped on teardown (from main goroutine)
mu sync.Mutex
// conn is the accepted client connection.
conn *connection
// debugger is the underlying debugger service.
debugger *debugger.Debugger
// binaryToRemove is the temp compiled binary to be removed on disconnect (if any).
binaryToRemove string
// noDebugProcess is set for the noDebug launch process.
noDebugProcess *process
// sendingMu synchronizes writing to conn
// to ensure that messages do not get interleaved
sendingMu sync.Mutex
// runningCmd tracks whether the server is running an asynchronous
// command that resumes execution, which may not correspond to the actual
// running state of the process (e.g. if a command is temporarily interrupted).
runningCmd bool
runningMu sync.Mutex
// haltRequested tracks whether a halt of the program has been requested, which may
// not correspond to whether a Halt Request has been sent to the target.
haltRequested bool
haltMu sync.Mutex
// changeStateMu must be held for a request to protect itself from another goroutine
// changing the state of the running process at the same time.
changeStateMu sync.Mutex
// stdoutReader the program's stdout.
stdoutReader io.ReadCloser
// stderrReader the program's stderr.
stderrReader io.ReadCloser
// preTerminatedWG the WaitGroup that needs to wait before sending a terminated event.
preTerminatedWG sync.WaitGroup
}
// Config is all the information needed to start the debugger, handle
// DAP connection traffic and signal to the server when it is time to stop.
type Config struct {
*service.Config
// log is used for structured logging.
log logflags.Logger
// StopTriggered is closed when the server is Stop()-ed.
// Can be used to safeguard against duplicate shutdown sequences.
StopTriggered chan struct{}
}
type connection struct {
mu sync.Mutex
closed bool
closedChan chan struct{}
io.ReadWriteCloser
}
func newConnection(conn io.ReadWriteCloser) *connection {
return &connection{ReadWriteCloser: conn, closedChan: make(chan struct{})}
}
func (c *connection) Close() error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.closed {
close(c.closedChan)
}
c.closed = true
return c.ReadWriteCloser.Close()
}
func (c *connection) isClosed() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.closed
}
type process struct {
*exec.Cmd
exited chan struct{}
}
// launchAttachArgs captures arguments from launch/attach request that
// impact handling of subsequent requests.
// The fields with cfgName tag can be updated through an evaluation request.
type launchAttachArgs struct {
// stopOnEntry is set to automatically stop the debuggee after start.
stopOnEntry bool
// StackTraceDepth is the maximum length of the returned list of stack frames.
StackTraceDepth int `cfgName:"stackTraceDepth"`
// ShowGlobalVariables indicates if global package variables should be loaded.
ShowGlobalVariables bool `cfgName:"showGlobalVariables"`
// ShowRegisters indicates if register values should be loaded.
ShowRegisters bool `cfgName:"showRegisters"`
// GoroutineFilters are the filters used when loading goroutines.
GoroutineFilters string `cfgName:"goroutineFilters"`
// ShowPprofLabels is an array of keys of pprof labels to show as a
// goroutine name in the threads view. If the array has one element, only
// that label's value will be shown; otherwise, each of the labels will be
// shown as "key:value". To show all labels, specify the single element "*".
ShowPprofLabels []string `cfgName:"showPprofLabels"`
// HideSystemGoroutines indicates if system goroutines should be removed from threads
// responses.
HideSystemGoroutines bool `cfgName:"hideSystemGoroutines"`
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
substitutePathClientToServer [][2]string `cfgName:"substitutePath"`
// substitutePathServerToClient indicates rules for converting file paths between debugger and client.
substitutePathServerToClient [][2]string
}
// defaultArgs borrows the defaults for the arguments from the original vscode-go adapter.
// TODO(polinasok): clean up this and its reference (Server.args)
// in favor of default*Config variables defined in types.go.
var defaultArgs = launchAttachArgs{
stopOnEntry: false,
StackTraceDepth: 50,
ShowGlobalVariables: false,
HideSystemGoroutines: false,
ShowRegisters: false,
GoroutineFilters: "",
ShowPprofLabels: []string{},
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
}
// dapClientCapabilities captures arguments from initialize request that
// impact handling of subsequent requests.
type dapClientCapabilities struct {
supportsVariableType bool
supportsVariablePaging bool
supportsRunInTerminalRequest bool
supportsMemoryReferences bool
supportsProgressReporting bool
}
// DefaultLoadConfig controls how variables are loaded from the target's memory.
// These limits are conservative to minimize performance overhead for bulk loading.
// With dlv-dap, users do not have a way to adjust these.
// Instead, we are focusing in interactive loading with nested reloads, array/map
// paging and context-specific string limits.
var DefaultLoadConfig = proc.LoadConfig{
FollowPointers: true,
MaxVariableRecurse: 1,
// TODO(polina): consider 1024 limit instead:
// - vscode+C appears to use 1024 as the load limit
// - vscode viewlet hover truncates at 1023 characters
MaxStringLen: 512,
MaxArrayValues: 64,
MaxStructFields: -1,
}
const (
// When a user examines a single string, we can relax the loading limit.
maxSingleStringLen = 4 << 10 // 4096
// Results of a call are single-use and transient. We need to maximize
// what is presented. A common use case of a call injection is to
// stringify complex data conveniently.
maxStringLenInCallRetVars = 1 << 10 // 1024
)
// Max number of goroutines that we will return.
// This is a var for testing
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,
// disconnects or requests shutdown. Once config.DisconnectChan is closed,
// Server.Stop() must be called to shutdown this single-user server.
//
// NewServer can be used to create a special DAP Server that works
// only with a predetermined client. In that case, config.Listener is
// nil and its RunWithClient must be used instead of Run.
func NewServer(config *service.Config) *Server {
logger := logflags.DAPLogger()
if config.Listener != nil {
logflags.WriteDAPListeningMessage(config.Listener.Addr())
} else {
logger.Debug("DAP server for a predetermined client")
}
logger.Debug("DAP server pid = ", os.Getpid())
if config.AcceptMulti {
logger.Warn("DAP server does not support accept-multiclient mode")
config.AcceptMulti = false
}
return &Server{
config: &Config{
Config: config,
log: logger,
StopTriggered: make(chan struct{}),
},
listener: config.Listener,
}
}
var sessionCount = 0
// 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, debugger *debugger.Debugger) *Session {
sessionCount++
if config.log == nil {
config.log = logflags.DAPLogger()
}
config.log.Debugf("DAP connection %d started", sessionCount)
if config.StopTriggered == nil {
config.log.Error("Session must be configured with StopTriggered")
os.Exit(1)
}
return &Session{
config: config,
id: sessionCount,
conn: newConnection(conn),
stackFrameHandles: newHandlesMap[stackFrame](),
variableHandles: newHandlesMap[*fullyQualifiedVariable](),
args: defaultArgs,
exceptionErr: nil,
debugger: debugger,
}
}
// If user-specified options are provided via Launch/AttachRequest,
// we override the defaults for optional args.
func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) {
s.args.stopOnEntry = args.StopOnEntry
if depth := args.StackTraceDepth; depth > 0 {
s.args.StackTraceDepth = depth
}
s.args.ShowGlobalVariables = args.ShowGlobalVariables
s.args.ShowRegisters = args.ShowRegisters
s.args.HideSystemGoroutines = args.HideSystemGoroutines
s.args.GoroutineFilters = args.GoroutineFilters
s.args.ShowPprofLabels = args.ShowPprofLabels
if paths := args.SubstitutePath; len(paths) > 0 {
clientToServer := make([][2]string, 0, len(paths))
serverToClient := make([][2]string, 0, len(paths))
for _, p := range paths {
clientToServer = append(clientToServer, [2]string{p.From, p.To})
serverToClient = append(serverToClient, [2]string{p.To, p.From})
}
s.args.substitutePathClientToServer = clientToServer
s.args.substitutePathServerToClient = serverToClient
}
}
// 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 or stops the noDebug process.
// This method mustn't be called more than once.
// StopTriggered notifies other goroutines that stop is in progress.
func (s *Server) Stop() {
s.config.log.Debug("DAP server stopping...")
defer s.config.log.Debug("DAP server stopped")
close(s.config.StopTriggered)
if s.listener != nil {
// If run goroutine is blocked on accept, this will unblock it.
s.listener.Close()
}
s.sessionMu.Lock()
defer s.sessionMu.Unlock()
if s.session == nil {
return
}
// If run goroutine is blocked on read, this will unblock it.
s.session.Close()
}
// Close closes the underlying debugger/process and connection.
// May be called more than once.
func (s *Session) Close() {
s.mu.Lock()
defer s.mu.Unlock()
if s.debugger != nil {
killProcess := s.debugger.AttachPid() == 0
s.stopDebugSession(killProcess)
} else if s.noDebugProcess != nil {
s.stopNoDebugProcess()
}
// The binary is no longer in use by the debugger. It is safe to remove it.
if s.binaryToRemove != "" {
gobuild.Remove(s.binaryToRemove)
s.binaryToRemove = "" // avoid error printed on duplicate removal
}
// Close client connection last, so other shutdown stages
// can send client notifications.
// 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 and
// allowing the run goroutines to exit.
// This connection is closed here and in serveDAPCodec().
// If this was a forced shutdown, external stop logic can close this first.
// If this was a client loop exit (on error or disconnect), serveDAPCodec()
// will be first.
// Duplicate close calls return an error, but are not fatal.
s.conn.Close()
}
// triggerServerStop closes DisconnectChan if not nil, which
// signals that client sent a disconnect request or there was connection
// failure or closure. Since the server currently services only one
// client, this is used as a signal to stop the entire server.
// The function safeguards against 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.
func (c *Config) triggerServerStop() {
// Avoid accidentally closing the channel twice and causing a panic, when
// this function is called more than once because stop was triggered
// by multiple conditions simultaneously.
if c.DisconnectChan != nil {
close(c.DisconnectChan)
c.DisconnectChan = nil
}
// There should be no logic here after the stop-server
// signal that might cause everything to shut down before this
// logic gets executed.
}
// 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 dap server only once? Note that some requests
// may change the server's environment (e.g. see dlvCwd of launch configuration).
// So if we want to reuse this server for multiple independent debugging sessions
// we need to take that into consideration.
func (s *Server) Run() {
if s.listener == nil {
s.config.log.Error("Misconfigured server: no Listener is configured.")
os.Exit(1)
}
go func() {
conn, err := s.listener.Accept() // listener is closed in Stop()
if err != nil {
select {
case <-s.config.StopTriggered:
default:
s.config.log.Errorf("Error accepting client connection: %s\n", err)
s.config.triggerServerStop()
}
return
}
if s.config.CheckLocalConnUser {
if !sameuser.CanAccept(s.listener.Addr(), conn.LocalAddr(), conn.RemoteAddr()) {
s.config.log.Error("Error accepting client connection: Only connections from the same user that started this instance of Delve are allowed to connect. See --only-same-user.")
s.config.triggerServerStop()
return
}
}
s.runSession(conn)
}()
}
func (s *Server) runSession(conn io.ReadWriteCloser) {
s.sessionMu.Lock()
s.session = NewSession(conn, s.config, nil) // closed in Stop()
s.sessionMu.Unlock()
s.session.ServeDAPCodec()
}
// RunWithClient is similar to Run but works only with an already established
// connection instead of waiting on the listener to accept a new client.
// RunWithClient takes ownership of conn. Debugger won't be started
// until a launch/attach request is received over the connection.
func (s *Server) RunWithClient(conn net.Conn) {
if s.listener != nil {
s.config.log.Error("RunWithClient must not be used when the Server is configured with a Listener")
os.Exit(1)
}
s.config.log.Debugf("Connected to the client at %s", conn.RemoteAddr())
go s.runSession(conn)
}
func (s *Session) address() string {
if s.config.Listener != nil {
return s.config.Listener.Addr().String()
}
if netconn, ok := s.conn.ReadWriteCloser.(net.Conn); ok {
return netconn.LocalAddr().String()
}
return ""
}
// 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() {
// Close conn, but not the debugger in case we are in AcceptMulti mode.
// If not, debugger will be shut down in Stop().
var triggerServerStop bool
defer s.conn.Close()
defer func() {
if triggerServerStop {
s.config.triggerServerStop()
}
}()
reader := bufio.NewReader(s.conn)
for {
request, err := dap.ReadProtocolMessage(reader)
// Handle dap.DecodeProtocolMessageFieldError errors gracefully by responding with an ErrorResponse.
// For example:
// -- "Request command 'foo' is not supported" means we
// potentially got some new DAP request that we do not yet have
// decoding support for, so we can respond with an ErrorResponse.
//
// Other errors, such as unmarshalling errors, will log the error and cause the server to trigger
// a stop.
if err != nil {
s.config.log.Debug("DAP error: ", err)
select {
case <-s.config.StopTriggered:
default:
triggerServerStop = !s.config.AcceptMulti
if err != io.EOF { // EOF means client closed connection
var decodeErr *dap.DecodeProtocolMessageFieldError
if errors.As(err, &decodeErr) {
// Send an error response to the users if we were unable to process the message.
s.sendInternalErrorResponse(decodeErr.Seq, err.Error())
continue
}
s.config.log.Error("DAP error: ", err)
}
}
return
}
s.handleRequest(request)
if _, ok := request.(*dap.DisconnectRequest); ok {
// disconnect already shut things down and triggered stopping
return
}
}
}
// In case a handler panics, we catch the panic to avoid crashing both
// the server and the target. We send an error response back, but
// in case it's a dup and ignored by the client, we also log the error.
func (s *Session) recoverPanic(request dap.Message) {
if ierr := recover(); ierr != nil {
s.config.log.Errorf("recovered panic: %s\n%s\n", ierr, debug.Stack())
s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("%v", ierr))
}
}
func (s *Session) handleRequest(request dap.Message) {
defer s.recoverPanic(request)
jsonmsg, _ := json.Marshal(request)
s.config.log.Debug("[<- from client]", string(jsonmsg))
if _, ok := request.(dap.RequestMessage); !ok {
s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("Unable to process non-request %#v\n", request))
return
}
if s.isNoDebug() {
switch request := request.(type) {
case *dap.DisconnectRequest:
s.onDisconnectRequest(request)
case *dap.RestartRequest:
s.sendUnsupportedErrorResponse(request.Request)
default:
r := request.(dap.RequestMessage).GetRequest()
s.sendErrorResponse(*r, NoDebugIsRunning, "noDebug mode", fmt.Sprintf("unable to process '%s' request", r.Command))
}
return
}
// These requests, can be handled regardless of whether the target is running
switch request := request.(type) {
case *dap.InitializeRequest: // Required
s.onInitializeRequest(request)
return
case *dap.LaunchRequest: // Required
s.onLaunchRequest(request)
return
case *dap.AttachRequest: // Required
s.onAttachRequest(request)
return
case *dap.DisconnectRequest: // Required
s.onDisconnectRequest(request)
return
case *dap.PauseRequest: // Required
s.onPauseRequest(request)
return
case *dap.TerminateRequest: // Optional (capability 'supportsTerminateRequest')
/*TODO*/ s.onTerminateRequest(request) // not yet implemented
return
case *dap.RestartRequest: // Optional (capability 'supportsRestartRequest')
/*TODO*/ s.onRestartRequest(request) // not yet implemented
return
}
// Most requests cannot be processed while the debuggee is running.
// We have a couple of options for handling these without blocking
// the request loop indefinitely when we are in running state.
// --1-- Return a dummy response or an error right away.
// --2-- Halt execution, process the request, maybe resume execution.
// --3-- Handle such requests asynchronously and let them block until
// the process stops or terminates (e.g. using a channel and a single
// goroutine to preserve the order). This might not be appropriate
// for requests such as continue or step because they would skip
// the stop, resuming execution right away. Other requests
// might not be relevant anymore when the stop is finally reached, and
// state changed from the previous snapshot. The user might want to
// resume execution before the backlog of buffered requests is cleared,
// so we would have to either cancel them or delay processing until
// the next stop. In addition, the editor itself might block waiting
// for these requests to return. We are not aware of any requests
// that would benefit from this approach at this time.
if s.debugger != nil && s.debugger.IsRunning() || s.isRunningCmd() {
switch request := request.(type) {
case *dap.ThreadsRequest: // Required
// On start-up, the client requests the baseline of currently existing threads
// right away as there are a number of DAP requests that require a thread id
// (pause, continue, stacktrace, etc.). This can happen after the program
// continues on entry, preventing the client from handling any pause requests
// from the user. We remedy this by sending back a placeholder thread id
// for the current goroutine.
response := &dap.ThreadsResponse{
Response: *newResponse(request.Request),
Body: dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: -1, Name: "Current"}}},
}
s.send(response)
case *dap.SetBreakpointsRequest: // Required
s.changeStateMu.Lock()
defer s.changeStateMu.Unlock()
s.config.log.Debug("halting execution to set breakpoints")
_, err := s.halt()
if err != nil {
s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
return
}
s.onSetBreakpointsRequest(request)
case *dap.SetFunctionBreakpointsRequest: // Optional (capability 'supportsFunctionBreakpoints')
s.changeStateMu.Lock()
defer s.changeStateMu.Unlock()
s.config.log.Debug("halting execution to set breakpoints")
_, err := s.halt()
if err != nil {
s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
return
}
s.onSetFunctionBreakpointsRequest(request)
default:
r := request.(dap.RequestMessage).GetRequest()
s.sendErrorResponse(*r, DebuggeeIsRunning, fmt.Sprintf("Unable to process `%s`", r.Command), "debuggee is running")
}
return
}
// Requests below can only be handled while target is stopped.
// Some of them are blocking and will be handled synchronously
// on this goroutine while non-blocking requests will be dispatched
// to another goroutine. Please note that because of the running
// check above, there should be no more than one pending asynchronous
// request at a time.
// Non-blocking request handlers will signal when they are ready
// setting up for async execution, so more requests can be processed.
resumeRequestLoop := newSyncflag()
switch request := request.(type) {
//--- Asynchronous requests ---
case *dap.ConfigurationDoneRequest: // Optional (capability 'supportsConfigurationDoneRequest')
go func() {
defer s.recoverPanic(request)
s.onConfigurationDoneRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.ContinueRequest: // Required
go func() {
defer s.recoverPanic(request)
s.onContinueRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.NextRequest: // Required
go func() {
defer s.recoverPanic(request)
s.onNextRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.StepInRequest: // Required
go func() {
defer s.recoverPanic(request)
s.onStepInRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.StepOutRequest: // Required
go func() {
defer s.recoverPanic(request)
s.onStepOutRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.StepBackRequest: // Optional (capability 'supportsStepBack')
go func() {
defer s.recoverPanic(request)
s.onStepBackRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
case *dap.ReverseContinueRequest: // Optional (capability 'supportsStepBack')
go func() {
defer s.recoverPanic(request)
s.onReverseContinueRequest(request, resumeRequestLoop)
}()
resumeRequestLoop.wait()
//--- Synchronous requests ---
case *dap.SetBreakpointsRequest: // Required
s.onSetBreakpointsRequest(request)
case *dap.SetFunctionBreakpointsRequest: // Optional (capability 'supportsFunctionBreakpoints')
s.onSetFunctionBreakpointsRequest(request)
case *dap.SetInstructionBreakpointsRequest: // Optional (capability 'supportsInstructionBreakpoints')
s.onSetInstructionBreakpointsRequest(request)
case *dap.SetExceptionBreakpointsRequest: // Optional (capability 'exceptionBreakpointFilters')
s.onSetExceptionBreakpointsRequest(request)
case *dap.ThreadsRequest: // Required
s.onThreadsRequest(request)
case *dap.StackTraceRequest: // Required
s.onStackTraceRequest(request)
case *dap.ScopesRequest: // Required
s.onScopesRequest(request)
case *dap.VariablesRequest: // Required
s.onVariablesRequest(request)
case *dap.EvaluateRequest: // Required
s.onEvaluateRequest(request)
case *dap.SetVariableRequest: // Optional (capability 'supportsSetVariable')
s.onSetVariableRequest(request)
case *dap.ExceptionInfoRequest: // Optional (capability 'supportsExceptionInfoRequest')
s.onExceptionInfoRequest(request)
case *dap.DisassembleRequest: // Optional (capability 'supportsDisassembleRequest')
s.onDisassembleRequest(request)
//--- Requests that we may want to support ---
case *dap.SourceRequest: // Required
/*TODO*/ s.sendUnsupportedErrorResponse(request.Request) // https://github.com/go-delve/delve/issues/2851
case *dap.SetExpressionRequest: // Optional (capability 'supportsSetExpression')
/*TODO*/ s.onSetExpressionRequest(request) // Not yet implemented
case *dap.LoadedSourcesRequest: // Optional (capability 'supportsLoadedSourcesRequest')
/*TODO*/ s.onLoadedSourcesRequest(request) // Not yet implemented
case *dap.ReadMemoryRequest: // Optional (capability 'supportsReadMemoryRequest')
/*TODO*/ s.onReadMemoryRequest(request) // Not yet implemented
case *dap.CancelRequest: // Optional (capability 'supportsCancelRequest')
/*TODO*/ s.onCancelRequest(request) // Not yet implemented (does this make sense?)
case *dap.ModulesRequest: // Optional (capability 'supportsModulesRequest')
/*TODO*/ s.sendUnsupportedErrorResponse(request.Request) // Not yet implemented (does this make sense?)
//--- Requests that we do not plan to support ---
case *dap.RestartFrameRequest: // Optional (capability 'supportsRestartFrame')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.GotoRequest: // Optional (capability 'supportsGotoTargetsRequest')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.TerminateThreadsRequest: // Optional (capability 'supportsTerminateThreadsRequest')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.StepInTargetsRequest: // Optional (capability 'supportsStepInTargetsRequest')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.GotoTargetsRequest: // Optional (capability 'supportsGotoTargetsRequest')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.CompletionsRequest: // Optional (capability 'supportsCompletionsRequest')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.DataBreakpointInfoRequest: // Optional (capability 'supportsDataBreakpoints')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.SetDataBreakpointsRequest: // Optional (capability 'supportsDataBreakpoints')
s.sendUnsupportedErrorResponse(request.Request)
case *dap.BreakpointLocationsRequest: // Optional (capability 'supportsBreakpointLocationsRequest')
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.
s.sendInternalErrorResponse(request.GetSeq(), fmt.Sprintf("Unable to process %#v\n", request))
}
}
func (s *Session) send(message dap.Message) {
jsonmsg, _ := json.Marshal(message)
s.config.log.Debug("[-> to client]", string(jsonmsg))
// TODO(polina): consider using a channel for all the sends and to have a dedicated
// goroutine that reads from that channel and sends over the connection.
// This will avoid blocking on slow network sends.
s.sendingMu.Lock()
defer s.sendingMu.Unlock()
err := dap.WriteProtocolMessage(s.conn, message)
if err != nil {
s.config.log.Debug(err)
}
}
func (s *Session) logToConsole(msg string) {
s.send(&dap.OutputEvent{
Event: *newEvent("output"),
Body: dap.OutputEventBody{
Output: msg + "\n",
Category: "console",
},
})
}
func (s *Session) onInitializeRequest(request *dap.InitializeRequest) {
s.setClientCapabilities(request.Arguments)
if request.Arguments.PathFormat != "path" {
s.sendErrorResponse(request.Request, FailedToInitialize, "Failed to initialize",
fmt.Sprintf("Unsupported 'pathFormat' value '%s'.", request.Arguments.PathFormat))
return
}
if !request.Arguments.LinesStartAt1 {
s.sendErrorResponse(request.Request, FailedToInitialize, "Failed to initialize",
"Only 1-based line numbers are supported.")
return
}
if !request.Arguments.ColumnsStartAt1 {
s.sendErrorResponse(request.Request, FailedToInitialize, "Failed to initialize",
"Only 1-based column numbers are supported.")
return
}
// TODO(polina): Respond with an error if debug session started
// with an initialize request is in progress?
response := &dap.InitializeResponse{Response: *newResponse(request.Request)}
response.Body.SupportsConfigurationDoneRequest = true
response.Body.SupportsConditionalBreakpoints = true
response.Body.SupportsDelayedStackTraceLoading = true
response.Body.SupportsFunctionBreakpoints = true
response.Body.SupportsInstructionBreakpoints = true
response.Body.SupportsExceptionInfoRequest = true
response.Body.SupportsSetVariable = true
response.Body.SupportsEvaluateForHovers = true
response.Body.SupportsClipboardContext = true
response.Body.SupportsSteppingGranularity = true
response.Body.SupportsLogPoints = true
response.Body.SupportsDisassembleRequest = true
// To be enabled by CapabilitiesEvent based on launch configuration
response.Body.SupportsStepBack = false
response.Body.SupportTerminateDebuggee = false
// TODO(polina): support these requests in addition to vscode-go feature parity
response.Body.SupportsTerminateRequest = false
response.Body.SupportsRestartRequest = false
response.Body.SupportsSetExpression = false
response.Body.SupportsLoadedSourcesRequest = false
response.Body.SupportsReadMemoryRequest = false
response.Body.SupportsCancelRequest = false
s.send(response)
}
func (s *Session) setClientCapabilities(args dap.InitializeRequestArguments) {
s.clientCapabilities.supportsMemoryReferences = args.SupportsMemoryReferences
s.clientCapabilities.supportsProgressReporting = args.SupportsProgressReporting
s.clientCapabilities.supportsRunInTerminalRequest = args.SupportsRunInTerminalRequest
s.clientCapabilities.supportsVariablePaging = args.SupportsVariablePaging
s.clientCapabilities.supportsVariableType = args.SupportsVariableType
}
func cleanExeName(name string) string {
if runtime.GOOS == "windows" && filepath.Ext(name) != ".exe" {
return name + ".exe"
}
return name
}
func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
var err error
if s.debugger != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
fmt.Sprintf("debug session already in progress at %s - use remote attach mode to connect to a server with an active debug session", s.address()))
return
}
args := defaultLaunchConfig // narrow copy for initializing non-zero default values
if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
s.sendShowUserErrorResponse(request.Request,
FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - %v", err))
return
}
s.config.log.Debug("parsed launch config: ", prettyPrint(args))
if args.DlvCwd != "" {
if err := os.Chdir(args.DlvCwd); err != nil {
s.sendShowUserErrorResponse(request.Request,
FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to chdir to %q - %v", args.DlvCwd, err))
return
}
}
for k, v := range args.Env {
if v != nil {
if err := os.Setenv(k, *v); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to setenv(%v) - %v", k, err))
return
}
} else {
if err := os.Unsetenv(k); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to unsetenv(%v) - %v", k, err))
return
}
}
}
if args.Mode == "" {
args.Mode = "debug"
}
if !isValidLaunchMode(args.Mode) {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
fmt.Sprintf("invalid debug configuration - unsupported 'mode' attribute %q", args.Mode))
return
}
if args.Program == "" && args.Mode != "replay" { // Only fail on modes requiring a program
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
"The program attribute is missing in debug configuration.")
return
}
if args.Backend == "" {
args.Backend = "default"
}
if args.Mode == "replay" {
// Validate trace directory
if args.TraceDirPath == "" {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
"The 'traceDirPath' attribute is missing in debug configuration.")
return
}
// Assign the rr trace directory path to debugger configuration
s.config.Debugger.CoreFile = args.TraceDirPath
args.Backend = "rr"
}
if args.Mode == "core" {
// Validate core dump path
if args.CoreFilePath == "" {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
"The 'coreFilePath' attribute is missing in debug configuration.")
return
}
// Assign the non-empty core file path to debugger configuration. This will
// trigger a native core file replay instead of a rr trace replay
s.config.Debugger.CoreFile = args.CoreFilePath
args.Backend = "core"
}
s.config.Debugger.Backend = args.Backend
// Prepare the debug executable filename, building it if necessary
debugbinary := args.Program
if args.Mode == "debug" || args.Mode == "test" {
deleteOnError := false
if args.Output == "" {
deleteOnError = true
args.Output = gobuild.DefaultDebugBinaryPath("__debug_bin")
} else {
args.Output = cleanExeName(args.Output)
}
args.Output, err = filepath.Abs(args.Output)
if err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
return
}
debugbinary = args.Output
var cmd string
var out []byte
switch args.Mode {
case "debug":