[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 <jonas@devlieghere.com>
This commit is contained in:
parent
4d79e9892c
commit
7790d69cce
@ -337,7 +337,7 @@ class DebugCommunication(object):
|
|||||||
self.send_packet(
|
self.send_packet(
|
||||||
{
|
{
|
||||||
"type": "response",
|
"type": "response",
|
||||||
"seq": -1,
|
"seq": 0,
|
||||||
"request_seq": response_or_request["seq"],
|
"request_seq": response_or_request["seq"],
|
||||||
"success": True,
|
"success": True,
|
||||||
"command": "runInTerminal",
|
"command": "runInTerminal",
|
||||||
@ -349,7 +349,7 @@ class DebugCommunication(object):
|
|||||||
self.send_packet(
|
self.send_packet(
|
||||||
{
|
{
|
||||||
"type": "response",
|
"type": "response",
|
||||||
"seq": -1,
|
"seq": 0,
|
||||||
"request_seq": response_or_request["seq"],
|
"request_seq": response_or_request["seq"],
|
||||||
"success": True,
|
"success": True,
|
||||||
"command": "startDebugging",
|
"command": "startDebugging",
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
Test lldb-dap IO handling.
|
Test lldb-dap IO handling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from lldbsuite.test.decorators import *
|
from lldbsuite.test.decorators import *
|
||||||
import lldbdap_testcase
|
import lldbdap_testcase
|
||||||
import dap_server
|
import dap_server
|
||||||
@ -19,18 +21,18 @@ class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
|
|||||||
if process.poll() is None:
|
if process.poll() is None:
|
||||||
process.terminate()
|
process.terminate()
|
||||||
process.wait()
|
process.wait()
|
||||||
stdout_data = process.stdout.read()
|
stdout_data = process.stdout.read().decode()
|
||||||
stderr_data = process.stderr.read()
|
stderr_data = process.stderr.read().decode()
|
||||||
print("========= STDOUT =========")
|
print("========= STDOUT =========", file=sys.stderr)
|
||||||
print(stdout_data)
|
print(stdout_data, file=sys.stderr)
|
||||||
print("========= END =========")
|
print("========= END =========", file=sys.stderr)
|
||||||
print("========= STDERR =========")
|
print("========= STDERR =========", file=sys.stderr)
|
||||||
print(stderr_data)
|
print(stderr_data, file=sys.stderr)
|
||||||
print("========= END =========")
|
print("========= END =========", file=sys.stderr)
|
||||||
print("========= DEBUG ADAPTER PROTOCOL LOGS =========")
|
print("========= DEBUG ADAPTER PROTOCOL LOGS =========", file=sys.stderr)
|
||||||
with open(log_file_path, "r") as file:
|
with open(log_file_path, "r") as file:
|
||||||
print(file.read())
|
print(file.read(), file=sys.stderr)
|
||||||
print("========= END =========")
|
print("========= END =========", file=sys.stderr)
|
||||||
|
|
||||||
# Execute the cleanup function during test case tear down.
|
# Execute the cleanup function during test case tear down.
|
||||||
self.addTearDownHook(cleanup)
|
self.addTearDownHook(cleanup)
|
||||||
@ -45,6 +47,15 @@ class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
|
|||||||
process.stdin.close()
|
process.stdin.close()
|
||||||
self.assertEqual(process.wait(timeout=5.0), 0)
|
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):
|
def test_partial_header(self):
|
||||||
"""
|
"""
|
||||||
lldb-dap handles parital message headers.
|
lldb-dap handles parital message headers.
|
||||||
@ -52,7 +63,7 @@ class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
|
|||||||
process = self.launch()
|
process = self.launch()
|
||||||
process.stdin.write(b"Content-Length: ")
|
process.stdin.write(b"Content-Length: ")
|
||||||
process.stdin.close()
|
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):
|
def test_incorrect_content_length(self):
|
||||||
"""
|
"""
|
||||||
@ -61,13 +72,13 @@ class TestDAP_io(lldbdap_testcase.DAPTestCaseBase):
|
|||||||
process = self.launch()
|
process = self.launch()
|
||||||
process.stdin.write(b"Content-Length: abc")
|
process.stdin.write(b"Content-Length: abc")
|
||||||
process.stdin.close()
|
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):
|
def test_partial_content_length(self):
|
||||||
"""
|
"""
|
||||||
lldb-dap handles partial messages.
|
lldb-dap handles partial messages.
|
||||||
"""
|
"""
|
||||||
process = self.launch()
|
process = self.launch()
|
||||||
process.stdin.write(b"Content-Length: 10{")
|
process.stdin.write(b"Content-Length: 10\r\n\r\n{")
|
||||||
process.stdin.close()
|
process.stdin.close()
|
||||||
self.assertEqual(process.wait(timeout=5.0), 0)
|
self.assertEqual(process.wait(timeout=5.0), 1)
|
||||||
|
@ -28,14 +28,14 @@ add_lldb_tool(lldb-dap
|
|||||||
FifoFiles.cpp
|
FifoFiles.cpp
|
||||||
FunctionBreakpoint.cpp
|
FunctionBreakpoint.cpp
|
||||||
InstructionBreakpoint.cpp
|
InstructionBreakpoint.cpp
|
||||||
IOStream.cpp
|
|
||||||
JSONUtils.cpp
|
JSONUtils.cpp
|
||||||
LLDBUtils.cpp
|
LLDBUtils.cpp
|
||||||
OutputRedirector.cpp
|
OutputRedirector.cpp
|
||||||
ProgressEvent.cpp
|
ProgressEvent.cpp
|
||||||
|
Protocol.cpp
|
||||||
RunInTerminal.cpp
|
RunInTerminal.cpp
|
||||||
SourceBreakpoint.cpp
|
SourceBreakpoint.cpp
|
||||||
Protocol.cpp
|
Transport.cpp
|
||||||
Watchpoint.cpp
|
Watchpoint.cpp
|
||||||
|
|
||||||
Handler/ResponseHandler.cpp
|
Handler/ResponseHandler.cpp
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "JSONUtils.h"
|
#include "JSONUtils.h"
|
||||||
#include "LLDBUtils.h"
|
#include "LLDBUtils.h"
|
||||||
#include "OutputRedirector.h"
|
#include "OutputRedirector.h"
|
||||||
|
#include "Transport.h"
|
||||||
#include "lldb/API/SBBreakpoint.h"
|
#include "lldb/API/SBBreakpoint.h"
|
||||||
#include "lldb/API/SBCommandInterpreter.h"
|
#include "lldb/API/SBCommandInterpreter.h"
|
||||||
#include "lldb/API/SBCommandReturnObject.h"
|
#include "lldb/API/SBCommandReturnObject.h"
|
||||||
@ -63,11 +64,10 @@ const char DEV_NULL[] = "/dev/null";
|
|||||||
|
|
||||||
namespace lldb_dap {
|
namespace lldb_dap {
|
||||||
|
|
||||||
DAP::DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log,
|
DAP::DAP(llvm::StringRef path, std::ofstream *log,
|
||||||
lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
|
const ReplMode default_repl_mode,
|
||||||
std::vector<std::string> pre_init_commands)
|
std::vector<std::string> pre_init_commands, Transport &transport)
|
||||||
: client_name(client_name), debug_adapter_path(path), log(log),
|
: debug_adapter_path(path), log(log), transport(transport),
|
||||||
input(std::move(input)), output(std::move(output)),
|
|
||||||
broadcaster("lldb-dap"), exception_breakpoints(),
|
broadcaster("lldb-dap"), exception_breakpoints(),
|
||||||
pre_init_commands(std::move(pre_init_commands)),
|
pre_init_commands(std::move(pre_init_commands)),
|
||||||
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
|
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),
|
configuration_done_sent(false), waiting_for_run_in_terminal(false),
|
||||||
progress_event_reporter(
|
progress_event_reporter(
|
||||||
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
|
[&](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;
|
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
|
// Serialize the JSON value into a string and send the JSON packet to
|
||||||
// the "out" stream.
|
// the "out" stream.
|
||||||
void DAP::SendJSON(const llvm::json::Value &json) {
|
void DAP::SendJSON(const llvm::json::Value &json) {
|
||||||
std::string json_str;
|
// FIXME: Instead of parsing the output message from JSON, pass the `Message`
|
||||||
llvm::raw_string_ostream strm(json_str);
|
// as parameter to `SendJSON`.
|
||||||
strm << json;
|
protocol::Message message;
|
||||||
static std::mutex mutex;
|
llvm::json::Path::Root root;
|
||||||
std::lock_guard<std::mutex> locker(mutex);
|
if (!fromJSON(json, message, root)) {
|
||||||
SendJSON(json_str);
|
DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}",
|
||||||
|
transport.GetClientName());
|
||||||
DAP_LOG(log, "({0}) <-- {1}", client_name, json_str);
|
return;
|
||||||
}
|
}
|
||||||
|
if (llvm::Error err = transport.Write(message))
|
||||||
// Read a JSON packet from the "in" stream.
|
DAP_LOG_ERROR(log, std::move(err), "({1}) write failed: {0}",
|
||||||
std::string DAP::ReadJSON() {
|
transport.GetClientName());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "OutputEvent": {
|
// "OutputEvent": {
|
||||||
@ -693,29 +662,10 @@ void DAP::SetTarget(const lldb::SBTarget target) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
|
bool DAP::HandleObject(const protocol::Message &M) {
|
||||||
std::string json = ReadJSON();
|
// FIXME: Directly handle `Message` instead of serializing to JSON.
|
||||||
if (json.empty())
|
llvm::json::Value v = toJSON(M);
|
||||||
return PacketStatus::EndOfFile;
|
llvm::json::Object object = *v.getAsObject();
|
||||||
|
|
||||||
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) {
|
|
||||||
const auto packet_type = GetString(object, "type");
|
const auto packet_type = GetString(object, "type");
|
||||||
if (packet_type == "request") {
|
if (packet_type == "request") {
|
||||||
const auto command = GetString(object, "command");
|
const auto command = GetString(object, "command");
|
||||||
@ -726,7 +676,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
|
|||||||
return true; // Success
|
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
|
return false; // Fail
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,9 +700,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
|
|||||||
// Result should be given, use null if not.
|
// Result should be given, use null if not.
|
||||||
if (GetBoolean(object, "success").value_or(false)) {
|
if (GetBoolean(object, "success").value_or(false)) {
|
||||||
llvm::json::Value Result = nullptr;
|
llvm::json::Value Result = nullptr;
|
||||||
if (auto *B = object.get("body")) {
|
if (auto *B = object.get("body"))
|
||||||
Result = std::move(*B);
|
Result = std::move(*B);
|
||||||
}
|
|
||||||
(*response_handler)(Result);
|
(*response_handler)(Result);
|
||||||
} else {
|
} else {
|
||||||
llvm::StringRef message = GetString(object, "message");
|
llvm::StringRef message = GetString(object, "message");
|
||||||
@ -818,19 +768,15 @@ llvm::Error DAP::Loop() {
|
|||||||
StopEventHandlers();
|
StopEventHandlers();
|
||||||
});
|
});
|
||||||
while (!disconnecting) {
|
while (!disconnecting) {
|
||||||
llvm::json::Object object;
|
llvm::Expected<std::optional<protocol::Message>> next = transport.Read();
|
||||||
lldb_dap::PacketStatus status = GetNextObject(object);
|
if (!next)
|
||||||
|
return next.takeError();
|
||||||
|
|
||||||
if (status == lldb_dap::PacketStatus::EndOfFile) {
|
// nullopt on EOF
|
||||||
|
if (!*next)
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (status != lldb_dap::PacketStatus::Success) {
|
if (!HandleObject(**next)) {
|
||||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
||||||
"failed to send packet");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HandleObject(object)) {
|
|
||||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||||
"unhandled packet");
|
"unhandled packet");
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,12 @@
|
|||||||
#include "FunctionBreakpoint.h"
|
#include "FunctionBreakpoint.h"
|
||||||
#include "Handler/RequestHandler.h"
|
#include "Handler/RequestHandler.h"
|
||||||
#include "Handler/ResponseHandler.h"
|
#include "Handler/ResponseHandler.h"
|
||||||
#include "IOStream.h"
|
|
||||||
#include "InstructionBreakpoint.h"
|
#include "InstructionBreakpoint.h"
|
||||||
#include "OutputRedirector.h"
|
#include "OutputRedirector.h"
|
||||||
#include "ProgressEvent.h"
|
#include "ProgressEvent.h"
|
||||||
|
#include "Protocol.h"
|
||||||
#include "SourceBreakpoint.h"
|
#include "SourceBreakpoint.h"
|
||||||
|
#include "Transport.h"
|
||||||
#include "lldb/API/SBBroadcaster.h"
|
#include "lldb/API/SBBroadcaster.h"
|
||||||
#include "lldb/API/SBCommandInterpreter.h"
|
#include "lldb/API/SBCommandInterpreter.h"
|
||||||
#include "lldb/API/SBDebugger.h"
|
#include "lldb/API/SBDebugger.h"
|
||||||
@ -39,7 +40,6 @@
|
|||||||
#include "llvm/Support/Error.h"
|
#include "llvm/Support/Error.h"
|
||||||
#include "llvm/Support/JSON.h"
|
#include "llvm/Support/JSON.h"
|
||||||
#include "llvm/Support/Threading.h"
|
#include "llvm/Support/Threading.h"
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@ -145,11 +145,9 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DAP {
|
struct DAP {
|
||||||
llvm::StringRef client_name;
|
|
||||||
llvm::StringRef debug_adapter_path;
|
llvm::StringRef debug_adapter_path;
|
||||||
std::ofstream *log;
|
std::ofstream *log;
|
||||||
InputStream input;
|
Transport &transport;
|
||||||
OutputStream output;
|
|
||||||
lldb::SBFile in;
|
lldb::SBFile in;
|
||||||
OutputRedirector out;
|
OutputRedirector out;
|
||||||
OutputRedirector err;
|
OutputRedirector err;
|
||||||
@ -210,12 +208,30 @@ struct DAP {
|
|||||||
// will contain that expression.
|
// will contain that expression.
|
||||||
std::string last_nonempty_var_expression;
|
std::string last_nonempty_var_expression;
|
||||||
|
|
||||||
DAP(llvm::StringRef client_name, llvm::StringRef path, std::ofstream *log,
|
/// Creates a new DAP sessions.
|
||||||
lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
|
///
|
||||||
std::vector<std::string> pre_init_commands);
|
/// \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();
|
||||||
|
|
||||||
|
/// DAP is not copyable.
|
||||||
|
/// @{
|
||||||
DAP(const DAP &rhs) = delete;
|
DAP(const DAP &rhs) = delete;
|
||||||
void operator=(const DAP &rhs) = delete;
|
void operator=(const DAP &rhs) = delete;
|
||||||
|
/// @}
|
||||||
|
|
||||||
ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
|
ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
|
||||||
ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
|
ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
|
||||||
|
|
||||||
@ -233,8 +249,6 @@ struct DAP {
|
|||||||
// the "out" stream.
|
// the "out" stream.
|
||||||
void SendJSON(const llvm::json::Value &json);
|
void SendJSON(const llvm::json::Value &json);
|
||||||
|
|
||||||
std::string ReadJSON();
|
|
||||||
|
|
||||||
void SendOutput(OutputType o, const llvm::StringRef output);
|
void SendOutput(OutputType o, const llvm::StringRef output);
|
||||||
|
|
||||||
void SendProgressEvent(uint64_t progress_id, const char *message,
|
void SendProgressEvent(uint64_t progress_id, const char *message,
|
||||||
@ -307,8 +321,7 @@ struct DAP {
|
|||||||
/// listeing for its breakpoint events.
|
/// listeing for its breakpoint events.
|
||||||
void SetTarget(const lldb::SBTarget target);
|
void SetTarget(const lldb::SBTarget target);
|
||||||
|
|
||||||
PacketStatus GetNextObject(llvm::json::Object &object);
|
bool HandleObject(const protocol::Message &M);
|
||||||
bool HandleObject(const llvm::json::Object &object);
|
|
||||||
|
|
||||||
/// Disconnect the DAP session.
|
/// Disconnect the DAP session.
|
||||||
lldb::SBError Disconnect();
|
lldb::SBError Disconnect();
|
||||||
@ -382,12 +395,6 @@ struct DAP {
|
|||||||
InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id);
|
InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id);
|
||||||
|
|
||||||
InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);
|
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
|
} // namespace lldb_dap
|
||||||
|
@ -111,7 +111,7 @@ void ProgressEventThreadFunction(DAP &dap) {
|
|||||||
// them prevent multiple threads from writing simultaneously so no locking
|
// them prevent multiple threads from writing simultaneously so no locking
|
||||||
// is required.
|
// is required.
|
||||||
static void EventThreadFunction(DAP &dap) {
|
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::SBEvent event;
|
||||||
lldb::SBListener listener = dap.debugger.GetListener();
|
lldb::SBListener listener = dap.debugger.GetListener();
|
||||||
dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
|
dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
126
lldb/tools/lldb-dap/Transport.cpp
Normal file
126
lldb/tools/lldb-dap/Transport.cpp
Normal file
@ -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
|
61
lldb/tools/lldb-dap/Transport.h
Normal file
61
lldb/tools/lldb-dap/Transport.h
Normal file
@ -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
|
@ -11,6 +11,7 @@
|
|||||||
#include "EventHelper.h"
|
#include "EventHelper.h"
|
||||||
#include "Handler/RequestHandler.h"
|
#include "Handler/RequestHandler.h"
|
||||||
#include "RunInTerminal.h"
|
#include "RunInTerminal.h"
|
||||||
|
#include "Transport.h"
|
||||||
#include "lldb/API/SBDebugger.h"
|
#include "lldb/API/SBDebugger.h"
|
||||||
#include "lldb/API/SBStream.h"
|
#include "lldb/API/SBStream.h"
|
||||||
#include "lldb/Host/Config.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,
|
std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex,
|
||||||
&dap_sessions]() {
|
&dap_sessions]() {
|
||||||
llvm::set_thread_name(client_name + ".runloop");
|
llvm::set_thread_name(client_name + ".runloop");
|
||||||
DAP dap = DAP(client_name, program_path, log, io, io, default_repl_mode,
|
Transport transport(client_name, log, io, io);
|
||||||
pre_init_commands);
|
DAP dap(program_path, log, default_repl_mode, pre_init_commands,
|
||||||
|
transport);
|
||||||
|
|
||||||
if (auto Err = dap.ConfigureIO()) {
|
if (auto Err = dap.ConfigureIO()) {
|
||||||
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
|
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()) {
|
if (auto Err = dap.Loop()) {
|
||||||
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
|
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
|
||||||
"DAP session error: ");
|
"DAP session (" + client_name +
|
||||||
|
") error: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
DAP_LOG(log, "({0}) client disconnected", client_name);
|
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();
|
auto error = dap->Disconnect();
|
||||||
if (error.Fail()) {
|
if (error.Fail()) {
|
||||||
client_failed = true;
|
client_failed = true;
|
||||||
llvm::errs() << "DAP client " << dap->client_name
|
llvm::errs() << "DAP client " << dap->transport.GetClientName()
|
||||||
<< " disconnected failed: " << error.GetCString() << "\n";
|
<< " disconnected failed: " << error.GetCString() << "\n";
|
||||||
}
|
}
|
||||||
// Close the socket to ensure the DAP::Loop read finishes.
|
// 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
|
// Create a memory monitor. This can return nullptr if the host platform is
|
||||||
// not supported.
|
// not supported.
|
||||||
std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor =
|
std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor =
|
||||||
lldb_private::MemoryMonitor::Create([&]() {
|
lldb_private::MemoryMonitor::Create([log = log.get()]() {
|
||||||
if (log)
|
DAP_LOG(log, "memory pressure detected");
|
||||||
*log << "memory pressure detected\n";
|
|
||||||
lldb::SBDebugger::MemoryPressureDetected();
|
lldb::SBDebugger::MemoryPressureDetected();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -565,8 +567,10 @@ int main(int argc, char *argv[]) {
|
|||||||
lldb::IOObjectSP output = std::make_shared<NativeFile>(
|
lldb::IOObjectSP output = std::make_shared<NativeFile>(
|
||||||
stdout_fd, File::eOpenOptionWriteOnly, false);
|
stdout_fd, File::eOpenOptionWriteOnly, false);
|
||||||
|
|
||||||
DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
|
constexpr llvm::StringLiteral client_name = "stdin/stdout";
|
||||||
std::move(output), default_repl_mode, pre_init_commands);
|
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
|
// stdout/stderr redirection to the IDE's console
|
||||||
if (auto Err = dap.ConfigureIO(stdout, stderr)) {
|
if (auto Err = dap.ConfigureIO(stdout, stderr)) {
|
||||||
@ -583,7 +587,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
if (auto Err = dap.Loop()) {
|
if (auto Err = dap.Loop()) {
|
||||||
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
|
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
|
||||||
"DAP session error: ");
|
"DAP session (" + client_name + ") error: ");
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user