Author: John Harrison Date: 2025-03-12T12:29:05-07:00 New Revision: 7790d69cce048d7c81fceaf979fd2ec60e37476b
URL: https://github.com/llvm/llvm-project/commit/7790d69cce048d7c81fceaf979fd2ec60e37476b DIFF: https://github.com/llvm/llvm-project/commit/7790d69cce048d7c81fceaf979fd2ec60e37476b.diff LOG: [lldb-dap] Refactoring IOStream into Transport handler. (#130026) Instead of having two discrete InputStream and OutputStream helpers, this merges the two into a unifed 'Transport' handler. This handler is responsible for reading the DAP message headers, parsing the resulting JSON and converting the messages into `lldb_dap::protocol::Message`s for both input and output. --------- Co-authored-by: Jonas Devlieghere <jo...@devlieghere.com> Added: lldb/tools/lldb-dap/Transport.cpp lldb/tools/lldb-dap/Transport.h Modified: lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py lldb/test/API/tools/lldb-dap/io/TestDAP_io.py lldb/tools/lldb-dap/CMakeLists.txt lldb/tools/lldb-dap/DAP.cpp lldb/tools/lldb-dap/DAP.h lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp lldb/tools/lldb-dap/lldb-dap.cpp Removed: lldb/tools/lldb-dap/IOStream.cpp lldb/tools/lldb-dap/IOStream.h ################################################################################ diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 9471594b66012..0fea3419d9725 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -337,7 +337,7 @@ def send_recv(self, command): self.send_packet( { "type": "response", - "seq": -1, + "seq": 0, "request_seq": response_or_request["seq"], "success": True, "command": "runInTerminal", @@ -349,7 +349,7 @@ def send_recv(self, command): self.send_packet( { "type": "response", - "seq": -1, + "seq": 0, "request_seq": response_or_request["seq"], "success": True, "command": "startDebugging", diff --git a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py index 04414cd7a3cdf..f05f876e57b49 100644 --- a/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py +++ b/lldb/test/API/tools/lldb-dap/io/TestDAP_io.py @@ -2,6 +2,8 @@ Test lldb-dap IO handling. """ +import sys + from lldbsuite.test.decorators import * import lldbdap_testcase import dap_server @@ -19,18 +21,18 @@ def cleanup(): if process.poll() is None: process.terminate() process.wait() - stdout_data = process.stdout.read() - stderr_data = process.stderr.read() - print("========= STDOUT =========") - print(stdout_data) - print("========= END =========") - print("========= STDERR =========") - print(stderr_data) - print("========= END =========") - print("========= DEBUG ADAPTER PROTOCOL LOGS =========") + stdout_data = process.stdout.read().decode() + stderr_data = process.stderr.read().decode() + print("========= STDOUT =========", file=sys.stderr) + print(stdout_data, file=sys.stderr) + print("========= END =========", file=sys.stderr) + print("========= STDERR =========", file=sys.stderr) + print(stderr_data, file=sys.stderr) + print("========= END =========", file=sys.stderr) + print("========= DEBUG ADAPTER PROTOCOL LOGS =========", file=sys.stderr) with open(log_file_path, "r") as file: - print(file.read()) - print("========= END =========") + print(file.read(), file=sys.stderr) + print("========= END =========", file=sys.stderr) # Execute the cleanup function during test case tear down. self.addTearDownHook(cleanup) @@ -45,6 +47,15 @@ def test_eof_immediately(self): process.stdin.close() self.assertEqual(process.wait(timeout=5.0), 0) + def test_invalid_header(self): + """ + lldb-dap handles invalid message headers. + """ + process = self.launch() + process.stdin.write(b"not the corret message header") + process.stdin.close() + self.assertEqual(process.wait(timeout=5.0), 1) + def test_partial_header(self): """ lldb-dap handles parital message headers. @@ -52,7 +63,7 @@ def test_partial_header(self): process = self.launch() process.stdin.write(b"Content-Length: ") process.stdin.close() - self.assertEqual(process.wait(timeout=5.0), 0) + self.assertEqual(process.wait(timeout=5.0), 1) def test_incorrect_content_length(self): """ @@ -61,13 +72,13 @@ def test_incorrect_content_length(self): process = self.launch() process.stdin.write(b"Content-Length: abc") process.stdin.close() - self.assertEqual(process.wait(timeout=5.0), 0) + self.assertEqual(process.wait(timeout=5.0), 1) def test_partial_content_length(self): """ lldb-dap handles partial messages. """ process = self.launch() - process.stdin.write(b"Content-Length: 10{") + process.stdin.write(b"Content-Length: 10\r\n\r\n{") process.stdin.close() - self.assertEqual(process.wait(timeout=5.0), 0) + self.assertEqual(process.wait(timeout=5.0), 1) diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 9a2d604f4d573..8a76cb58dbcab 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -28,14 +28,14 @@ add_lldb_tool(lldb-dap FifoFiles.cpp FunctionBreakpoint.cpp InstructionBreakpoint.cpp - IOStream.cpp JSONUtils.cpp LLDBUtils.cpp OutputRedirector.cpp ProgressEvent.cpp + Protocol.cpp RunInTerminal.cpp SourceBreakpoint.cpp - Protocol.cpp + Transport.cpp Watchpoint.cpp Handler/ResponseHandler.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index edd3b31be8ff7..4080e2c211035 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -12,6 +12,7 @@ #include "JSONUtils.h" #include "LLDBUtils.h" #include "OutputRedirector.h" +#include "Transport.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" @@ -63,11 +64,10 @@ const char DEV_NULL[] = "/dev/null"; namespace lldb_dap { -DAP::DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log, - lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode, - std::vector<std::string> pre_init_commands) - : client_name(client_name), debug_adapter_path(path), log(log), - input(std::move(input)), output(std::move(output)), +DAP::DAP(llvm::StringRef path, std::ofstream *log, + const ReplMode default_repl_mode, + std::vector<std::string> pre_init_commands, Transport &transport) + : debug_adapter_path(path), log(log), transport(transport), broadcaster("lldb-dap"), exception_breakpoints(), pre_init_commands(std::move(pre_init_commands)), focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false), @@ -78,7 +78,7 @@ DAP::DAP(llvm::StringRef client_name, 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(repl_mode) {} + reverse_request_seq(0), repl_mode(default_repl_mode) {} DAP::~DAP() = default; @@ -221,52 +221,21 @@ void DAP::StopEventHandlers() { } } -// Send the JSON in "json_str" to the "out" stream. Correctly send the -// "Content-Length:" field followed by the length, followed by the raw -// JSON bytes. -void DAP::SendJSON(const std::string &json_str) { - output.write_full("Content-Length: "); - output.write_full(llvm::utostr(json_str.size())); - output.write_full("\r\n\r\n"); - output.write_full(json_str); -} - // Serialize the JSON value into a string and send the JSON packet to // the "out" stream. void DAP::SendJSON(const llvm::json::Value &json) { - std::string json_str; - llvm::raw_string_ostream strm(json_str); - strm << json; - static std::mutex mutex; - std::lock_guard<std::mutex> locker(mutex); - SendJSON(json_str); - - DAP_LOG(log, "({0}) <-- {1}", client_name, json_str); -} - -// Read a JSON packet from the "in" stream. -std::string DAP::ReadJSON() { - std::string length_str; - std::string json_str; - int length; - - if (!input.read_expected(log, "Content-Length: ")) - return json_str; - - if (!input.read_line(log, length_str)) - return json_str; - - if (!llvm::to_integer(length_str, length)) - return json_str; - - if (!input.read_expected(log, "\r\n")) - return json_str; - - if (!input.read_full(log, length, json_str)) - return json_str; - - DAP_LOG(log, "({0}) --> {1}", client_name, json_str); - return json_str; + // FIXME: Instead of parsing the output message from JSON, pass the `Message` + // as parameter to `SendJSON`. + protocol::Message message; + llvm::json::Path::Root root; + if (!fromJSON(json, message, root)) { + DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}", + transport.GetClientName()); + return; + } + if (llvm::Error err = transport.Write(message)) + DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}", + transport.GetClientName()); } // "OutputEvent": { @@ -693,29 +662,10 @@ void DAP::SetTarget(const lldb::SBTarget target) { } } -PacketStatus DAP::GetNextObject(llvm::json::Object &object) { - std::string json = ReadJSON(); - if (json.empty()) - return PacketStatus::EndOfFile; - - llvm::StringRef json_sref(json); - llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref); - if (!json_value) { - DAP_LOG_ERROR(log, json_value.takeError(), - "({1}) failed to parse JSON: {0}", client_name); - return PacketStatus::JSONMalformed; - } - - llvm::json::Object *object_ptr = json_value->getAsObject(); - if (!object_ptr) { - DAP_LOG(log, "({0}) error: json packet isn't a object", client_name); - return PacketStatus::JSONNotObject; - } - object = *object_ptr; - return PacketStatus::Success; -} - -bool DAP::HandleObject(const llvm::json::Object &object) { +bool DAP::HandleObject(const protocol::Message &M) { + // FIXME: Directly handle `Message` instead of serializing to JSON. + llvm::json::Value v = toJSON(M); + llvm::json::Object object = *v.getAsObject(); const auto packet_type = GetString(object, "type"); if (packet_type == "request") { const auto command = GetString(object, "command"); @@ -726,7 +676,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) { return true; // Success } - DAP_LOG(log, "({0}) error: unhandled command '{1}'", client_name, command); + DAP_LOG(log, "({0}) error: unhandled command '{1}'", + transport.GetClientName(), command); return false; // Fail } @@ -749,9 +700,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) { // Result should be given, use null if not. if (GetBoolean(object, "success").value_or(false)) { llvm::json::Value Result = nullptr; - if (auto *B = object.get("body")) { + if (auto *B = object.get("body")) Result = std::move(*B); - } (*response_handler)(Result); } else { llvm::StringRef message = GetString(object, "message"); @@ -818,19 +768,15 @@ llvm::Error DAP::Loop() { StopEventHandlers(); }); while (!disconnecting) { - llvm::json::Object object; - lldb_dap::PacketStatus status = GetNextObject(object); + llvm::Expected<std::optional<protocol::Message>> next = transport.Read(); + if (!next) + return next.takeError(); - if (status == lldb_dap::PacketStatus::EndOfFile) { + // nullopt on EOF + if (!*next) break; - } - - if (status != lldb_dap::PacketStatus::Success) { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "failed to send packet"); - } - if (!HandleObject(object)) { + if (!HandleObject(**next)) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "unhandled packet"); } diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 3ff1992b61f5b..db3473b7c7027 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -14,11 +14,12 @@ #include "FunctionBreakpoint.h" #include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" -#include "IOStream.h" #include "InstructionBreakpoint.h" #include "OutputRedirector.h" #include "ProgressEvent.h" +#include "Protocol.h" #include "SourceBreakpoint.h" +#include "Transport.h" #include "lldb/API/SBBroadcaster.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBDebugger.h" @@ -39,7 +40,6 @@ #include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Threading.h" -#include <map> #include <memory> #include <mutex> #include <optional> @@ -145,11 +145,9 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface { }; struct DAP { - llvm::StringRef client_name; llvm::StringRef debug_adapter_path; std::ofstream *log; - InputStream input; - OutputStream output; + Transport &transport; lldb::SBFile in; OutputRedirector out; OutputRedirector err; @@ -210,12 +208,30 @@ struct DAP { // will contain that expression. std::string last_nonempty_var_expression; - DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log, - lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode, - std::vector<std::string> pre_init_commands); + /// Creates a new DAP sessions. + /// + /// \param[in] path + /// Path to the lldb-dap binary. + /// \param[in] log + /// Log file stream, if configured. + /// \param[in] default_repl_mode + /// Default repl mode behavior, as configured by the binary. + /// \param[in] pre_init_commands + /// LLDB commands to execute as soon as the debugger instance is allocaed. + /// \param[in] transport + /// Transport for this debug session. + DAP(llvm::StringRef path, std::ofstream *log, + const ReplMode default_repl_mode, + std::vector<std::string> pre_init_commands, Transport &transport); + ~DAP(); + + /// DAP is not copyable. + /// @{ DAP(const DAP &rhs) = delete; void operator=(const DAP &rhs) = delete; + /// @} + ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); @@ -233,8 +249,6 @@ struct DAP { // the "out" stream. void SendJSON(const llvm::json::Value &json); - std::string ReadJSON(); - void SendOutput(OutputType o, const llvm::StringRef output); void SendProgressEvent(uint64_t progress_id, const char *message, @@ -307,8 +321,7 @@ struct DAP { /// listeing for its breakpoint events. void SetTarget(const lldb::SBTarget target); - PacketStatus GetNextObject(llvm::json::Object &object); - bool HandleObject(const llvm::json::Object &object); + bool HandleObject(const protocol::Message &M); /// Disconnect the DAP session. lldb::SBError Disconnect(); @@ -382,12 +395,6 @@ struct DAP { InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id); InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread); - -private: - // Send the JSON in "json_str" to the "out" stream. Correctly send the - // "Content-Length:" field followed by the length, followed by the raw - // JSON bytes. - void SendJSON(const std::string &json_str); }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 7b7d8d5cedaa6..3262b70042a0e 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -111,7 +111,7 @@ void ProgressEventThreadFunction(DAP &dap) { // them prevent multiple threads from writing simultaneously so no locking // is required. static void EventThreadFunction(DAP &dap) { - llvm::set_thread_name(dap.client_name + ".event_handler"); + llvm::set_thread_name(dap.transport.GetClientName() + ".event_handler"); lldb::SBEvent event; lldb::SBListener listener = dap.debugger.GetListener(); dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread); diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp deleted file mode 100644 index ee22a297ec248..0000000000000 --- a/lldb/tools/lldb-dap/IOStream.cpp +++ /dev/null @@ -1,73 +0,0 @@ -//===-- IOStream.cpp --------------------------------------------*- C++ -*-===// -// -// 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 "IOStream.h" -#include "lldb/Utility/IOObject.h" -#include "lldb/Utility/Status.h" -#include <fstream> -#include <string> - -using namespace lldb_dap; - -bool OutputStream::write_full(llvm::StringRef str) { - if (!descriptor) - return false; - - size_t num_bytes = str.size(); - auto status = descriptor->Write(str.data(), num_bytes); - return status.Success(); -} - -bool InputStream::read_full(std::ofstream *log, size_t length, - std::string &text) { - if (!descriptor) - return false; - - std::string data; - data.resize(length); - - auto status = descriptor->Read(data.data(), length); - if (status.Fail()) - return false; - - text += data.substr(0, length); - return true; -} - -bool InputStream::read_line(std::ofstream *log, std::string &line) { - line.clear(); - while (true) { - std::string next; - if (!read_full(log, 1, next)) - return false; - - // If EOF is encoutnered, '' is returned, break out of this loop. - if (next.empty()) - return false; - - line += next; - - if (llvm::StringRef(line).ends_with("\r\n")) - break; - } - line.erase(line.size() - 2); - return true; -} - -bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) { - std::string result; - if (!read_full(log, expected.size(), result)) - return false; - if (expected != result) { - if (log) - *log << "Warning: Expected '" << expected.str() << "', got '" << result - << "\n"; - return false; - } - return true; -} diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h deleted file mode 100644 index e9fb8e11c92da..0000000000000 --- a/lldb/tools/lldb-dap/IOStream.h +++ /dev/null @@ -1,42 +0,0 @@ -//===-- IOStream.h ----------------------------------------------*- C++ -*-===// -// -// 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_IOSTREAM_H -#define LLDB_TOOLS_LLDB_DAP_IOSTREAM_H - -#include "lldb/lldb-forward.h" -#include "llvm/ADT/StringRef.h" -#include <fstream> -#include <string> - -namespace lldb_dap { - -struct InputStream { - lldb::IOObjectSP descriptor; - - explicit InputStream(lldb::IOObjectSP descriptor) - : descriptor(std::move(descriptor)) {} - - bool read_full(std::ofstream *log, size_t length, std::string &text); - - bool read_line(std::ofstream *log, std::string &line); - - bool read_expected(std::ofstream *log, llvm::StringRef expected); -}; - -struct OutputStream { - lldb::IOObjectSP descriptor; - - explicit OutputStream(lldb::IOObjectSP descriptor) - : descriptor(std::move(descriptor)) {} - - bool write_full(llvm::StringRef str); -}; -} // namespace lldb_dap - -#endif diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp new file mode 100644 index 0000000000000..db2d7228d3fb7 --- /dev/null +++ b/lldb/tools/lldb-dap/Transport.cpp @@ -0,0 +1,126 @@ +//===-- Transport.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 "Transport.h" +#include "DAPLog.h" +#include "Protocol.h" +#include "lldb/Utility/IOObject.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include <string> +#include <utility> + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; +using namespace lldb_dap; +using namespace lldb_dap::protocol; + +/// ReadFull attempts to read the specified number of bytes. If EOF is +/// encountered, an empty string is returned. +static Expected<std::string> ReadFull(IOObject &descriptor, size_t length) { + std::string data; + data.resize(length); + auto status = descriptor.Read(data.data(), length); + if (status.Fail()) + return status.takeError(); + // Return the actual number of bytes read. + return data.substr(0, length); +} + +static Expected<std::string> ReadUntil(IOObject &descriptor, + StringRef delimiter) { + std::string buffer; + buffer.reserve(delimiter.size() + 1); + while (!llvm::StringRef(buffer).ends_with(delimiter)) { + Expected<std::string> next = + ReadFull(descriptor, buffer.empty() ? delimiter.size() : 1); + if (auto Err = next.takeError()) + return std::move(Err); + // Return "" if EOF is encountered. + if (next->empty()) + return ""; + buffer += *next; + } + return buffer.substr(0, buffer.size() - delimiter.size()); +} + +/// DAP message format +/// ``` +/// Content-Length: (?<length>\d+)\r\n\r\n(?<content>.{\k<length>}) +/// ``` +static constexpr StringLiteral kHeaderContentLength = "Content-Length: "; +static constexpr StringLiteral kHeaderSeparator = "\r\n\r\n"; + +namespace lldb_dap { + +Transport::Transport(StringRef client_name, std::ofstream *log, + IOObjectSP input, IOObjectSP output) + : m_client_name(client_name), m_log(log), m_input(std::move(input)), + m_output(std::move(output)) {} + +Expected<std::optional<Message>> Transport::Read() { + if (!m_input || !m_input->IsValid()) + return createStringError("transport output is closed"); + + IOObject *input = m_input.get(); + Expected<std::string> message_header = + ReadFull(*input, kHeaderContentLength.size()); + if (!message_header) + return message_header.takeError(); + // '' returned on EOF. + if (message_header->empty()) + return std::nullopt; + if (*message_header != kHeaderContentLength) + return createStringError(formatv("expected '{0}' and got '{1}'", + kHeaderContentLength, *message_header) + .str()); + + Expected<std::string> raw_length = ReadUntil(*input, kHeaderSeparator); + if (!raw_length) + return raw_length.takeError(); + if (raw_length->empty()) + return createStringError("unexpected EOF parsing DAP header"); + + size_t length; + if (!to_integer(*raw_length, length)) + return createStringError( + formatv("invalid content length {0}", *raw_length).str()); + + Expected<std::string> raw_json = ReadFull(*input, length); + if (!raw_json) + return raw_json.takeError(); + // If we got less than the expected number of bytes then we hit EOF. + if (raw_json->length() != length) + return createStringError("unexpected EOF parse DAP message body"); + + DAP_LOG(m_log, "<-- ({0}) {1}", m_client_name, *raw_json); + + return json::parse<Message>(*raw_json); +} + +Error Transport::Write(const Message &message) { + if (!m_output || !m_output->IsValid()) + return createStringError("transport output is closed"); + + std::string json = formatv("{0}", toJSON(message)).str(); + + DAP_LOG(m_log, "--> ({0}) {1}", m_client_name, json); + + std::string Output; + raw_string_ostream OS(Output); + OS << kHeaderContentLength << json.length() << kHeaderSeparator << json; + size_t num_bytes = Output.size(); + return m_output->Write(Output.data(), num_bytes).takeError(); +} + +} // end namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h new file mode 100644 index 0000000000000..013a6c98af1ce --- /dev/null +++ b/lldb/tools/lldb-dap/Transport.h @@ -0,0 +1,61 @@ +//===-- Transport.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 +// +//===----------------------------------------------------------------------===// +// +// Debug Adapter Protocol transport layer for encoding and decoding protocol +// messages. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_TRANSPORT_H +#define LLDB_TOOLS_LLDB_DAP_TRANSPORT_H + +#include "Protocol.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <fstream> +#include <optional> + +namespace lldb_dap { + +/// A transport class that performs the Debug Adapter Protocol communication +/// with the client. +class Transport { +public: + Transport(llvm::StringRef client_name, std::ofstream *log, + lldb::IOObjectSP input, lldb::IOObjectSP output); + ~Transport() = default; + + /// Transport is not copyable. + /// @{ + Transport(const Transport &rhs) = delete; + void operator=(const Transport &rhs) = delete; + /// @} + + /// Writes a Debug Adater Protocol message to the output stream. + llvm::Error Write(const protocol::Message &M); + + /// Reads the next Debug Adater Protocol message from the input stream. + /// + /// \returns Returns the next protocol message or nullopt if EOF is reached. + llvm::Expected<std::optional<protocol::Message>> Read(); + + /// Returns the name of this transport client, for example `stdin/stdout` or + /// `client_1`. + llvm::StringRef GetClientName() { return m_client_name; } + +private: + llvm::StringRef m_client_name; + std::ofstream *m_log; + lldb::IOObjectSP m_input; + lldb::IOObjectSP m_output; +}; + +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index ab40b75e5425d..ca8b548632ff7 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -11,6 +11,7 @@ #include "EventHelper.h" #include "Handler/RequestHandler.h" #include "RunInTerminal.h" +#include "Transport.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBStream.h" #include "lldb/Host/Config.h" @@ -325,8 +326,9 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex, &dap_sessions]() { llvm::set_thread_name(client_name + ".runloop"); - DAP dap = DAP(client_name, program_path, log, io, io, default_repl_mode, - pre_init_commands); + Transport transport(client_name, log, io, io); + DAP dap(program_path, log, default_repl_mode, pre_init_commands, + transport); if (auto Err = dap.ConfigureIO()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -343,7 +345,8 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, if (auto Err = dap.Loop()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), - "DAP session error: "); + "DAP session (" + client_name + + ") error: "); } DAP_LOG(log, "({0}) client disconnected", client_name); @@ -374,7 +377,7 @@ serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, auto error = dap->Disconnect(); if (error.Fail()) { client_failed = true; - llvm::errs() << "DAP client " << dap->client_name + llvm::errs() << "DAP client " << dap->transport.GetClientName() << " disconnected failed: " << error.GetCString() << "\n"; } // Close the socket to ensure the DAP::Loop read finishes. @@ -498,9 +501,8 @@ int main(int argc, char *argv[]) { // Create a memory monitor. This can return nullptr if the host platform is // not supported. std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor = - lldb_private::MemoryMonitor::Create([&]() { - if (log) - *log << "memory pressure detected\n"; + lldb_private::MemoryMonitor::Create([log = log.get()]() { + DAP_LOG(log, "memory pressure detected"); lldb::SBDebugger::MemoryPressureDetected(); }); @@ -565,8 +567,10 @@ int main(int argc, char *argv[]) { lldb::IOObjectSP output = std::make_shared<NativeFile>( stdout_fd, File::eOpenOptionWriteOnly, false); - DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input), - std::move(output), default_repl_mode, pre_init_commands); + constexpr llvm::StringLiteral client_name = "stdin/stdout"; + Transport transport(client_name, log.get(), input, output); + DAP dap(program_path, log.get(), default_repl_mode, pre_init_commands, + transport); // stdout/stderr redirection to the IDE's console if (auto Err = dap.ConfigureIO(stdout, stderr)) { @@ -583,7 +587,7 @@ int main(int argc, char *argv[]) { if (auto Err = dap.Loop()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), - "DAP session error: "); + "DAP session (" + client_name + ") error: "); return EXIT_FAILURE; } return EXIT_SUCCESS; _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits