| //===-- InitializeRequestHandler.cpp --------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DAP.h" |
| #include "EventHelper.h" |
| #include "JSONUtils.h" |
| #include "RequestHandler.h" |
| #include "lldb/API/SBEvent.h" |
| #include "lldb/API/SBListener.h" |
| #include "lldb/API/SBStream.h" |
| |
| using namespace lldb; |
| |
| namespace lldb_dap { |
| |
| static void ProgressEventThreadFunction(DAP &dap) { |
| llvm::set_thread_name(dap.name + ".progress_handler"); |
| lldb::SBListener listener("lldb-dap.progress.listener"); |
| dap.debugger.GetBroadcaster().AddListener( |
| listener, lldb::SBDebugger::eBroadcastBitProgress | |
| lldb::SBDebugger::eBroadcastBitExternalProgress); |
| dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); |
| lldb::SBEvent event; |
| bool done = false; |
| while (!done) { |
| if (listener.WaitForEvent(1, event)) { |
| const auto event_mask = event.GetType(); |
| if (event.BroadcasterMatchesRef(dap.broadcaster)) { |
| if (event_mask & eBroadcastBitStopProgressThread) { |
| done = true; |
| } |
| } else { |
| uint64_t progress_id = 0; |
| uint64_t completed = 0; |
| uint64_t total = 0; |
| bool is_debugger_specific = false; |
| const char *message = lldb::SBDebugger::GetProgressFromEvent( |
| event, progress_id, completed, total, is_debugger_specific); |
| if (message) |
| dap.SendProgressEvent(progress_id, message, completed, total); |
| } |
| } |
| } |
| } |
| |
| // All events from the debugger, target, process, thread and frames are |
| // received in this function that runs in its own thread. We are using a |
| // "FILE *" to output packets back to VS Code and they have mutexes in them |
| // them prevent multiple threads from writing simultaneously so no locking |
| // is required. |
| static void EventThreadFunction(DAP &dap) { |
| llvm::set_thread_name(dap.name + ".event_handler"); |
| lldb::SBEvent event; |
| lldb::SBListener listener = dap.debugger.GetListener(); |
| dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread); |
| bool done = false; |
| while (!done) { |
| if (listener.WaitForEvent(1, event)) { |
| const auto event_mask = event.GetType(); |
| if (lldb::SBProcess::EventIsProcessEvent(event)) { |
| lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); |
| if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { |
| auto state = lldb::SBProcess::GetStateFromEvent(event); |
| switch (state) { |
| case lldb::eStateInvalid: |
| // Not a state event |
| break; |
| case lldb::eStateUnloaded: |
| break; |
| case lldb::eStateConnected: |
| break; |
| case lldb::eStateAttaching: |
| break; |
| case lldb::eStateLaunching: |
| break; |
| case lldb::eStateStepping: |
| break; |
| case lldb::eStateCrashed: |
| break; |
| case lldb::eStateDetached: |
| break; |
| case lldb::eStateSuspended: |
| break; |
| case lldb::eStateStopped: |
| // We launch and attach in synchronous mode then the first stop |
| // event will not be delivered. If we use "launchCommands" during a |
| // launch or "attachCommands" during an attach we might some process |
| // stop events which we do not want to send an event for. We will |
| // manually send a stopped event in request_configurationDone(...) |
| // so don't send any before then. |
| if (dap.configuration_done_sent) { |
| // Only report a stopped event if the process was not |
| // automatically restarted. |
| if (!lldb::SBProcess::GetRestartedFromEvent(event)) { |
| SendStdOutStdErr(dap, process); |
| SendThreadStoppedEvent(dap); |
| } |
| } |
| break; |
| case lldb::eStateRunning: |
| dap.WillContinue(); |
| SendContinuedEvent(dap); |
| break; |
| case lldb::eStateExited: |
| lldb::SBStream stream; |
| process.GetStatus(stream); |
| dap.SendOutput(OutputType::Console, stream.GetData()); |
| |
| // When restarting, we can get an "exited" event for the process we |
| // just killed with the old PID, or even with no PID. In that case |
| // we don't have to terminate the session. |
| if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || |
| process.GetProcessID() == dap.restarting_process_id) { |
| dap.restarting_process_id = LLDB_INVALID_PROCESS_ID; |
| } else { |
| // Run any exit LLDB commands the user specified in the |
| // launch.json |
| dap.RunExitCommands(); |
| SendProcessExitedEvent(dap, process); |
| dap.SendTerminatedEvent(); |
| done = true; |
| } |
| break; |
| } |
| } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || |
| (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { |
| SendStdOutStdErr(dap, process); |
| } |
| } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { |
| if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { |
| auto event_type = |
| lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); |
| auto bp = Breakpoint( |
| dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); |
| // If the breakpoint was set through DAP, it will have the |
| // BreakpointBase::GetBreakpointLabel() label. Regardless |
| // of whether locations were added, removed, or resolved, the |
| // breakpoint isn't going away and the reason is always "changed". |
| if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || |
| event_type & lldb::eBreakpointEventTypeLocationsRemoved || |
| event_type & lldb::eBreakpointEventTypeLocationsResolved) && |
| bp.MatchesName(BreakpointBase::GetBreakpointLabel())) { |
| // As the DAP client already knows the path of this breakpoint, we |
| // don't need to send it back as part of the "changed" event. This |
| // avoids sending paths that should be source mapped. Note that |
| // CreateBreakpoint doesn't apply source mapping and certain |
| // implementation ignore the source part of this event anyway. |
| llvm::json::Value source_bp = CreateBreakpoint(&bp); |
| source_bp.getAsObject()->erase("source"); |
| |
| llvm::json::Object body; |
| body.try_emplace("breakpoint", source_bp); |
| body.try_emplace("reason", "changed"); |
| |
| llvm::json::Object bp_event = CreateEventObject("breakpoint"); |
| bp_event.try_emplace("body", std::move(body)); |
| |
| dap.SendJSON(llvm::json::Value(std::move(bp_event))); |
| } |
| } |
| } else if (event.BroadcasterMatchesRef(dap.broadcaster)) { |
| if (event_mask & eBroadcastBitStopEventThread) { |
| done = true; |
| } |
| } |
| } |
| } |
| } |
| |
| // "InitializeRequest": { |
| // "allOf": [ { "$ref": "#/definitions/Request" }, { |
| // "type": "object", |
| // "description": "Initialize request; value of command field is |
| // 'initialize'.", |
| // "properties": { |
| // "command": { |
| // "type": "string", |
| // "enum": [ "initialize" ] |
| // }, |
| // "arguments": { |
| // "$ref": "#/definitions/InitializeRequestArguments" |
| // } |
| // }, |
| // "required": [ "command", "arguments" ] |
| // }] |
| // }, |
| // "InitializeRequestArguments": { |
| // "type": "object", |
| // "description": "Arguments for 'initialize' request.", |
| // "properties": { |
| // "clientID": { |
| // "type": "string", |
| // "description": "The ID of the (frontend) client using this adapter." |
| // }, |
| // "adapterID": { |
| // "type": "string", |
| // "description": "The ID of the debug adapter." |
| // }, |
| // "locale": { |
| // "type": "string", |
| // "description": "The ISO-639 locale of the (frontend) client using |
| // this adapter, e.g. en-US or de-CH." |
| // }, |
| // "linesStartAt1": { |
| // "type": "boolean", |
| // "description": "If true all line numbers are 1-based (default)." |
| // }, |
| // "columnsStartAt1": { |
| // "type": "boolean", |
| // "description": "If true all column numbers are 1-based (default)." |
| // }, |
| // "pathFormat": { |
| // "type": "string", |
| // "_enum": [ "path", "uri" ], |
| // "description": "Determines in what format paths are specified. The |
| // default is 'path', which is the native format." |
| // }, |
| // "supportsVariableType": { |
| // "type": "boolean", |
| // "description": "Client supports the optional type attribute for |
| // variables." |
| // }, |
| // "supportsVariablePaging": { |
| // "type": "boolean", |
| // "description": "Client supports the paging of variables." |
| // }, |
| // "supportsRunInTerminalRequest": { |
| // "type": "boolean", |
| // "description": "Client supports the runInTerminal request." |
| // } |
| // }, |
| // "required": [ "adapterID" ] |
| // }, |
| // "InitializeResponse": { |
| // "allOf": [ { "$ref": "#/definitions/Response" }, { |
| // "type": "object", |
| // "description": "Response to 'initialize' request.", |
| // "properties": { |
| // "body": { |
| // "$ref": "#/definitions/Capabilities", |
| // "description": "The capabilities of this debug adapter." |
| // } |
| // } |
| // }] |
| // } |
| void InitializeRequestHandler::operator()( |
| const llvm::json::Object &request) const { |
| llvm::json::Object response; |
| FillResponse(request, response); |
| llvm::json::Object body; |
| |
| const auto *arguments = request.getObject("arguments"); |
| // sourceInitFile option is not from formal DAP specification. It is only |
| // used by unit tests to prevent sourcing .lldbinit files from environment |
| // which may affect the outcome of tests. |
| bool source_init_file = GetBoolean(arguments, "sourceInitFile", true); |
| |
| // Do not source init files until in/out/err are configured. |
| dap.debugger = lldb::SBDebugger::Create(false); |
| dap.debugger.SetInputFile(dap.in); |
| auto out_fd = dap.out.GetWriteFileDescriptor(); |
| if (llvm::Error err = out_fd.takeError()) { |
| response["success"] = false; |
| EmplaceSafeString(response, "message", llvm::toString(std::move(err))); |
| dap.SendJSON(llvm::json::Value(std::move(response))); |
| return; |
| } |
| dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); |
| auto err_fd = dap.err.GetWriteFileDescriptor(); |
| if (llvm::Error err = err_fd.takeError()) { |
| response["success"] = false; |
| EmplaceSafeString(response, "message", llvm::toString(std::move(err))); |
| dap.SendJSON(llvm::json::Value(std::move(response))); |
| return; |
| } |
| dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); |
| |
| auto interp = dap.debugger.GetCommandInterpreter(); |
| |
| if (source_init_file) { |
| dap.debugger.SkipLLDBInitFiles(false); |
| dap.debugger.SkipAppInitFiles(false); |
| lldb::SBCommandReturnObject init; |
| interp.SourceInitFileInGlobalDirectory(init); |
| interp.SourceInitFileInHomeDirectory(init); |
| } |
| |
| if (llvm::Error err = dap.RunPreInitCommands()) { |
| response["success"] = false; |
| EmplaceSafeString(response, "message", llvm::toString(std::move(err))); |
| dap.SendJSON(llvm::json::Value(std::move(response))); |
| return; |
| } |
| |
| dap.PopulateExceptionBreakpoints(); |
| auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand( |
| "lldb-dap", "Commands for managing lldb-dap."); |
| if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) { |
| cmd.AddCommand( |
| "start-debugging", new StartDebuggingRequestHandler(dap), |
| "Sends a startDebugging request from the debug adapter to the client " |
| "to start a child debug session of the same type as the caller."); |
| } |
| cmd.AddCommand( |
| "repl-mode", new ReplModeRequestHandler(dap), |
| "Get or set the repl behavior of lldb-dap evaluation requests."); |
| cmd.AddCommand("send-event", new SendEventRequestHandler(dap), |
| "Sends an DAP event to the client."); |
| |
| dap.progress_event_thread = |
| std::thread(ProgressEventThreadFunction, std::ref(dap)); |
| |
| // Start our event thread so we can receive events from the debugger, target, |
| // process and more. |
| dap.event_thread = std::thread(EventThreadFunction, std::ref(dap)); |
| |
| // The debug adapter supports the configurationDoneRequest. |
| body.try_emplace("supportsConfigurationDoneRequest", true); |
| // The debug adapter supports function breakpoints. |
| body.try_emplace("supportsFunctionBreakpoints", true); |
| // The debug adapter supports conditional breakpoints. |
| body.try_emplace("supportsConditionalBreakpoints", true); |
| // The debug adapter supports breakpoints that break execution after a |
| // specified number of hits. |
| body.try_emplace("supportsHitConditionalBreakpoints", true); |
| // The debug adapter supports a (side effect free) evaluate request for |
| // data hovers. |
| body.try_emplace("supportsEvaluateForHovers", true); |
| // Available filters or options for the setExceptionBreakpoints request. |
| llvm::json::Array filters; |
| for (const auto &exc_bp : *dap.exception_breakpoints) { |
| filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp)); |
| } |
| body.try_emplace("exceptionBreakpointFilters", std::move(filters)); |
| // The debug adapter supports launching a debugee in intergrated VSCode |
| // terminal. |
| body.try_emplace("supportsRunInTerminalRequest", true); |
| // The debug adapter supports stepping back via the stepBack and |
| // reverseContinue requests. |
| body.try_emplace("supportsStepBack", false); |
| // The debug adapter supports setting a variable to a value. |
| body.try_emplace("supportsSetVariable", true); |
| // The debug adapter supports restarting a frame. |
| body.try_emplace("supportsRestartFrame", false); |
| // The debug adapter supports the gotoTargetsRequest. |
| body.try_emplace("supportsGotoTargetsRequest", false); |
| // The debug adapter supports the stepInTargetsRequest. |
| body.try_emplace("supportsStepInTargetsRequest", true); |
| // The debug adapter supports the completions request. |
| body.try_emplace("supportsCompletionsRequest", true); |
| // The debug adapter supports the disassembly request. |
| body.try_emplace("supportsDisassembleRequest", true); |
| // The debug adapter supports the `breakpointLocations` request. |
| body.try_emplace("supportsBreakpointLocationsRequest", true); |
| // The debug adapter supports stepping granularities (argument `granularity`) |
| // for the stepping requests. |
| body.try_emplace("supportsSteppingGranularity", true); |
| // The debug adapter support for instruction breakpoint. |
| body.try_emplace("supportsInstructionBreakpoints", true); |
| |
| llvm::json::Array completion_characters; |
| completion_characters.emplace_back("."); |
| completion_characters.emplace_back(" "); |
| completion_characters.emplace_back("\t"); |
| body.try_emplace("completionTriggerCharacters", |
| std::move(completion_characters)); |
| |
| // The debug adapter supports the modules request. |
| body.try_emplace("supportsModulesRequest", true); |
| // The set of additional module information exposed by the debug adapter. |
| // body.try_emplace("additionalModuleColumns"] = ColumnDescriptor |
| // Checksum algorithms supported by the debug adapter. |
| // body.try_emplace("supportedChecksumAlgorithms"] = ChecksumAlgorithm |
| // The debug adapter supports the RestartRequest. In this case a client |
| // should not implement 'restart' by terminating and relaunching the adapter |
| // but by calling the RestartRequest. |
| body.try_emplace("supportsRestartRequest", true); |
| // The debug adapter supports 'exceptionOptions' on the |
| // setExceptionBreakpoints request. |
| body.try_emplace("supportsExceptionOptions", true); |
| // The debug adapter supports a 'format' attribute on the stackTraceRequest, |
| // variablesRequest, and evaluateRequest. |
| body.try_emplace("supportsValueFormattingOptions", true); |
| // The debug adapter supports the exceptionInfo request. |
| body.try_emplace("supportsExceptionInfoRequest", true); |
| // The debug adapter supports the 'terminateDebuggee' attribute on the |
| // 'disconnect' request. |
| body.try_emplace("supportTerminateDebuggee", true); |
| // The debug adapter supports the delayed loading of parts of the stack, |
| // which requires that both the 'startFrame' and 'levels' arguments and the |
| // 'totalFrames' result of the 'StackTrace' request are supported. |
| body.try_emplace("supportsDelayedStackTraceLoading", true); |
| // The debug adapter supports the 'loadedSources' request. |
| body.try_emplace("supportsLoadedSourcesRequest", false); |
| // The debug adapter supports sending progress reporting events. |
| body.try_emplace("supportsProgressReporting", true); |
| // The debug adapter supports 'logMessage' in breakpoint. |
| body.try_emplace("supportsLogPoints", true); |
| // The debug adapter supports data watchpoints. |
| body.try_emplace("supportsDataBreakpoints", true); |
| // The debug adapter supports the `readMemory` request. |
| body.try_emplace("supportsReadMemoryRequest", true); |
| |
| // Put in non-DAP specification lldb specific information. |
| llvm::json::Object lldb_json; |
| lldb_json.try_emplace("version", dap.debugger.GetVersionString()); |
| body.try_emplace("__lldb", std::move(lldb_json)); |
| |
| response.try_emplace("body", std::move(body)); |
| dap.SendJSON(llvm::json::Value(std::move(response))); |
| } |
| |
| } // namespace lldb_dap |