// Copyright 2020 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 defines helpers and request handlers for a dummy server // that accepts DAP requests and responds with dummy or error responses. // Fake-supported requests: // - initialize // - launch // - setBreakpoints // - setExceptionBreakpoints // - configurationDone // - threads // - stackTrace // - scopes // - variables // - continue // - disconnect // All other requests result in ErrorResponse's. // // The server uses the following goroutines: // - "main" goroutine accepts client connections one by one and // handles them serially by reading and decoding incoming requests // and dispatching each one to a new goroutine for further // processing. // - per-request goroutines process each request as if // letting fake debugger take over. They send events and responses // via the sender goroutine. // - sender goroutine listens for messages to send and // writes them to the client connection. // package main import ( "bufio" "io" "log" "net" "sync" "time" "github.com/google/go-dap" ) // server starts a server that listens on a specified port // and blocks indefinitely. This server can accept multiple // client connections at the same time. func server(port string) error { listener, err := net.Listen("tcp", ":"+port) if err != nil { return err } defer listener.Close() log.Println("Started server at", listener.Addr()) for { conn, err := listener.Accept() if err != nil { log.Println("Connection failed:", err) continue } log.Println("Accepted connection from", conn.RemoteAddr()) // Handle multiple client connections concurrently go handleConnection(conn) } } // handleConnection handles a connection from a single client. // It reads and decodes the incoming data and dispatches it // to per-request processing goroutines. It also launches the // sender goroutine to send resulting messages over the connection // back to the client. func handleConnection(conn net.Conn) { debugSession := fakeDebugSession{ rw: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), sendQueue: make(chan dap.Message), stopDebug: make(chan struct{}), } go debugSession.sendFromQueue() for { err := debugSession.handleRequest() // TODO(polina): check for connection vs decoding error? if err != nil { if err == io.EOF { log.Println("No more data to read:", err) break } // There maybe more messages to process, but // we will start with the strict behavior of only accepting // expected inputs. log.Fatal("Server error: ", err) } } log.Println("Closing connection from", conn.RemoteAddr()) close(debugSession.stopDebug) debugSession.sendWg.Wait() close(debugSession.sendQueue) conn.Close() } func (ds *fakeDebugSession) handleRequest() error { log.Println("Reading request...") request, err := dap.ReadProtocolMessage(ds.rw.Reader) if err != nil { return err } log.Printf("Received request\n\t%#v\n", request) ds.sendWg.Add(1) go func() { ds.dispatchRequest(request) ds.sendWg.Done() }() return nil } // dispatchRequest launches a new goroutine to process each request // and send back events and responses. func (ds *fakeDebugSession) dispatchRequest(request dap.Message) { switch request := request.(type) { case *dap.InitializeRequest: ds.onInitializeRequest(request) case *dap.LaunchRequest: ds.onLaunchRequest(request) case *dap.AttachRequest: ds.onAttachRequest(request) case *dap.DisconnectRequest: ds.onDisconnectRequest(request) case *dap.TerminateRequest: ds.onTerminateRequest(request) case *dap.RestartRequest: ds.onRestartRequest(request) case *dap.SetBreakpointsRequest: ds.onSetBreakpointsRequest(request) case *dap.SetFunctionBreakpointsRequest: ds.onSetFunctionBreakpointsRequest(request) case *dap.SetExceptionBreakpointsRequest: ds.onSetExceptionBreakpointsRequest(request) case *dap.ConfigurationDoneRequest: ds.onConfigurationDoneRequest(request) case *dap.ContinueRequest: ds.onContinueRequest(request) case *dap.NextRequest: ds.onNextRequest(request) case *dap.StepInRequest: ds.onStepInRequest(request) case *dap.StepOutRequest: ds.onStepOutRequest(request) case *dap.StepBackRequest: ds.onStepBackRequest(request) case *dap.ReverseContinueRequest: ds.onReverseContinueRequest(request) case *dap.RestartFrameRequest: ds.onRestartFrameRequest(request) case *dap.GotoRequest: ds.onGotoRequest(request) case *dap.PauseRequest: ds.onPauseRequest(request) case *dap.StackTraceRequest: ds.onStackTraceRequest(request) case *dap.ScopesRequest: ds.onScopesRequest(request) case *dap.VariablesRequest: ds.onVariablesRequest(request) case *dap.SetVariableRequest: ds.onSetVariableRequest(request) case *dap.SetExpressionRequest: ds.onSetExpressionRequest(request) case *dap.SourceRequest: ds.onSourceRequest(request) case *dap.ThreadsRequest: ds.onThreadsRequest(request) case *dap.TerminateThreadsRequest: ds.onTerminateThreadsRequest(request) case *dap.EvaluateRequest: ds.onEvaluateRequest(request) case *dap.StepInTargetsRequest: ds.onStepInTargetsRequest(request) case *dap.GotoTargetsRequest: ds.onGotoTargetsRequest(request) case *dap.CompletionsRequest: ds.onCompletionsRequest(request) case *dap.ExceptionInfoRequest: ds.onExceptionInfoRequest(request) case *dap.LoadedSourcesRequest: ds.onLoadedSourcesRequest(request) case *dap.DataBreakpointInfoRequest: ds.onDataBreakpointInfoRequest(request) case *dap.SetDataBreakpointsRequest: ds.onSetDataBreakpointsRequest(request) case *dap.ReadMemoryRequest: ds.onReadMemoryRequest(request) case *dap.DisassembleRequest: ds.onDisassembleRequest(request) case *dap.CancelRequest: ds.onCancelRequest(request) case *dap.BreakpointLocationsRequest: ds.onBreakpointLocationsRequest(request) default: log.Fatalf("Unable to process %#v", request) } } // send lets the sender goroutine know via a channel that there is // a message to be sent to client. This is called by per-request // goroutines to send events and responses for each request and // to notify of events triggered by the fake debugger. func (ds *fakeDebugSession) send(message dap.Message) { ds.sendQueue <- message } // sendFromQueue is to be run in a separate goroutine to listen on a // channel for messages to send back to the client. It will // return once the channel is closed. func (ds *fakeDebugSession) sendFromQueue() { for message := range ds.sendQueue { dap.WriteProtocolMessage(ds.rw.Writer, message) log.Printf("Message sent\n\t%#v\n", message) ds.rw.Flush() } } // ----------------------------------------------------------------------- // Very Fake Debugger // // The debugging session will keep track of how many breakpoints // have been set. Once start-up is done (i.e. configurationDone // request is processed), it will "stop" at each breakpoint one by // one, and once there are no more, it will trigger a terminated event. type fakeDebugSession struct { // rw is used to read requests and write events/responses rw *bufio.ReadWriter // sendQueue is used to capture messages from multiple request // processing goroutines while writing them to the client connection // from a single goroutine via sendFromQueue. We must keep track of // the multiple channel senders with a wait group to make sure we do // not close this channel prematurely. Closing this channel will signal // the sendFromQueue goroutine that it can exit. sendQueue chan dap.Message sendWg sync.WaitGroup // stopDebug is used to notify long-running handlers to stop processing. stopDebug chan struct{} // bpSet is a counter of the remaining breakpoints that the debug // session is yet to stop at before the program terminates. bpSet int bpSetMux sync.Mutex } // doContinue allows fake program execution to continue when the program // is started or unpaused. It simulates events from the debug session // by "stopping" on a breakpoint or terminating if there are no more // breakpoints. Safe to use concurrently. func (ds *fakeDebugSession) doContinue() { var e dap.Message ds.bpSetMux.Lock() if ds.bpSet == 0 { // Pretend that the program is running. // The delay will allow for all in-flight responses // to be sent before termination. time.Sleep(1000 * time.Millisecond) e = &dap.TerminatedEvent{ Event: *newEvent("terminated"), } } else { e = &dap.StoppedEvent{ Event: *newEvent("stopped"), Body: dap.StoppedEventBody{Reason: "breakpoint", ThreadId: 1, AllThreadsStopped: true}, } ds.bpSet-- } ds.bpSetMux.Unlock() ds.send(e) } // ----------------------------------------------------------------------- // Request Handlers // // Below is a dummy implementation of the request handlers. // They take no action, but just return dummy responses. // A real debug adaptor would call the debugger methods here // and use their results to populate each response. func (ds *fakeDebugSession) onInitializeRequest(request *dap.InitializeRequest) { response := &dap.InitializeResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body.SupportsConfigurationDoneRequest = true response.Body.SupportsFunctionBreakpoints = false response.Body.SupportsConditionalBreakpoints = false response.Body.SupportsHitConditionalBreakpoints = false response.Body.SupportsEvaluateForHovers = false response.Body.ExceptionBreakpointFilters = []dap.ExceptionBreakpointsFilter{} response.Body.SupportsStepBack = false response.Body.SupportsSetVariable = false response.Body.SupportsRestartFrame = false response.Body.SupportsGotoTargetsRequest = false response.Body.SupportsStepInTargetsRequest = false response.Body.SupportsCompletionsRequest = false response.Body.CompletionTriggerCharacters = []string{} response.Body.SupportsModulesRequest = false response.Body.AdditionalModuleColumns = []dap.ColumnDescriptor{} response.Body.SupportedChecksumAlgorithms = []dap.ChecksumAlgorithm{} response.Body.SupportsRestartRequest = false response.Body.SupportsExceptionOptions = false response.Body.SupportsValueFormattingOptions = false response.Body.SupportsExceptionInfoRequest = false response.Body.SupportTerminateDebuggee = false response.Body.SupportsDelayedStackTraceLoading = false response.Body.SupportsLoadedSourcesRequest = false response.Body.SupportsLogPoints = false response.Body.SupportsTerminateThreadsRequest = false response.Body.SupportsSetExpression = false response.Body.SupportsTerminateRequest = false response.Body.SupportsDataBreakpoints = false response.Body.SupportsReadMemoryRequest = false response.Body.SupportsDisassembleRequest = false response.Body.SupportsCancelRequest = false response.Body.SupportsBreakpointLocationsRequest = false // This is a fake set up, so we can start "accepting" configuration // requests for setting breakpoints, etc from the client at any time. // Notify the client with an 'initialized' event. The client will end // the configuration sequence with 'configurationDone' request. e := &dap.InitializedEvent{Event: *newEvent("initialized")} ds.send(e) ds.send(response) } func (ds *fakeDebugSession) onLaunchRequest(request *dap.LaunchRequest) { // This is where a real debug adaptor would check the soundness of the // arguments (e.g. program from launch.json) and then use them to launch the // debugger and attach to the program. response := &dap.LaunchResponse{} response.Response = *newResponse(request.Seq, request.Command) ds.send(response) } func (ds *fakeDebugSession) onAttachRequest(request *dap.AttachRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "AttachRequest is not yet supported")) } func (ds *fakeDebugSession) onDisconnectRequest(request *dap.DisconnectRequest) { response := &dap.DisconnectResponse{} response.Response = *newResponse(request.Seq, request.Command) ds.send(response) } func (ds *fakeDebugSession) onTerminateRequest(request *dap.TerminateRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported")) } func (ds *fakeDebugSession) onRestartRequest(request *dap.RestartRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "RestartRequest is not yet supported")) } func (ds *fakeDebugSession) onSetBreakpointsRequest(request *dap.SetBreakpointsRequest) { response := &dap.SetBreakpointsResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body.Breakpoints = make([]dap.Breakpoint, len(request.Arguments.Breakpoints)) for i, b := range request.Arguments.Breakpoints { response.Body.Breakpoints[i].Line = b.Line response.Body.Breakpoints[i].Verified = true ds.bpSetMux.Lock() ds.bpSet++ ds.bpSetMux.Unlock() } ds.send(response) } func (ds *fakeDebugSession) onSetFunctionBreakpointsRequest(request *dap.SetFunctionBreakpointsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "SetFunctionBreakpointsRequest is not yet supported")) } func (ds *fakeDebugSession) onSetExceptionBreakpointsRequest(request *dap.SetExceptionBreakpointsRequest) { response := &dap.SetExceptionBreakpointsResponse{} response.Response = *newResponse(request.Seq, request.Command) ds.send(response) } func (ds *fakeDebugSession) onConfigurationDoneRequest(request *dap.ConfigurationDoneRequest) { // This would be the place to check if the session was configured to // stop on entry and if that is the case, to issue a // stopped-on-breakpoint event. This being a mock implementation, // we "let" the program continue after sending a successful response. e := &dap.ThreadEvent{Event: *newEvent("thread"), Body: dap.ThreadEventBody{Reason: "started", ThreadId: 1}} ds.send(e) response := &dap.ConfigurationDoneResponse{} response.Response = *newResponse(request.Seq, request.Command) ds.send(response) ds.doContinue() } func (ds *fakeDebugSession) onContinueRequest(request *dap.ContinueRequest) { response := &dap.ContinueResponse{} response.Response = *newResponse(request.Seq, request.Command) ds.send(response) ds.doContinue() } func (ds *fakeDebugSession) onNextRequest(request *dap.NextRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "NextRequest is not yet supported")) } func (ds *fakeDebugSession) onStepInRequest(request *dap.StepInRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "StepInRequest is not yet supported")) } func (ds *fakeDebugSession) onStepOutRequest(request *dap.StepOutRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "StepOutRequest is not yet supported")) } func (ds *fakeDebugSession) onStepBackRequest(request *dap.StepBackRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "StepBackRequest is not yet supported")) } func (ds *fakeDebugSession) onReverseContinueRequest(request *dap.ReverseContinueRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "ReverseContinueRequest is not yet supported")) } func (ds *fakeDebugSession) onRestartFrameRequest(request *dap.RestartFrameRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "RestartFrameRequest is not yet supported")) } func (ds *fakeDebugSession) onGotoRequest(request *dap.GotoRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "GotoRequest is not yet supported")) } func (ds *fakeDebugSession) onPauseRequest(request *dap.PauseRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "PauseRequest is not yet supported")) } func (ds *fakeDebugSession) onStackTraceRequest(request *dap.StackTraceRequest) { response := &dap.StackTraceResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body = dap.StackTraceResponseBody{ StackFrames: []dap.StackFrame{ { Id: 1000, Source: &dap.Source{Name: "hello.go", Path: "/Users/foo/go/src/hello/hello.go", SourceReference: 0}, Line: 5, Column: 0, Name: "main.main", }, }, TotalFrames: 1, } ds.send(response) } func (ds *fakeDebugSession) onScopesRequest(request *dap.ScopesRequest) { response := &dap.ScopesResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body = dap.ScopesResponseBody{ Scopes: []dap.Scope{ {Name: "Local", VariablesReference: 1000, Expensive: false}, {Name: "Global", VariablesReference: 1001, Expensive: true}, }, } ds.send(response) } func (ds *fakeDebugSession) onVariablesRequest(request *dap.VariablesRequest) { select { case <-ds.stopDebug: return // simulate long-running processing to make this handler // respond to this request after the next request is received case <-time.After(100 * time.Millisecond): response := &dap.VariablesResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body = dap.VariablesResponseBody{ Variables: []dap.Variable{{Name: "i", Value: "18434528", EvaluateName: "i", VariablesReference: 0}}, } ds.send(response) } } func (ds *fakeDebugSession) onSetVariableRequest(request *dap.SetVariableRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "setVariableRequest is not yet supported")) } func (ds *fakeDebugSession) onSetExpressionRequest(request *dap.SetExpressionRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "SetExpressionRequest is not yet supported")) } func (ds *fakeDebugSession) onSourceRequest(request *dap.SourceRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "SourceRequest is not yet supported")) } func (ds *fakeDebugSession) onThreadsRequest(request *dap.ThreadsRequest) { response := &dap.ThreadsResponse{} response.Response = *newResponse(request.Seq, request.Command) response.Body = dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: 1, Name: "main"}}} ds.send(response) } func (ds *fakeDebugSession) onTerminateThreadsRequest(request *dap.TerminateThreadsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "TerminateRequest is not yet supported")) } func (ds *fakeDebugSession) onEvaluateRequest(request *dap.EvaluateRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "EvaluateRequest is not yet supported")) } func (ds *fakeDebugSession) onStepInTargetsRequest(request *dap.StepInTargetsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "StepInTargetRequest is not yet supported")) } func (ds *fakeDebugSession) onGotoTargetsRequest(request *dap.GotoTargetsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "GotoTargetRequest is not yet supported")) } func (ds *fakeDebugSession) onCompletionsRequest(request *dap.CompletionsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "CompletionRequest is not yet supported")) } func (ds *fakeDebugSession) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "ExceptionRequest is not yet supported")) } func (ds *fakeDebugSession) onLoadedSourcesRequest(request *dap.LoadedSourcesRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "LoadedRequest is not yet supported")) } func (ds *fakeDebugSession) onDataBreakpointInfoRequest(request *dap.DataBreakpointInfoRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "DataBreakpointInfoRequest is not yet supported")) } func (ds *fakeDebugSession) onSetDataBreakpointsRequest(request *dap.SetDataBreakpointsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "SetDataBreakpointsRequest is not yet supported")) } func (ds *fakeDebugSession) onReadMemoryRequest(request *dap.ReadMemoryRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "ReadMemoryRequest is not yet supported")) } func (ds *fakeDebugSession) onDisassembleRequest(request *dap.DisassembleRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "DisassembleRequest is not yet supported")) } func (ds *fakeDebugSession) onCancelRequest(request *dap.CancelRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "CancelRequest is not yet supported")) } func (ds *fakeDebugSession) onBreakpointLocationsRequest(request *dap.BreakpointLocationsRequest) { ds.send(newErrorResponse(request.Seq, request.Command, "BreakpointLocationsRequest is not yet supported")) } func newEvent(event string) *dap.Event { return &dap.Event{ ProtocolMessage: dap.ProtocolMessage{ Seq: 0, Type: "event", }, Event: event, } } func newResponse(requestSeq int, command string) *dap.Response { return &dap.Response{ ProtocolMessage: dap.ProtocolMessage{ Seq: 0, Type: "response", }, Command: command, RequestSeq: requestSeq, Success: true, } } func newErrorResponse(requestSeq int, command string, message string) *dap.ErrorResponse { er := &dap.ErrorResponse{} er.Response = *newResponse(requestSeq, command) er.Success = false er.Message = "unsupported" er.Body.Error.Format = message er.Body.Error.Id = 12345 return er }