diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index adad75a79fa7a..6e71c1746b648 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -37,6 +37,10 @@ add_lldb_tool(lldb-dap Transport.cpp Watchpoint.cpp + Events/ExitedEventHandler.cpp + Events/ProcessEventHandler.cpp + Events/OutputEventHandler.cpp + Handler/ResponseHandler.cpp Handler/AttachRequestHandler.cpp Handler/BreakpointLocationsHandler.cpp @@ -75,8 +79,9 @@ add_lldb_tool(lldb-dap Handler/VariablesRequestHandler.cpp Protocol/ProtocolBase.cpp - Protocol/ProtocolTypes.cpp + Protocol/ProtocolEvents.cpp Protocol/ProtocolRequests.cpp + Protocol/ProtocolTypes.cpp LINK_LIBS liblldb diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index a1e2187288768..1cd6e52d9e7b7 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -8,6 +8,7 @@ #include "DAP.h" #include "DAPLog.h" +#include "Events/EventHandler.h" #include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" @@ -82,7 +83,8 @@ DAP::DAP(llvm::StringRef path, std::ofstream *log, configuration_done_sent(false), waiting_for_run_in_terminal(false), progress_event_reporter( [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }), - reverse_request_seq(0), repl_mode(default_repl_mode) {} + reverse_request_seq(0), repl_mode(default_repl_mode), SendExited(*this), + SendProcess(*this), SendOutput(*this) {} DAP::~DAP() = default; @@ -202,12 +204,12 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true); if (auto Error = out.RedirectTo(overrideOut, [this](llvm::StringRef output) { - SendOutput(OutputType::Stdout, output); + SendOutput(output, OutputCategory::Stdout); })) return Error; if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) { - SendOutput(OutputType::Stderr, output); + SendOutput(output, OutputCategory::Stderr); })) return Error; @@ -246,103 +248,6 @@ void DAP::Send(const protocol::Message &message) { transport.GetClientName()); } -// "OutputEvent": { -// "allOf": [ { "$ref": "#/definitions/Event" }, { -// "type": "object", -// "description": "Event message for 'output' event type. The event -// indicates that the target has produced some output.", -// "properties": { -// "event": { -// "type": "string", -// "enum": [ "output" ] -// }, -// "body": { -// "type": "object", -// "properties": { -// "category": { -// "type": "string", -// "description": "The output category. If not specified, -// 'console' is assumed.", -// "_enum": [ "console", "stdout", "stderr", "telemetry" ] -// }, -// "output": { -// "type": "string", -// "description": "The output to report." -// }, -// "variablesReference": { -// "type": "number", -// "description": "If an attribute 'variablesReference' exists -// and its value is > 0, the output contains -// objects which can be retrieved by passing -// variablesReference to the VariablesRequest." -// }, -// "source": { -// "$ref": "#/definitions/Source", -// "description": "An optional source location where the output -// was produced." -// }, -// "line": { -// "type": "integer", -// "description": "An optional source location line where the -// output was produced." -// }, -// "column": { -// "type": "integer", -// "description": "An optional source location column where the -// output was produced." -// }, -// "data": { -// "type":["array","boolean","integer","null","number","object", -// "string"], -// "description": "Optional data to report. For the 'telemetry' -// category the data will be sent to telemetry, for -// the other categories the data is shown in JSON -// format." -// } -// }, -// "required": ["output"] -// } -// }, -// "required": [ "event", "body" ] -// }] -// } -void DAP::SendOutput(OutputType o, const llvm::StringRef output) { - if (output.empty()) - return; - - const char *category = nullptr; - switch (o) { - case OutputType::Console: - category = "console"; - break; - case OutputType::Stdout: - category = "stdout"; - break; - case OutputType::Stderr: - category = "stderr"; - break; - case OutputType::Telemetry: - category = "telemetry"; - break; - } - - // Send each line of output as an individual event, including the newline if - // present. - ::size_t idx = 0; - do { - ::size_t end = output.find('\n', idx); - if (end == llvm::StringRef::npos) - end = output.size() - 1; - llvm::json::Object event(CreateEventObject("output")); - llvm::json::Object body; - body.try_emplace("category", category); - EmplaceSafeString(body, "output", output.slice(idx, end + 1).str()); - event.try_emplace("body", std::move(body)); - SendJSON(llvm::json::Value(std::move(event))); - idx = end + 1; - } while (idx < output.size()); -} - // interface ProgressStartEvent extends Event { // event: 'progressStart'; // @@ -441,16 +346,17 @@ void DAP::SendProgressEvent(uint64_t progress_id, const char *message, progress_event_reporter.Push(progress_id, message, completed, total); } -void __attribute__((format(printf, 3, 4))) -DAP::SendFormattedOutput(OutputType o, const char *format, ...) { - char buffer[1024]; - va_list args; - va_start(args, format); - int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); - va_end(args); - SendOutput( - o, llvm::StringRef(buffer, std::min(actual_length, sizeof(buffer)))); -} +// void __attribute__((format(printf, 3, 4))) +// DAP::SendFormattedOutput(OutputType o, const char *format, ...) { +// char buffer[1024]; +// va_list args; +// va_start(args, format); +// int actual_length = vsnprintf(buffer, sizeof(buffer), format, args); +// va_end(args); +// SendOutput( +// o, llvm::StringRef(buffer, std::min(actual_length, +// sizeof(buffer)))); +// } ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) { const auto num = thread.GetStopReasonDataCount(); @@ -564,7 +470,7 @@ bool DAP::RunLLDBCommands(llvm::StringRef prefix, bool required_command_failed = false; std::string output = ::RunLLDBCommands(debugger, prefix, commands, required_command_failed); - SendOutput(OutputType::Console, output); + SendOutput(output); return !required_command_failed; } @@ -1041,8 +947,7 @@ void DAP::SetFrameFormat(llvm::StringRef format) { lldb::SBError error; frame_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { - SendOutput(OutputType::Console, - llvm::formatv( + SendOutput(llvm::formatv( "The provided frame format '{0}' couldn't be parsed: {1}\n", format, error.GetCString()) .str()); @@ -1055,8 +960,7 @@ void DAP::SetThreadFormat(llvm::StringRef format) { lldb::SBError error; thread_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { - SendOutput(OutputType::Console, - llvm::formatv( + SendOutput(llvm::formatv( "The provided thread format '{0}' couldn't be parsed: {1}\n", format, error.GetCString()) .str()); diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 4c57f9fef3d89..1ad73c9838955 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -10,6 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_DAP_H #include "DAPForward.h" +#include "Events/EventHandler.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "InstructionBreakpoint.h" @@ -58,8 +59,6 @@ typedef llvm::StringMap FunctionBreakpointMap; typedef llvm::DenseMap InstructionBreakpointMap; -enum class OutputType { Console, Stdout, Stderr, Telemetry }; - /// Buffer size for handling output events. constexpr uint64_t OutputBufferSize = (1u << 12); @@ -230,6 +229,19 @@ struct DAP { void operator=(const DAP &rhs) = delete; /// @} + /// Typed Events Handlers + /// @{ + + /// Sends an event that the debuggee has exited. + ExitedEventHandler SendExited; + /// Sends an event that indicates that the debugger has begun debugging a new + /// process. + ProcessEventHandler SendProcess; + /// Sends an event that indicates the target has produced some output. + OutputEventHandler SendOutput; + + /// @} + ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); @@ -249,14 +261,9 @@ struct DAP { /// Send the given message to the client void Send(const protocol::Message &message); - void SendOutput(OutputType o, const llvm::StringRef output); - void SendProgressEvent(uint64_t progress_id, const char *message, uint64_t completed, uint64_t total); - void __attribute__((format(printf, 3, 4))) - SendFormattedOutput(OutputType o, const char *format, ...); - static int64_t GetNextSourceReference(); ExceptionBreakpoint *GetExceptionBPFromStopReason(lldb::SBThread &thread); diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 705eb0a457d9c..77b9482b32bd5 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -8,6 +8,7 @@ #include "EventHelper.h" #include "DAP.h" +#include "Events/EventHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "lldb/API/SBFileSpec.h" @@ -32,87 +33,6 @@ static void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) { dap.SendJSON(llvm::json::Value(std::move(event))); } -// "ProcessEvent": { -// "allOf": [ -// { "$ref": "#/definitions/Event" }, -// { -// "type": "object", -// "description": "Event message for 'process' event type. The event -// indicates that the debugger has begun debugging a -// new process. Either one that it has launched, or one -// that it has attached to.", -// "properties": { -// "event": { -// "type": "string", -// "enum": [ "process" ] -// }, -// "body": { -// "type": "object", -// "properties": { -// "name": { -// "type": "string", -// "description": "The logical name of the process. This is -// usually the full path to process's executable -// file. Example: /home/myproj/program.js." -// }, -// "systemProcessId": { -// "type": "integer", -// "description": "The system process id of the debugged process. -// This property will be missing for non-system -// processes." -// }, -// "isLocalProcess": { -// "type": "boolean", -// "description": "If true, the process is running on the same -// computer as the debug adapter." -// }, -// "startMethod": { -// "type": "string", -// "enum": [ "launch", "attach", "attachForSuspendedLaunch" ], -// "description": "Describes how the debug engine started -// debugging this process.", -// "enumDescriptions": [ -// "Process was launched under the debugger.", -// "Debugger attached to an existing process.", -// "A project launcher component has launched a new process in -// a suspended state and then asked the debugger to attach." -// ] -// } -// }, -// "required": [ "name" ] -// } -// }, -// "required": [ "event", "body" ] -// } -// ] -// } -void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { - lldb::SBFileSpec exe_fspec = dap.target.GetExecutable(); - char exe_path[PATH_MAX]; - exe_fspec.GetPath(exe_path, sizeof(exe_path)); - llvm::json::Object event(CreateEventObject("process")); - llvm::json::Object body; - EmplaceSafeString(body, "name", std::string(exe_path)); - const auto pid = dap.target.GetProcess().GetProcessID(); - body.try_emplace("systemProcessId", (int64_t)pid); - body.try_emplace("isLocalProcess", true); - const char *startMethod = nullptr; - switch (launch_method) { - case Launch: - startMethod = "launch"; - break; - case Attach: - startMethod = "attach"; - break; - case AttachForSuspendedLaunch: - startMethod = "attachForSuspendedLaunch"; - break; - } - body.try_emplace("startMethod", startMethod); - event.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(event))); -} - // Send a thread stopped event for all threads as long as the process // is stopped. void SendThreadStoppedEvent(DAP &dap) { @@ -209,9 +129,9 @@ void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) { char buffer[OutputBufferSize]; size_t count; while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) - dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); + dap.SendOutput(llvm::StringRef(buffer, count), OutputCategory::Stdout); while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) - dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); + dap.SendOutput(llvm::StringRef(buffer, count), OutputCategory::Stderr); } // Send a "continued" event to indicate the process is in the running state. @@ -235,13 +155,4 @@ void SendContinuedEvent(DAP &dap) { dap.SendJSON(llvm::json::Value(std::move(event))); } -// Send a "exited" event to indicate the process has exited. -void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) { - llvm::json::Object event(CreateEventObject("exited")); - llvm::json::Object body; - body.try_emplace("exitCode", (int64_t)process.GetExitStatus()); - event.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(event))); -} - } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index 90b009c73089e..e6a54e63d00df 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -14,10 +14,6 @@ namespace lldb_dap { struct DAP; -enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; - -void SendProcessEvent(DAP &dap, LaunchMethod launch_method); - void SendThreadStoppedEvent(DAP &dap); void SendTerminatedEvent(DAP &dap); @@ -26,8 +22,6 @@ void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process); void SendContinuedEvent(DAP &dap); -void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process); - } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/Events/EventHandler.h b/lldb/tools/lldb-dap/Events/EventHandler.h new file mode 100644 index 0000000000000..5ffc088a1708d --- /dev/null +++ b/lldb/tools/lldb-dap/Events/EventHandler.h @@ -0,0 +1,61 @@ +//===-- EventHandler.h ----------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_EVENTS_EVENT_HANDLER +#define LLDB_TOOLS_LLDB_DAP_EVENTS_EVENT_HANDLER + +#include "DAPForward.h" +#include "Protocol/ProtocolEvents.h" +#include "llvm/ADT/StringRef.h" + +namespace lldb_dap { + +/// An event handler for triggering DAP events. +class EventHandler { +public: + EventHandler(DAP &dap) : dap(dap) {} + virtual ~EventHandler() = default; + +protected: + DAP &dap; +}; + +/// Handler for the event indicates that the debuggee has exited and returns its +/// exit code. +class ExitedEventHandler : public EventHandler { +public: + using EventHandler::EventHandler; + static constexpr llvm::StringLiteral event = "exited"; + void operator()(lldb::SBProcess &process) const; +}; + +using ProcessStartMethod = protocol::ProcessEventBody::StartMethod; + +/// Handler for the event indicates that the debugger has begun debugging a new +/// process. Either one that it has launched, or one that it has attached to. +class ProcessEventHandler : public EventHandler { +public: + using EventHandler::EventHandler; + static constexpr llvm::StringLiteral event = "process"; + void operator()(lldb::SBTarget &target, ProcessStartMethod startMethod) const; +}; + +using OutputCategory = protocol::OutputEventBody::Category; + +/// Handle for the event indicates that the target has produced some output. +class OutputEventHandler : public EventHandler { +public: + using EventHandler::EventHandler; + static constexpr llvm::StringLiteral event = "output"; + void operator()(llvm::StringRef output, + OutputCategory category = OutputCategory::Console) const; +}; + +} // end namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/Events/ExitedEventHandler.cpp b/lldb/tools/lldb-dap/Events/ExitedEventHandler.cpp new file mode 100644 index 0000000000000..96e0ad1350366 --- /dev/null +++ b/lldb/tools/lldb-dap/Events/ExitedEventHandler.cpp @@ -0,0 +1,25 @@ +//===-- ExitedEventHandler.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 "Events/EventHandler.h" +#include "lldb/API/SBProcess.h" + +namespace lldb_dap { + +void ExitedEventHandler::operator()(lldb::SBProcess &process) const { + if (!process.IsValid()) + return; + + protocol::ExitedEventBody body; + body.exitCode = process.GetExitStatus(); + dap.Send(protocol::Event{/*event=*/ExitedEventHandler::event.str(), + /*body=*/std::move(body)}); +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Events/OutputEventHandler.cpp b/lldb/tools/lldb-dap/Events/OutputEventHandler.cpp new file mode 100644 index 0000000000000..d7a1e0a44d0cd --- /dev/null +++ b/lldb/tools/lldb-dap/Events/OutputEventHandler.cpp @@ -0,0 +1,43 @@ +//===-- OutputEventHandler.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 "Events/EventHandler.h" +#include "Protocol/ProtocolEvents.h" +#include "llvm/ADT/StringRef.h" + +using namespace llvm; +using namespace lldb_dap::protocol; + +namespace lldb_dap { + +void OutputEventHandler::operator()(llvm::StringRef output, + OutputCategory category) const { + if (output.empty()) + return; + + // Send each line of output as an individual event, including the newline if + // present. + size_t idx = 0; + do { + size_t end = output.find('\n', idx); + if (end == llvm::StringRef::npos) + end = output.size() - 1; + + OutputEventBody body; + body.output = output.slice(idx, end + 1).str(); + body.category = category; + + dap.Send(protocol::Event{/*event=*/OutputEventHandler::event.str(), + /*body*/ body}); + + idx = end + 1; + } while (idx < output.size()); +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Events/ProcessEventHandler.cpp b/lldb/tools/lldb-dap/Events/ProcessEventHandler.cpp new file mode 100644 index 0000000000000..da046c7827ec5 --- /dev/null +++ b/lldb/tools/lldb-dap/Events/ProcessEventHandler.cpp @@ -0,0 +1,42 @@ +//===-- ProcessEventHandler.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 "Events/EventHandler.h" +#include "Protocol/ProtocolEvents.h" +#include "lldb/API/SBProcess.h" +#include "lldb/API/SBTarget.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_dap::protocol; + +namespace lldb_dap { + +void ProcessEventHandler::operator()(SBTarget &target, + ProcessStartMethod startMethod) const { + SBProcess process = target.GetProcess(); + if (!target.IsValid() || !process.IsValid()) + return; + + char path[PATH_MAX] = {0}; + target.GetExecutable().GetPath(path, sizeof(path)); + + ProcessEventBody body; + body.name = path; + body.systemProcessId = process.GetProcessID(); + body.isLocalProcess = target.GetPlatform().GetName() == + lldb::SBPlatform::GetHostPlatform().GetName(); + body.startMethod = startMethod; + body.pointerSize = target.GetAddressByteSize(); + + dap.Send(protocol::Event{/*event=*/ProcessEventHandler::event.str(), + /*body*/ body}); +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 20f7c80a1ed90..de6dc0e80a013 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -8,6 +8,7 @@ #include "DAP.h" #include "EventHelper.h" +#include "Events/EventHandler.h" #include "JSONUtils.h" #include "RequestHandler.h" #include "lldb/API/SBListener.h" @@ -131,8 +132,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", dap.target.GetExecutable().GetFilename()); - dap.SendOutput(OutputType::Console, - llvm::StringRef(attach_msg, attach_msg_len)); + dap.SendOutput(llvm::StringRef(attach_msg, attach_msg_len)); } if (attachCommands.empty()) { // No "attachCommands", just attach normally. @@ -201,7 +201,7 @@ void AttachRequestHandler::operator()(const llvm::json::Object &request) const { dap.SendJSON(llvm::json::Value(std::move(response))); if (error.Success()) { - SendProcessEvent(dap, Attach); + dap.SendProcess(dap.target, ProcessStartMethod::attach); dap.SendJSON(CreateEventObject("initialized")); } } diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 3262b70042a0e..a1b8bf7294612 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -166,7 +166,7 @@ static void EventThreadFunction(DAP &dap) { case lldb::eStateExited: lldb::SBStream stream; process.GetStatus(stream); - dap.SendOutput(OutputType::Console, stream.GetData()); + dap.SendOutput(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 @@ -178,7 +178,7 @@ static void EventThreadFunction(DAP &dap) { // Run any exit LLDB commands the user specified in the // launch.json dap.RunExitCommands(); - SendProcessExitedEvent(dap, process); + dap.SendExited(process); dap.SendTerminatedEvent(); done = true; } diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index f64c186376a36..67429446d725f 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -8,6 +8,7 @@ #include "DAP.h" #include "EventHelper.h" +#include "Events/EventHandler.h" #include "JSONUtils.h" #include "RequestHandler.h" #include "llvm/Support/FileSystem.h" @@ -123,12 +124,9 @@ void LaunchRequestHandler::operator()(const llvm::json::Object &request) const { dap.SendJSON(llvm::json::Value(std::move(response))); - if (!status.Fail()) { - if (dap.is_attach) - SendProcessEvent(dap, Attach); // this happens when doing runInTerminal - else - SendProcessEvent(dap, Launch); - } + if (!status.Fail()) + dap.SendProcess(dap.target, ProcessStartMethod::launch); + dap.SendJSON(CreateEventObject("initialized")); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 60c82649938d6..b0bea1ed73901 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -66,7 +66,7 @@ void BaseRequestHandler::SetSourceMapFromArguments( if (mapping == nullptr || mapping->size() != 2 || (*mapping)[0].kind() != llvm::json::Value::String || (*mapping)[1].kind() != llvm::json::Value::String) { - dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + dap.SendOutput(llvm::StringRef(sourceMapHelp)); return; } const auto mapFrom = GetAsString((*mapping)[0]); @@ -81,7 +81,7 @@ void BaseRequestHandler::SetSourceMapFromArguments( } } else { if (ObjectContainsKey(arguments, sourceMapKey)) { - dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + dap.SendOutput(llvm::StringRef(sourceMapHelp)); return; } if (sourcePath.empty()) diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h index e10a903b80aaa..2ddfeb8ae0852 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h @@ -17,8 +17,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_H -#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_H +#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H +#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_BASE_H #include "llvm/Support/JSON.h" #include diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp new file mode 100644 index 0000000000000..32cd2e0076cef --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp @@ -0,0 +1,96 @@ +//===-- ProtocolEvents.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 "Protocol/ProtocolEvents.h" +#include "Events/EventHandler.h" +#include "llvm/Support/JSON.h" + +using namespace llvm; + +namespace lldb_dap::protocol { + +json::Value toJSON(const ExitedEventBody &EEB) { + return json::Object{{"exitCode", EEB.exitCode}}; +} + +json::Value toJSON(const ProcessEventBody::StartMethod &m) { + switch (m) { + case ProcessEventBody::StartMethod::launch: + return "launch"; + case ProcessEventBody::StartMethod::attach: + return "attach"; + case ProcessEventBody::StartMethod::attachForSuspendedLaunch: + return "attachForSuspendedLaunch"; + } +} + +json::Value toJSON(const ProcessEventBody &PEB) { + json::Object result{{"name", PEB.name}}; + + if (PEB.systemProcessId) + result.insert({"systemProcessId", PEB.systemProcessId}); + if (PEB.isLocalProcess) + result.insert({"isLocalProcess", PEB.isLocalProcess}); + if (PEB.startMethod) + result.insert({"startMethod", PEB.startMethod}); + if (PEB.pointerSize) + result.insert({"pointerSize", PEB.pointerSize}); + + return std::move(result); +} + +json::Value toJSON(const OutputEventBody::Category &C) { + switch (C) { + case OutputEventBody::Category::Console: + return "console"; + case OutputEventBody::Category::Important: + return "important"; + case OutputEventBody::Category::Stdout: + return "stdout"; + case OutputEventBody::Category::Stderr: + return "stderr"; + case OutputEventBody::Category::Telemetry: + return "telemetry"; + } +} + +json::Value toJSON(const OutputEventBody::Group &G) { + switch (G) { + case OutputEventBody::Group::start: + return "start"; + case OutputEventBody::Group::startCollapsed: + return "startCollapsed"; + case OutputEventBody::Group::end: + return "end"; + } +} + +json::Value toJSON(const OutputEventBody &OEB) { + json::Object result{{"output", OEB.output}}; + + if (OEB.category) + result.insert({"category", *OEB.category}); + if (OEB.group) + result.insert({"group", *OEB.group}); + if (OEB.variablesReference) + result.insert({"variablesReference", *OEB.variablesReference}); + if (OEB.source) + result.insert({"source", *OEB.source}); + if (OEB.line) + result.insert({"line", *OEB.line}); + if (OEB.column) + result.insert({"column", *OEB.column}); + if (OEB.data) + result.insert({"data", *OEB.data}); + if (OEB.locationReference) + result.insert({"locationReference", *OEB.locationReference}); + + return std::move(result); +} + +} // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h new file mode 100644 index 0000000000000..27f4424f62a35 --- /dev/null +++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h @@ -0,0 +1,176 @@ +//===-- ProtocolEvents.h --------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file contains POD structs based on the DAP specification at +// https://microsoft.github.io/debug-adapter-protocol/specification +// +// This is not meant to be a complete implementation, new interfaces are added +// when they're needed. +// +// Each struct has a toJSON and fromJSON function, that converts between +// the struct and a JSON representation. (See JSON.h) +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H +#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H + +#include "Protocol/ProtocolTypes.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" + +namespace lldb_dap::protocol { + +// MARK: Events + +/// The event indicates that the debuggee has exited and returns its exit code. +struct ExitedEventBody { + static llvm::StringLiteral getEvent() { return "exited"; } + + /// The exit code returned from the debuggee. + int exitCode; +}; +llvm::json::Value toJSON(const ExitedEventBody &); + +/// The event indicates that the debugger has begun debugging a new process. +/// Either one that it has launched, or one that it has attached to. +struct ProcessEventBody { + /// The logical name of the process. This is usually the full path to + /// process's executable file. Example: /home/example/myproj/program.js. + std::string name; + + /// The process ID of the debugged process, as assigned by the operating + /// system. This property should be omitted for logical processes that do not + /// map to operating system processes on the machine. + std::optional systemProcessId; + + /// If true, the process is running on the same computer as the debug adapter. + std::optional isLocalProcess; + + enum class StartMethod { + /// Process was launched under the debugger. + launch, + /// Debugger attached to an existing process. + attach, + /// A project launcher component has launched a new process in a suspended + /// state and then asked the debugger to attach. + attachForSuspendedLaunch + }; + + /// Describes how the debug engine started debugging this process. + std::optional startMethod; + + /// The size of a pointer or address for this process, in bits. This value may + /// be used by clients when formatting addresses for display. + std::optional pointerSize; +}; +llvm::json::Value toJSON(const ProcessEventBody &); + +/// The event indicates that the target has produced some output. +struct OutputEventBody { + enum class Category { + /// Show the output in the client's default message UI, e.g. a + /// 'debug console'. This category should only be used for informational + /// output from the debugger (as opposed to the debuggee). + Console, + /// A hint for the client to show the output in the client's UI + /// for important and highly visible information, e.g. as a popup + /// notification. This category should only be used for important messages + /// from the debugger (as opposed to the debuggee). Since this category + /// value + /// is a hint, clients might ignore the hint and assume the `console` + /// category. + Important, + /// Show the output as normal program output from the debuggee. + Stdout, + /// Show the output as error program output from the debuggee. + Stderr, + /// Send the output to telemetry instead of showing it to the user. + Telemetry, + }; + + /// The output category. If not specified or if the category is not + /// understood by the client, `console` is assumed. + std::optional category; + + /// The output to report. + /// + /// ANSI escape sequences may be used to influence text color and styling if + /// `supportsANSIStyling` is present in both the adapter's `Capabilities` and + /// the client's `InitializeRequestArguments`. A client may strip any + /// unrecognized ANSI sequences. + /// + /// If the `supportsANSIStyling` capabilities are not both true, then the + /// client should display the output literally. + std::string output; + + enum class Group { + /// Start a new group in expanded mode. Subsequent output events are members + /// of the group and should be shown indented. + /// + /// The `output` attribute becomes the name of the group and is not + /// indented. + start, + + /// Start a new group in collapsed mode. Subsequent output events are + /// members of the group and should be shown indented (as soon as the group + /// is expanded). + /// + /// The `output` attribute becomes the name of the group and is not + /// indented. + startCollapsed, + + /// End the current group and decrease the indentation of subsequent output + /// events. + /// + /// A non-empty `output` attribute is shown as the unindented end of the + /// group. + end, + }; + + /// Support for keeping an output log organized by grouping related messages. + std::optional group; + + /// If an attribute `variablesReference` exists and its value is > 0, the + /// output contains objects which can be retrieved by passing + /// `variablesReference` to the `variables` request as long as execution + /// remains suspended. See 'Lifetime of Object References' in the Overview + /// section for details. + std::optional variablesReference; + + /// The source location where the output was produced. + std::optional source; + + /// The source location's line where the output was produced. + std::optional line; + /// The position in `line` where the output was produced. It is measured in + /// UTF-16 code units and the client capability `columnsStartAt1` determines + /// whether it is 0- or 1-based. + std::optional column; + + /// Additional data to report. For the `telemetry` category the data is + /// sent to telemetry, for the other categories the data is shown in JSON + /// format. + std::optional data; + + /// A reference that allows the client to request the location where the new + /// value is declared. For example, if the logged value is function pointer, + /// the adapter may be able to look up the function's location. This should + /// be present only if the adapter is likely to be able to resolve the + /// location. + /// + /// This reference shares the same lifetime as the `variablesReference`. See + /// 'Lifetime of Object References' in the Overview section for details. + std::optional locationReference; +}; +llvm::json::Value toJSON(const OutputEventBody &); + +} // namespace lldb_dap::protocol + +#endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index efb5c3abe32bf..62569fdd43f2c 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -37,6 +37,32 @@ bool fromJSON(const json::Value &Params, Source::PresentationHint &PH, return true; } +json::Value toJSON(const Source::PresentationHint &P) { + switch (P) { + case Source::PresentationHint::normal: + return "normal"; + case Source::PresentationHint::emphasize: + return "emphasize"; + case Source::PresentationHint::deemphasize: + return "deemphasize"; + } +} + +json::Value toJSON(const Source &S) { + json::Object result; + + if (S.name) + result.insert({"name", *S.name}); + if (S.path) + result.insert({"path", *S.path}); + if (S.sourceReference) + result.insert({"sourceReference", *S.sourceReference}); + if (S.presentationHint) + result.insert({"presentationHint", *S.presentationHint}); + + return std::move(result); +} + bool fromJSON(const json::Value &Params, Source &S, json::Path P) { json::ObjectMapper O(Params, P); return O && O.mapOptional("name", S.name) && O.mapOptional("path", S.path) && diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index b54d76cb29a77..7e17306402a9f 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -56,6 +56,7 @@ struct Source { // unsupported keys: origin, sources, adapterData, checksums }; +llvm::json::Value toJSON(const Source &); bool fromJSON(const llvm::json::Value &, Source &, llvm::json::Path); } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp index 4c6b36119c84f..2b286af3fe0f4 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp +++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp @@ -294,7 +294,7 @@ void SourceBreakpoint::SetLogMessage() { void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) { std::string message = "Log message has error: "; message += error; - dap.SendOutput(OutputType::Console, message); + dap.SendOutput(message); } /*static*/ @@ -328,7 +328,7 @@ bool SourceBreakpoint::BreakpointHitCallback( } if (!output.empty() && output.back() != '\n') output.push_back('\n'); // Ensure log message has line break. - bp->dap.SendOutput(OutputType::Console, output.c_str()); + bp->dap.SendOutput(output); // Do not stop. return false; diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 59e31cf8e2cc8..f2ff2c48d5afc 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -9,6 +9,7 @@ #include "DAP.h" #include "DAPLog.h" #include "EventHelper.h" +#include "Events/EventHandler.h" #include "Handler/RequestHandler.h" #include "RunInTerminal.h" #include "Transport.h"