[Lldb-commits] [lldb] [lldb-dap] Refactoring IO handling for the SBDebugger. (PR #167067)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Wed Dec 3 16:34:11 PST 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/167067
>From 04f0a42ef1279ac31cbf954cf5188818f781aeb7 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 20 Nov 2025 10:15:12 -0800
Subject: [PATCH] [lldb-dap] Refactoring IO handling for the SBDebugger.
This refactors the IO handling of lldb-dap and its SBDebugger. This adjusts the SBDebugger instance to have a pty associated with in/out/err. Using a pty allows us to handle commands with raw input modes, such as `script` or `breakpoint command add`.
Additionally, to better handle output produced by the debugger and evaluate command I added a print helper. This new print helper will inspect the SBCommandReturnObject and store any associated variables in the variable store. This lets users more easily inspect variables directly in the Debug Console in VSCode.
---
lldb/include/lldb/Utility/LLDBLog.h | 1 +
.../test/tools/lldb-dap/dap_server.py | 14 +-
lldb/source/Core/Debugger.cpp | 5 +
.../SystemInitializerCommon.cpp | 1 +
lldb/source/Utility/LLDBLog.cpp | 2 +
.../tools/lldb-dap/cancel/TestDAP_cancel.py | 2 -
.../lldb-dap/evaluate/TestDAP_evaluate.py | 1 +
lldb/tools/lldb-dap/DAP.cpp | 521 ++++++++++--------
lldb/tools/lldb-dap/DAP.h | 74 ++-
lldb/tools/lldb-dap/DAPError.cpp | 14 +-
lldb/tools/lldb-dap/DAPError.h | 22 +-
lldb/tools/lldb-dap/DAPForward.h | 5 +
lldb/tools/lldb-dap/DAPLog.cpp | 20 +-
lldb/tools/lldb-dap/DAPLog.h | 46 +-
lldb/tools/lldb-dap/DAPSessionManager.cpp | 66 +--
lldb/tools/lldb-dap/DAPSessionManager.h | 50 +-
lldb/tools/lldb-dap/EventHelper.cpp | 53 +-
lldb/tools/lldb-dap/EventHelper.h | 11 +-
.../lldb-dap/Handler/AttachRequestHandler.cpp | 35 +-
.../lldb-dap/Handler/CompletionsHandler.cpp | 3 +-
.../ConfigurationDoneRequestHandler.cpp | 15 +-
.../Handler/EvaluateRequestHandler.cpp | 69 ++-
.../Handler/InitializeRequestHandler.cpp | 8 +-
.../lldb-dap/Handler/LaunchRequestHandler.cpp | 2 +
.../tools/lldb-dap/Handler/RequestHandler.cpp | 25 +-
lldb/tools/lldb-dap/Handler/RequestHandler.h | 31 +-
.../Handler/SetVariableRequestHandler.cpp | 3 +-
.../Handler/VariablesRequestHandler.cpp | 63 ++-
lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp | 16 +-
lldb/tools/lldb-dap/Protocol/ProtocolBase.h | 6 +-
.../lldb-dap/Protocol/ProtocolEvents.cpp | 51 ++
lldb/tools/lldb-dap/Protocol/ProtocolEvents.h | 93 ++++
.../lldb-dap/Protocol/ProtocolRequests.cpp | 26 +-
.../lldb-dap/Protocol/ProtocolRequests.h | 17 +-
lldb/tools/lldb-dap/SourceBreakpoint.cpp | 5 +-
lldb/tools/lldb-dap/Transport.cpp | 11 +-
lldb/tools/lldb-dap/Transport.h | 7 +-
lldb/tools/lldb-dap/Variables.cpp | 48 +-
lldb/tools/lldb-dap/Variables.h | 7 +-
lldb/tools/lldb-dap/tool/Options.td | 6 -
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 84 ++-
lldb/unittests/DAP/CMakeLists.txt | 1 +
lldb/unittests/DAP/DAPErrorTest.cpp | 8 +-
lldb/unittests/DAP/DAPSessionManagerTest.cpp | 72 ++-
lldb/unittests/DAP/Handler/DisconnectTest.cpp | 10 +-
lldb/unittests/DAP/TestBase.cpp | 86 +--
lldb/unittests/DAP/TestBase.h | 17 +-
lldb/unittests/DAP/TestFixtures.cpp | 85 +++
lldb/unittests/DAP/TestFixtures.h | 51 ++
lldb/unittests/DAP/VariablesTest.cpp | 57 +-
50 files changed, 1182 insertions(+), 744 deletions(-)
create mode 100644 lldb/unittests/DAP/TestFixtures.cpp
create mode 100644 lldb/unittests/DAP/TestFixtures.h
diff --git a/lldb/include/lldb/Utility/LLDBLog.h b/lldb/include/lldb/Utility/LLDBLog.h
index ac360bfdf8cee..279f0531a258e 100644
--- a/lldb/include/lldb/Utility/LLDBLog.h
+++ b/lldb/include/lldb/Utility/LLDBLog.h
@@ -57,6 +57,7 @@ enum class LLDBLog : Log::MaskType {
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
void InitializeLldbChannel();
+void TerminateLldbChannel();
template <> Log::Channel &LogChannelFor<LLDBLog>();
} // namespace lldb_private
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 4a7ba78b63993..8f291f96daaa9 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
@@ -995,15 +995,15 @@ def request_writeMemory(self, memoryReference, data, offset=0, allowPartial=Fals
def request_evaluate(
self,
- expression,
+ expression: str,
frameIndex=0,
- threadId=None,
- context=None,
+ threadId: Optional[int] = None,
+ context: Optional[
+ Literal["watch", "repl", "hover", "clipboard", "variables"]
+ ] = None,
is_hex: Optional[bool] = None,
- ):
+ ) -> Optional[Response]:
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
- if stackFrame is None:
- return []
args_dict = {
"expression": expression,
"frameId": stackFrame["id"],
@@ -1012,7 +1012,7 @@ def request_evaluate(
args_dict["context"] = context
if is_hex is not None:
args_dict["format"] = {"hex": is_hex}
- command_dict = {
+ command_dict: Request = {
"command": "evaluate",
"type": "request",
"arguments": args_dict,
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 02f38e9094ec5..acbcf01b0d363 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -743,6 +743,11 @@ void Debugger::Terminate() {
debugger->Clear();
g_debugger_list_ptr->clear();
}
+
+ delete g_debugger_list_ptr;
+ delete g_debugger_list_mutex_ptr;
+ g_debugger_list_ptr = nullptr;
+ g_debugger_list_mutex_ptr = nullptr;
}
}
diff --git a/lldb/source/Initialization/SystemInitializerCommon.cpp b/lldb/source/Initialization/SystemInitializerCommon.cpp
index 1a172a95aa147..3ca953d26bb20 100644
--- a/lldb/source/Initialization/SystemInitializerCommon.cpp
+++ b/lldb/source/Initialization/SystemInitializerCommon.cpp
@@ -101,4 +101,5 @@ void SystemInitializerCommon::Terminate() {
Log::DisableAllLogChannels();
FileSystem::Terminate();
Diagnostics::Terminate();
+ TerminateLldbChannel();
}
diff --git a/lldb/source/Utility/LLDBLog.cpp b/lldb/source/Utility/LLDBLog.cpp
index a08764d84edd2..f23a871af6ab4 100644
--- a/lldb/source/Utility/LLDBLog.cpp
+++ b/lldb/source/Utility/LLDBLog.cpp
@@ -87,3 +87,5 @@ template <> Log::Channel &lldb_private::LogChannelFor<LLDBLog>() {
void lldb_private::InitializeLldbChannel() {
Log::Register("lldb", g_log_channel);
}
+
+void lldb_private::TerminateLldbChannel() { Log::Unregister("lldb"); }
diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
index 14789a6694686..c1bb6fad32b6b 100644
--- a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
+++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py
@@ -2,8 +2,6 @@
Test lldb-dap cancel request
"""
-import time
-
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase
diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
index 95573780e94bd..58e9872c061d0 100644
--- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
+++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
@@ -128,6 +128,7 @@ def run_test_evaluate_expressions(
self.assertEvaluate("var1", "20", want_type="int")
# Empty expression should equate to the previous expression.
if context == "repl":
+ self.assertEvaluate("p var1", "20")
self.assertEvaluate("", "20")
else:
self.assertEvaluateFailure("")
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 465d85a07bd34..d5cc50e57c8c8 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -21,21 +21,22 @@
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "ProtocolUtils.h"
-#include "Transport.h"
-#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBLanguageRuntime.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBMutex.h"
#include "lldb/API/SBProcess.h"
-#include "lldb/API/SBStream.h"
-#include "lldb/Host/JSONTransport.h"
+#include "lldb/Host/File.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/PosixApi.h" // IWYU pragma: keep
+#include "lldb/Host/PseudoTerminal.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
@@ -48,15 +49,14 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
-#include <algorithm>
#include <cassert>
#include <chrono>
#include <condition_variable>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
+#include <fcntl.h>
#include <functional>
-#include <future>
#include <memory>
#include <mutex>
#include <optional>
@@ -67,7 +67,6 @@
#if defined(_WIN32)
#define NOMINMAX
-#include <fcntl.h>
#include <io.h>
#include <windows.h>
#else
@@ -78,14 +77,6 @@ using namespace lldb_dap;
using namespace lldb_dap::protocol;
using namespace lldb_private;
-namespace {
-#ifdef _WIN32
-const char DEV_NULL[] = "nul";
-#else
-const char DEV_NULL[] = "/dev/null";
-#endif
-} // namespace
-
namespace lldb_dap {
static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
@@ -120,16 +111,107 @@ static std::string capitalize(llvm::StringRef str) {
return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();
}
+ReplContext::ReplContext(DAP &dap, llvm::StringRef line_ref)
+ : line_ref(line_ref), dap(dap),
+ progress(lldb::SBProgress("Evaluating expression", line_ref.str().c_str(),
+ dap.debugger)) {
+ assert(dap.repl_context == nullptr);
+ dap.repl_context = this;
+ UpdateWantsMore();
+}
+
+ReplContext::~ReplContext() {
+ assert(dap.repl_context == this);
+ dap.repl_context = nullptr;
+}
+
+bool ReplContext::WantsRawInput() {
+ return !dap.debugger.GetCommandInterpreter().IsActive();
+}
+
+void ReplContext::UpdateWantsMore() {
+ if (!WantsRawInput())
+ return;
+
+ stop_on_next_write = true;
+ // If the command interpreter is not active, we're in a raw input. However,
+ // the input handler may not write any new output after receiving the input
+ // for example, if the continuation prompt has no `... ` indicator.
+ //
+ // ```
+ // (lldb) script
+ // >>> sys.ps1 = ''
+ // <no prompt>
+ // ````
+ //
+ // Use a timeout to ensure we don't hang forever if the command entered raw
+ // input mode. We may exit the loop early if output is produced before the
+ // timeout.
+ loop.AddCallback([](auto &loop) { loop.RequestTermination(); },
+ kRawInputTimeout);
+}
+
+llvm::Error ReplContext::Run() {
+ // Ensure the command is terminated.
+ std::string line = line_ref.str() + "\n";
+
+ size_t num_bytes = line.size();
+ if (llvm::Error err =
+ dap.pty_primary->Write(line.data(), num_bytes).takeError())
+ return err;
+
+ // Unblock the IOThread from handling the input.
+ dap.GetAPIMutex().unlock();
+ // Wait for us to receive the repl response.
+ llvm::Error err = loop.Run().takeError();
+ // Lock the API mutex to continue processing.
+ dap.GetAPIMutex().lock();
+
+ return err;
+}
+
+void ReplContext::Write(llvm::StringRef str) {
+ // Skip the line if its the input echo.
+ if (str == std::string(line_ref) + "\r\n")
+ return;
+
+ output.append(str);
+ if (stop_on_next_write)
+ loop.AddPendingCallback([](auto &loop) { loop.RequestTermination(); });
+}
+
+void ReplContext::Write(lldb::SBCommandReturnObject &result) {
+ if (result.GetOutputSize())
+ output.append(result.GetOutput());
+ if (result.GetErrorSize())
+ output.append(result.GetError());
+
+ if (!result.Succeeded())
+ succeeded = false;
+
+ if (result.GetStatus() == lldb::eReturnStatusSuccessFinishResult) {
+ lldb::SBValueList v = result.GetValues(lldb::eNoDynamicValues);
+ for (uint32_t i = 0; i < v.GetSize(); ++i)
+ if (v.GetValueAtIndex(i).IsValid())
+ values.Append(v.GetValueAtIndex(i));
+ }
+
+ UpdateWantsMore();
+
+ if (!WantsRawInput())
+ loop.AddPendingCallback([](auto &loop) { loop.RequestTermination(); });
+}
+
llvm::StringRef DAP::debug_adapter_path = "";
-DAP::DAP(Log *log, const ReplMode default_repl_mode,
- std::vector<std::string> pre_init_commands, bool no_lldbinit,
- llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop)
+DAP::DAP(const ReplMode default_repl_mode,
+ const std::vector<std::string> &pre_init_commands,
+ llvm::StringRef client_name, Log &log, DAPTransport &transport,
+ lldb_private::MainLoop &loop)
: log(log), transport(transport), broadcaster("lldb-dap"),
progress_event_reporter(
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
- repl_mode(default_repl_mode), no_lldbinit(no_lldbinit),
- m_client_name(client_name), m_loop(loop) {
+ repl_mode(default_repl_mode), m_client_name(client_name), m_loop(loop) {
configuration.preInitCommands = std::move(pre_init_commands);
RegisterRequests();
}
@@ -227,15 +309,13 @@ ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
}
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::Console, output);
+ SendOutput(eOutputCategoryConsole, output);
}))
return Error;
if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) {
- SendOutput(OutputType::Console, output);
+ SendOutput(eOutputCategoryConsole, output);
}))
return Error;
@@ -246,7 +326,7 @@ void DAP::StopEventHandlers() {
event_thread_sp.reset();
// Clean up expired event threads from the session manager.
- DAPSessionManager::GetInstance().ReleaseExpiredEventThreads();
+ SessionManager::GetInstance().ReleaseExpiredEventThreads();
// Still handle the progress thread normally since it's per-DAP instance.
if (progress_event_thread.joinable()) {
@@ -263,8 +343,7 @@ void DAP::SendJSON(const llvm::json::Value &json) {
Message message;
llvm::json::Path::Root root;
if (!fromJSON(json, message, root)) {
- DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}",
- m_client_name);
+ DAP_LOG_ERROR(log, root.getError(), "encoding failed: {0}");
return;
}
Send(message);
@@ -275,22 +354,20 @@ Id DAP::Send(const Message &message) {
Message msg = std::visit(
[this](auto &&msg) -> Message {
if (msg.seq == kCalculateSeq)
- msg.seq = seq++;
+ msg.seq = ++seq;
return msg;
},
Message(message));
if (const protocol::Event *event = std::get_if<protocol::Event>(&msg)) {
if (llvm::Error err = transport.Send(*event))
- DAP_LOG_ERROR(log, std::move(err), "({0}) sending event failed",
- m_client_name);
+ DAP_LOG_ERROR(log, std::move(err), "sending event failed: {0}");
return event->seq;
}
if (const Request *req = std::get_if<Request>(&msg)) {
if (llvm::Error err = transport.Send(*req))
- DAP_LOG_ERROR(log, std::move(err), "({0}) sending request failed",
- m_client_name);
+ DAP_LOG_ERROR(log, std::move(err), "sending request failed: {0}");
return req->seq;
}
@@ -310,114 +387,38 @@ Id DAP::Send(const Message &message) {
})
: transport.Send(*resp);
if (err)
- DAP_LOG_ERROR(log, std::move(err), "({0}) sending response failed",
- m_client_name);
+ DAP_LOG_ERROR(log, std::move(err), "sending response failed: {0}");
return resp->seq;
}
llvm_unreachable("Unexpected message type");
}
-// "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) {
+void DAP::SendOutput(OutputCategory cat, const llvm::StringRef output) {
if (output.empty())
return;
- const char *category = nullptr;
- switch (o) {
- case OutputType::Console:
- category = "console";
- break;
- case OutputType::Important:
- category = "important";
- 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);
+ protocol::OutputEventBody body;
+ body.category = cat;
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)));
+ body.output = output.slice(idx, end + 1).str();
+ SendOutput(body);
idx = end + 1;
} while (idx < output.size());
}
+void DAP::SendOutput(const protocol::OutputEventBody &body) {
+ protocol::Event event{/*event=*/"output",
+ /*body=*/body};
+ Send(event);
+}
+
// interface ProgressStartEvent extends Event {
// event: 'progressStart';
//
@@ -510,23 +511,11 @@ void DAP::SendOutput(OutputType o, const llvm::StringRef output) {
// message?: string;
// };
// }
-
void DAP::SendProgressEvent(uint64_t progress_id, const char *message,
uint64_t completed, uint64_t total) {
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<int>(actual_length, sizeof(buffer))));
-}
-
int32_t DAP::CreateSourceReference(lldb::addr_t address) {
std::lock_guard<std::mutex> guard(m_source_references_mutex);
auto iter = llvm::find(m_source_references, address);
@@ -607,6 +596,14 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
return ReplMode::Command;
}
+ // If the command interpreter is not active then its waiting on raw input,
+ // (e.g. `breakpoint command add`).
+ if (!debugger.GetCommandInterpreter().IsActive())
+ return ReplMode::Command;
+
+ if (expression.empty())
+ return ReplMode::Command;
+
switch (repl_mode) {
case ReplMode::Variable:
return ReplMode::Variable;
@@ -724,7 +721,7 @@ bool DAP::RunLLDBCommands(llvm::StringRef prefix,
std::string output = ::RunLLDBCommands(
debugger, prefix, commands, required_command_failed,
/*parse_command_directives*/ true, /*echo_commands*/ true);
- SendOutput(OutputType::Console, output);
+ SendOutput(eOutputCategoryConsole, output);
return !required_command_failed;
}
@@ -854,8 +851,7 @@ bool DAP::HandleObject(const Message &M) {
dispatcher.Set("error",
llvm::Twine("unhandled-command:" + req->command).str());
- DAP_LOG(log, "({0}) error: unhandled command '{1}'", m_client_name,
- req->command);
+ DAP_LOG(log, "error: unhandled command '{1}'", req->command);
return false; // Fail
}
@@ -1001,35 +997,33 @@ void DAP::Received(const protocol::Request &request) {
// effort attempt to interrupt.
std::lock_guard<std::mutex> guard(m_active_request_mutex);
if (m_active_request && cancel_args->requestId == m_active_request->seq) {
- DAP_LOG(log, "({0}) interrupting inflight request (command={1} seq={2})",
- m_client_name, m_active_request->command, m_active_request->seq);
+ DAP_LOG(log, "interrupting inflight request (command={0} seq={1})",
+ m_active_request->command, m_active_request->seq);
debugger.RequestInterrupt();
}
}
std::lock_guard<std::mutex> guard(m_queue_mutex);
- DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name,
- request.command, request.seq);
+ DAP_LOG(log, "queued (command={0} seq={1})", request.command, request.seq);
m_queue.push_back(request);
m_queue_cv.notify_one();
}
void DAP::Received(const protocol::Response &response) {
std::lock_guard<std::mutex> guard(m_queue_mutex);
- DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name,
- response.command, response.request_seq);
+ DAP_LOG(log, "queued (command={0} seq={1})", response.command,
+ response.request_seq);
m_queue.push_back(response);
m_queue_cv.notify_one();
}
void DAP::OnError(llvm::Error error) {
- DAP_LOG_ERROR(log, std::move(error), "({1}) received error: {0}",
- m_client_name);
+ DAP_LOG_ERROR(log, std::move(error), "received error: {0}");
TerminateLoop(/*failed=*/true);
}
void DAP::OnClosed() {
- DAP_LOG(log, "({0}) received EOF", m_client_name);
+ DAP_LOG(log, "received EOF");
TerminateLoop();
}
@@ -1055,16 +1049,14 @@ void DAP::TransportHandler() {
auto handle = transport.RegisterMessageHandler(m_loop, *this);
if (!handle) {
DAP_LOG_ERROR(log, handle.takeError(),
- "({1}) registering message handler failed: {0}",
- m_client_name);
+ "registering message handler failed: {0}");
std::lock_guard<std::mutex> guard(m_queue_mutex);
m_error_occurred = true;
return;
}
if (Status status = m_loop.Run(); status.Fail()) {
- DAP_LOG_ERROR(log, status.takeError(), "({1}) MainLoop run failed: {0}",
- m_client_name);
+ DAP_LOG_ERROR(log, status.takeError(), "MainLoop run failed: {0}");
std::lock_guard<std::mutex> guard(m_queue_mutex);
m_error_occurred = true;
return;
@@ -1081,11 +1073,15 @@ llvm::Error DAP::Loop() {
auto thread = std::thread(std::bind(&DAP::TransportHandler, this));
auto cleanup = llvm::make_scope_exit([this]() {
- // FIXME: Merge these into the MainLoop handler.
+ DAP_LOG(log, "Cleanup DAP handlers");
out.Stop();
err.Stop();
StopEventHandlers();
+ // Close the pty before destroying the debugger to ensure the IOThread
+ // closes.
+ pty_primary.reset();
+
// Destroy the debugger when the session ends. This will trigger the
// debugger's destroy callbacks for earlier logging and clean-ups, rather
// than waiting for the termination of the lldb-dap process.
@@ -1198,7 +1194,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,
+ SendOutput(eOutputCategoryConsole,
llvm::formatv(
"The provided frame format '{0}' couldn't be parsed: {1}\n",
format, error.GetCString())
@@ -1210,7 +1206,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,
+ SendOutput(eOutputCategoryConsole,
llvm::formatv(
"The provided thread format '{0}' couldn't be parsed: {1}\n",
format, error.GetCString())
@@ -1308,8 +1304,150 @@ protocol::Capabilities DAP::GetCustomCapabilities() {
void DAP::StartEventThread() {
// Get event thread for this debugger (creates it if it doesn't exist).
- event_thread_sp = DAPSessionManager::GetInstance().GetEventThreadForDebugger(
- debugger, this);
+ event_thread_sp =
+ SessionManager::GetInstance().GetEventThreadForDebugger(debugger, this);
+}
+
+llvm::Error DAP::InitializeDebugger() {
+ // Setup the PTY that used for evaluating user repl commands.
+ lldb_private::PseudoTerminal pty;
+ if (auto err = pty.OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY | O_NONBLOCK))
+ return err;
+
+ if (auto err = pty.OpenSecondary(O_RDWR | O_NOCTTY))
+ return err;
+
+ pty_primary = std::make_shared<lldb_private::NativeFile>(
+ pty.ReleasePrimaryFileDescriptor(),
+ lldb_private::NativeFile::eOpenOptionReadWrite |
+ lldb_private::NativeFile::eOpenOptionNonBlocking,
+ NativeFile::Owned);
+
+ int pty_replica = pty.ReleaseSecondaryFileDescriptor();
+ lldb::FileSP in = std::make_shared<lldb_private::NativeFile>(
+ pty_replica, lldb_private::NativeFile::eOpenOptionReadOnly,
+ NativeFile::Owned);
+ lldb::FileSP out = std::make_shared<lldb_private::NativeFile>(
+ pty_replica, lldb_private::NativeFile::eOpenOptionWriteOnly,
+ NativeFile::Unowned);
+
+ lldb_private::Terminal term(pty_primary->GetDescriptor());
+ if (llvm::Error err = term.SetCanonical(true))
+ return err;
+
+ // Do not source init files until in/out/err are configured.
+ debugger = lldb::SBDebugger::Create(false);
+ target = debugger.GetDummyTarget();
+
+ debugger.SetInputFile(in);
+ debugger.SetOutputFile(out);
+ debugger.SetErrorFile(out);
+
+ // Disable the prompt in the Debug Console, otherwise the console has a number
+ // of prompts that clutter the output and we don't have direct control over
+ // where they show up in the output.
+ debugger.SetPrompt(nullptr);
+
+ if (client_features.contains(eClientFeatureProgressReporting))
+ StartProgressEventThread();
+
+ // Start our event thread so we can receive events from the debugger, target,
+ // process and more.
+ StartEventThread();
+
+ Status error;
+ m_pty_handle = m_loop.RegisterReadObject(
+ pty_primary,
+ [this](MainLoopBase &loop) {
+ lldb::SBMutex lock = GetAPIMutex();
+ std::lock_guard<lldb::SBMutex> guard(lock);
+
+ char buffer[4096] = {0};
+ size_t bytes_read = sizeof(buffer);
+ if (auto err = pty_primary->Read(buffer, bytes_read).takeError())
+ DAP_LOG_ERROR(log, std::move(err), "Reading from pty failed {0}");
+ if (bytes_read == 0) { // EOF
+ DAP_LOG(log, "pty closed");
+ m_pty_handle.reset(nullptr);
+ return;
+ }
+ std::string str(buffer, bytes_read);
+
+ if (repl_context)
+ repl_context->Write(str);
+ else
+ SendOutput(eOutputCategoryConsole, str);
+ },
+ error);
+ if (error.Fail())
+ DAP_LOG_ERROR(log, error.takeError(),
+ "Failed to register pty read handler: {0}");
+
+ // Register the print callback helper to print to the debug console.
+ debugger.GetCommandInterpreter().SetPrintCallback(
+ [](lldb::SBCommandReturnObject &result, void *baton) {
+ if (!result.GetCommand()) // skip internal commands.
+ return lldb::eCommandReturnObjectPrintCallbackSkipped;
+
+ DAP *dap = static_cast<DAP *>(baton);
+ lldb::SBMutex lock = dap->GetAPIMutex();
+ std::lock_guard<lldb::SBMutex> guard(lock);
+
+ if (dap->repl_context) {
+ dap->repl_context->Write(result);
+ return lldb::eCommandReturnObjectPrintCallbackHandled;
+ }
+
+ lldb::SBValueList variables = result.GetValues(lldb::eNoDynamicValues);
+
+ // Check if we have something to output.
+ if (result.GetOutputSize() == 0 && result.GetErrorSize() == 0 &&
+ !variables)
+ return lldb::eCommandReturnObjectPrintCallbackHandled;
+
+ protocol::OutputEventBody body;
+ body.output = "print_callback: ";
+ body.category = eOutputCategoryConsole;
+ if (result.GetOutputSize())
+ body.output += result.GetOutput();
+ if (result.GetErrorSize())
+ body.output += result.GetError();
+ if (variables && variables.GetSize() > 0) {
+ if (variables.GetSize() == 1) {
+ lldb::SBValue v = variables.GetValueAtIndex(0);
+ if (!IsPersistent(v))
+ v = v.Persist();
+ VariableDescription desc(
+ v, dap->configuration.enableAutoVariableSummaries);
+ if (v.MightHaveChildren() || ValuePointsToCode(v))
+ body.variablesReference = dap->variables.InsertVariable(v);
+ } else {
+ body.variablesReference = dap->variables.InsertVariables(variables);
+ }
+ }
+
+ dap->SendOutput(body);
+
+ return lldb::eCommandReturnObjectPrintCallbackHandled;
+ },
+ this);
+
+ auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand(
+ "lldb-dap", "Commands for managing lldb-dap.");
+ if (client_features.contains(eClientFeatureStartDebuggingRequest)) {
+ cmd.AddCommand(
+ "start-debugging", new StartDebuggingCommand(*this),
+ "Sends a startDebugging request from the debug adapter to the client "
+ "to start a child debug session of the same type as the caller.");
+ }
+
+ cmd.AddCommand(
+ "repl-mode", new ReplModeCommand(*this),
+ "Get or set the repl behavior of lldb-dap evaluation requests.");
+ cmd.AddCommand("send-event", new SendEventCommand(*this),
+ "Sends an DAP event to the client.");
+
+ return llvm::Error::success();
}
void DAP::StartProgressEventThread() {
@@ -1317,7 +1455,7 @@ void DAP::StartProgressEventThread() {
}
void DAP::StartEventThreads() {
- if (clientFeatures.contains(eClientFeatureProgressReporting))
+ if (client_features.contains(eClientFeatureProgressReporting))
StartProgressEventThread();
StartEventThread();
@@ -1345,61 +1483,6 @@ llvm::Error DAP::InitializeDebugger(int debugger_id,
return llvm::Error::success();
}
-llvm::Error DAP::InitializeDebugger() {
- debugger = lldb::SBDebugger::Create(/*argument_name=*/false);
-
- // Configure input/output/error file descriptors.
- debugger.SetInputFile(in);
- target = debugger.GetDummyTarget();
-
- llvm::Expected<int> out_fd = out.GetWriteFileDescriptor();
- if (!out_fd)
- return out_fd.takeError();
- debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
-
- llvm::Expected<int> err_fd = err.GetWriteFileDescriptor();
- if (!err_fd)
- return err_fd.takeError();
- debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
-
- // The sourceInitFile option is not part of the DAP specification. It is an
- // extension used by the test suite to prevent sourcing `.lldbinit` and
- // changing its behavior. The CLI flag --no-lldbinit takes precedence over
- // the DAP parameter.
- bool should_source_init_files = !no_lldbinit && sourceInitFile;
- if (should_source_init_files) {
- debugger.SkipLLDBInitFiles(false);
- debugger.SkipAppInitFiles(false);
- lldb::SBCommandReturnObject init;
- auto interp = debugger.GetCommandInterpreter();
- interp.SourceInitFileInGlobalDirectory(init);
- interp.SourceInitFileInHomeDirectory(init);
- }
-
- // Run initialization commands.
- if (llvm::Error err = RunPreInitCommands())
- return err;
-
- auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand(
- "lldb-dap", "Commands for managing lldb-dap.");
-
- if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) {
- cmd.AddCommand(
- "start-debugging", new StartDebuggingCommand(*this),
- "Sends a startDebugging request from the debug adapter to the client "
- "to start a child debug session of the same type as the caller.");
- }
-
- cmd.AddCommand(
- "repl-mode", new ReplModeCommand(*this),
- "Get or set the repl behavior of lldb-dap evaluation requests.");
- cmd.AddCommand("send-event", new SendEventCommand(*this),
- "Sends an DAP event to the client.");
-
- StartEventThreads();
- return llvm::Error::success();
-}
-
void DAP::ProgressEventThread() {
lldb::SBListener listener("lldb-dap.progress.listener");
debugger.GetBroadcaster().AddListener(
@@ -1428,9 +1511,9 @@ void DAP::ProgressEventThread() {
if (completed == 0) {
if (total == UINT64_MAX) {
- // This progress is non deterministic and won't get updated until it
- // is completed. Send the "message" which will be the combined title
- // and detail. The only other progress event for thus
+ // This progress is non deterministic and won't get updated until
+ // it is completed. Send the "message" which will be the combined
+ // title and detail. The only other progress event for thus
// non-deterministic progress will be the completed event So there
// will be no need to update the detail.
const std::string message =
@@ -1439,9 +1522,9 @@ void DAP::ProgressEventThread() {
} else {
// This progress is deterministic and will receive updates,
// on the progress creation event VSCode will save the message in
- // the create packet and use that as the title, so we send just the
- // title in the progressCreate packet followed immediately by a
- // detail packet, if there is any detail.
+ // the create packet and use that as the title, so we send just
+ // the title in the progressCreate packet followed immediately by
+ // a detail packet, if there is any detail.
const std::string title =
GetStringFromStructuredData(data, "title");
SendProgressEvent(progress_id, title.c_str(), completed, total);
@@ -1449,10 +1532,10 @@ void DAP::ProgressEventThread() {
SendProgressEvent(progress_id, details.c_str(), completed, total);
}
} else {
- // This progress event is either the end of the progress dialog, or an
- // update with possible detail. The "detail" string we send to VS Code
- // will be appended to the progress dialog's initial text from when it
- // was created.
+ // This progress event is either the end of the progress dialog, or
+ // an update with possible detail. The "detail" string we send to VS
+ // Code will be appended to the progress dialog's initial text from
+ // when it was created.
SendProgressEvent(progress_id, details.c_str(), completed, total);
}
}
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index b5f2a57d9dc5f..aa8834b665820 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -17,27 +17,26 @@
#include "OutputRedirector.h"
#include "ProgressEvent.h"
#include "Protocol/ProtocolBase.h"
+#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "SourceBreakpoint.h"
#include "Transport.h"
#include "Variables.h"
#include "lldb/API/SBBroadcaster.h"
-#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBError.h"
-#include "lldb/API/SBFile.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBMutex.h"
+#include "lldb/API/SBProgress.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBThread.h"
#include "lldb/Host/MainLoop.h"
-#include "lldb/Utility/Status.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
@@ -45,6 +44,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
+#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
@@ -52,6 +52,7 @@
#include <memory>
#include <mutex>
#include <optional>
+#include <string>
#include <thread>
#include <vector>
@@ -67,8 +68,7 @@ typedef llvm::DenseMap<lldb::addr_t, InstructionBreakpoint>
using AdapterFeature = protocol::AdapterFeature;
using ClientFeature = protocol::ClientFeature;
-
-enum class OutputType { Console, Important, Stdout, Stderr, Telemetry };
+using OutputCategory = protocol::OutputCategory;
/// Buffer size for handling output events.
constexpr uint64_t OutputBufferSize = (1u << 12);
@@ -79,6 +79,34 @@ enum DAPBroadcasterBits {
};
enum class ReplMode { Variable = 0, Command, Auto };
+struct ReplContext {
+ llvm::StringRef line_ref;
+ bool succeeded = true;
+ bool did_timeout = false;
+ bool stop_on_next_write = false;
+ static constexpr std::chrono::milliseconds kRawInputTimeout =
+ std::chrono::milliseconds(250);
+ DAP &dap;
+ lldb_private::MainLoop loop;
+ llvm::SmallString<256> output;
+ lldb::SBValueList values;
+
+ // FIXME: It would be nice to be able to cancel this operation, at the
+ // moment we do not support progress cancellation.
+ lldb::SBProgress progress;
+
+ explicit ReplContext(DAP &dap, llvm::StringRef line);
+ ~ReplContext();
+
+ bool WantsRawInput();
+
+ void UpdateWantsMore();
+
+ void Write(llvm::StringRef str);
+ void Write(lldb::SBCommandReturnObject &result);
+
+ llvm::Error Run();
+};
using DAPTransport = lldb_private::transport::JSONTransport<ProtocolDescriptor>;
@@ -88,9 +116,9 @@ struct DAP final : public DAPTransport::MessageHandler {
/// Path to the lldb-dap binary itself.
static llvm::StringRef debug_adapter_path;
- Log *log;
+ Log &log;
DAPTransport &transport;
- lldb::SBFile in;
+ lldb::FileSP pty_primary;
OutputRedirector out;
OutputRedirector err;
@@ -145,6 +173,7 @@ struct DAP final : public DAPTransport::MessageHandler {
llvm::SmallDenseMap<int64_t, std::unique_ptr<ResponseHandler>>
inflight_reverse_requests;
ReplMode repl_mode;
+ ReplContext *repl_context = nullptr;
lldb::SBFormat frame_format;
lldb::SBFormat thread_format;
@@ -156,15 +185,12 @@ struct DAP final : public DAPTransport::MessageHandler {
std::string last_nonempty_var_expression;
/// The set of features supported by the connected client.
- llvm::DenseSet<ClientFeature> clientFeatures;
-
- /// Whether to disable sourcing .lldbinit files.
- bool no_lldbinit;
+ llvm::DenseSet<ClientFeature> client_features;
/// Stores whether the initialize request specified a value for
/// lldbExtSourceInitFile. Used by the test suite to prevent sourcing
/// `.lldbinit` and changing its behavior.
- bool sourceInitFile = true;
+ bool source_init_files = true;
/// The initial thread list upon attaching.
std::vector<protocol::Thread> initial_thread_list;
@@ -181,22 +207,22 @@ struct DAP final : public DAPTransport::MessageHandler {
/// Creates a new DAP sessions.
///
- /// \param[in] log
- /// Log 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
/// allocated.
- /// \param[in] no_lldbinit
- /// Whether to disable sourcing .lldbinit files.
+ /// \param[in] client_name
+ /// The name of this client, for example 'stdio' or 'client_1'.
+ /// \param[in] log
+ /// Log stream.
/// \param[in] transport
/// Transport for this debug session.
/// \param[in] loop
/// Main loop associated with this instance.
- DAP(Log *log, const ReplMode default_repl_mode,
- std::vector<std::string> pre_init_commands, bool no_lldbinit,
- llvm::StringRef client_name, DAPTransport &transport,
+ DAP(const ReplMode default_repl_mode,
+ const std::vector<std::string> &pre_init_commands,
+ llvm::StringRef client_name, Log &log, DAPTransport &transport,
lldb_private::MainLoop &loop);
~DAP();
@@ -232,14 +258,13 @@ struct DAP final : public DAPTransport::MessageHandler {
/// Send the given message to the client.
protocol::Id Send(const protocol::Message &message);
- void SendOutput(OutputType o, const llvm::StringRef output);
+ /// Send output to the client.
+ void SendOutput(OutputCategory o, const llvm::StringRef output);
+ void SendOutput(const protocol::OutputEventBody &);
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, ...);
-
int32_t CreateSourceReference(lldb::addr_t address);
std::optional<lldb::addr_t> GetSourceReferenceAddress(int32_t reference);
@@ -509,6 +534,7 @@ struct DAP final : public DAPTransport::MessageHandler {
// Loop for managing reading from the client.
lldb_private::MainLoop &m_loop;
+ lldb_private::MainLoop::ReadHandleUP m_pty_handle;
std::mutex m_cancelled_requests_mutex;
llvm::SmallSet<int64_t, 4> m_cancelled_requests;
diff --git a/lldb/tools/lldb-dap/DAPError.cpp b/lldb/tools/lldb-dap/DAPError.cpp
index 5c5bae37cc600..1fd031e78b965 100644
--- a/lldb/tools/lldb-dap/DAPError.cpp
+++ b/lldb/tools/lldb-dap/DAPError.cpp
@@ -15,9 +15,19 @@ namespace lldb_dap {
char DAPError::ID;
+DAPError::DAPError(std::string message) : DAPError(std::move(message), true) {}
+
+DAPError::DAPError(std::string message, std::error_code EC)
+ : DAPError(std::move(message), std::move(EC), true) {}
+
+DAPError::DAPError(std::string message, bool show_user)
+ : DAPError(std::move(message), llvm::inconvertibleErrorCode(), show_user) {}
+
+DAPError::DAPError(std::string message, std::error_code EC, bool show_user)
+ : DAPError(std::move(message), std::move(EC), show_user, "", "") {}
+
DAPError::DAPError(std::string message, std::error_code EC, bool show_user,
- std::optional<std::string> url,
- std::optional<std::string> url_label)
+ std::string url, std::string url_label)
: m_message(std::move(message)), m_ec(EC), m_show_user(show_user),
m_url(std::move(url)), m_url_label(std::move(url_label)) {}
diff --git a/lldb/tools/lldb-dap/DAPError.h b/lldb/tools/lldb-dap/DAPError.h
index 26b1daae59340..d866c26ee0249 100644
--- a/lldb/tools/lldb-dap/DAPError.h
+++ b/lldb/tools/lldb-dap/DAPError.h
@@ -22,25 +22,31 @@ class DAPError : public llvm::ErrorInfo<DAPError> {
public:
static char ID;
- DAPError(std::string message,
- std::error_code EC = llvm::inconvertibleErrorCode(),
- bool show_user = true, std::optional<std::string> url = std::nullopt,
- std::optional<std::string> url_label = std::nullopt);
+ explicit DAPError(std::string message);
+
+ DAPError(std::string message, std::error_code EC);
+
+ DAPError(std::string message, bool show_user);
+
+ DAPError(std::string message, std::error_code EC, bool show_user);
+
+ DAPError(std::string message, std::error_code EC, bool show_user,
+ std::string url, std::string url_label);
void log(llvm::raw_ostream &OS) const override;
std::error_code convertToErrorCode() const override;
const std::string &getMessage() const { return m_message; }
bool getShowUser() const { return m_show_user; }
- const std::optional<std::string> &getURL() const { return m_url; }
- const std::optional<std::string> &getURLLabel() const { return m_url_label; }
+ const std::string &getURL() const { return m_url; }
+ const std::string &getURLLabel() const { return m_url_label; }
private:
std::string m_message;
std::error_code m_ec;
bool m_show_user;
- std::optional<std::string> m_url;
- std::optional<std::string> m_url_label;
+ std::string m_url;
+ std::string m_url_label;
};
/// An error that indicates the current request handler cannot execute because
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index e7fbbf669e7ec..7bcd0dea8a989 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -9,6 +9,9 @@
#ifndef LLDB_TOOLS_LLDB_DAP_DAPFORWARD_H
#define LLDB_TOOLS_LLDB_DAP_DAPFORWARD_H
+#include "llvm/Support/raw_ostream.h"
+#include <memory>
+
// IWYU pragma: begin_exports
namespace lldb_dap {
@@ -22,6 +25,8 @@ class ResponseHandler;
class SourceBreakpoint;
class Watchpoint;
struct DAP;
+using LogSP = std::shared_ptr<Log>;
+using StreamSP = std::shared_ptr<llvm::raw_ostream>;
} // namespace lldb_dap
namespace lldb {
diff --git a/lldb/tools/lldb-dap/DAPLog.cpp b/lldb/tools/lldb-dap/DAPLog.cpp
index f66784e197518..8907d02d3e9f4 100644
--- a/lldb/tools/lldb-dap/DAPLog.cpp
+++ b/lldb/tools/lldb-dap/DAPLog.cpp
@@ -8,22 +8,32 @@
#include "DAPLog.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <chrono>
#include <mutex>
-#include <system_error>
using namespace llvm;
namespace lldb_dap {
-Log::Log(StringRef filename, std::error_code &EC) : m_stream(filename, EC) {}
+void Log::Emit(StringRef message) {
+ std::lock_guard<Log::Mutex> lock(m_mutex);
+ std::chrono::duration<double> now{
+ std::chrono::system_clock::now().time_since_epoch()};
+ m_stream << formatv("{0:f9} ", now.count()).str() << m_prefix << message
+ << "\n";
+ m_stream.flush();
+}
-void Log::WriteMessage(StringRef message) {
- std::lock_guard<std::mutex> lock(m_mutex);
+void Log::Emit(StringRef file, size_t line, StringRef message) {
+ std::lock_guard<Log::Mutex> lock(m_mutex);
std::chrono::duration<double> now{
std::chrono::system_clock::now().time_since_epoch()};
- m_stream << formatv("{0:f9} ", now.count()).str() << message << "\n";
+ m_stream << formatv("{0:f9} {1}:{2} ", now.count(), sys::path::filename(file),
+ line)
+ .str()
+ << m_prefix << message << "\n";
m_stream.flush();
}
diff --git a/lldb/tools/lldb-dap/DAPLog.h b/lldb/tools/lldb-dap/DAPLog.h
index 484001a9b1628..bfc1cf4f03bd5 100644
--- a/lldb/tools/lldb-dap/DAPLog.h
+++ b/lldb/tools/lldb-dap/DAPLog.h
@@ -9,35 +9,32 @@
#ifndef LLDB_TOOLS_LLDB_DAP_DAPLOG_H
#define LLDB_TOOLS_LLDB_DAP_DAPLOG_H
+#include "DAPForward.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
-#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include <mutex>
#include <string>
-#include <system_error>
// Write a message to log, if logging is enabled.
#define DAP_LOG(log, ...) \
do { \
- ::lldb_dap::Log *log_private = (log); \
- if (log_private) { \
- log_private->WriteMessage(::llvm::formatv(__VA_ARGS__).str()); \
- } \
+ ::lldb_dap::Log &log_private = (log); \
+ log_private.Emit(__FILE__, __LINE__, ::llvm::formatv(__VA_ARGS__).str()); \
} while (0)
// Write message to log, if error is set. In the log message refer to the error
// with {0}. Error is cleared regardless of whether logging is enabled.
#define DAP_LOG_ERROR(log, error, ...) \
do { \
- ::lldb_dap::Log *log_private = (log); \
+ ::lldb_dap::Log &log_private = (log); \
::llvm::Error error_private = (error); \
- if (log_private && error_private) { \
- log_private->WriteMessage( \
+ if (error_private) { \
+ log_private.Emit( \
+ __FILE__, __LINE__, \
::lldb_dap::FormatError(::std::move(error_private), __VA_ARGS__)); \
- } else \
- ::llvm::consumeError(::std::move(error_private)); \
+ } \
} while (0)
namespace lldb_dap {
@@ -46,14 +43,31 @@ namespace lldb_dap {
/// `DAP_LOG_ERROR` helpers.
class Log final {
public:
- /// Creates a log file with the given filename.
- Log(llvm::StringRef filename, std::error_code &EC);
+ using Mutex = std::recursive_mutex;
+
+ Log(llvm::raw_ostream &stream, Mutex &mutex)
+ : m_stream(stream), m_mutex(mutex) {}
+ Log(llvm::StringRef prefix, const Log &log)
+ : m_prefix(prefix), m_stream(log.m_stream), m_mutex(log.m_mutex) {}
+
+ /// Retuns a new Log instance with the associated prefix for all messages.
+ inline Log WithPrefix(llvm::StringRef prefix) const {
+ std::string full_prefix =
+ m_prefix.empty() ? prefix.str() : m_prefix + " " + prefix.str();
+ return Log(full_prefix, *this);
+ }
+
+ /// Emit writes a message to the underlying stream.
+ void Emit(llvm::StringRef message);
- void WriteMessage(llvm::StringRef message);
+ /// Emit writes a message to the underlying stream, including the file and
+ /// line the message originated from.
+ void Emit(llvm::StringRef file, size_t line, llvm::StringRef message);
private:
- std::mutex m_mutex;
- llvm::raw_fd_ostream m_stream;
+ std::string m_prefix;
+ llvm::raw_ostream &m_stream;
+ Mutex &m_mutex;
};
template <typename... Args>
diff --git a/lldb/tools/lldb-dap/DAPSessionManager.cpp b/lldb/tools/lldb-dap/DAPSessionManager.cpp
index d5440ffd64597..7ec6bf060f7bf 100644
--- a/lldb/tools/lldb-dap/DAPSessionManager.cpp
+++ b/lldb/tools/lldb-dap/DAPSessionManager.cpp
@@ -7,15 +7,12 @@
//===----------------------------------------------------------------------===//
#include "DAPSessionManager.h"
#include "DAP.h"
+#include "DAPLog.h"
#include "EventHelper.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBTarget.h"
-#include "lldb/Host/MainLoopBase.h"
-#include "llvm/Support/Threading.h"
#include "llvm/Support/WithColor.h"
-
-#include <chrono>
#include <mutex>
namespace lldb_dap {
@@ -31,55 +28,39 @@ ManagedEventThread::~ManagedEventThread() {
}
}
-DAPSessionManager &DAPSessionManager::GetInstance() {
+SessionManager &SessionManager::GetInstance() {
static std::once_flag initialized;
- static DAPSessionManager *instance =
- nullptr; // NOTE: intentional leak to avoid issues with C++ destructor
- // chain
+ static SessionManager *instance = nullptr; // NOTE: intentional leak to avoid
+ // issues with C++ destructor chain
- std::call_once(initialized, []() { instance = new DAPSessionManager(); });
+ std::call_once(initialized, []() { instance = new SessionManager(); });
return *instance;
}
-void DAPSessionManager::RegisterSession(lldb_private::MainLoop *loop,
- DAP *dap) {
+SessionManager::SessionHandle SessionManager::Register(DAP &dap) {
std::lock_guard<std::mutex> lock(m_sessions_mutex);
- m_active_sessions[loop] = dap;
+ m_active_sessions.insert(&dap);
+ return SessionHandle(*this, dap);
}
-void DAPSessionManager::UnregisterSession(lldb_private::MainLoop *loop) {
- std::unique_lock<std::mutex> lock(m_sessions_mutex);
- m_active_sessions.erase(loop);
- std::notify_all_at_thread_exit(m_sessions_condition, std::move(lock));
-}
-
-std::vector<DAP *> DAPSessionManager::GetActiveSessions() {
+std::vector<DAP *> SessionManager::GetActiveSessions() {
std::lock_guard<std::mutex> lock(m_sessions_mutex);
- std::vector<DAP *> sessions;
- for (const auto &[loop, dap] : m_active_sessions)
- if (dap)
- sessions.emplace_back(dap);
- return sessions;
+ return std::vector<DAP *>(m_active_sessions.begin(), m_active_sessions.end());
}
-void DAPSessionManager::DisconnectAllSessions() {
+void SessionManager::DisconnectAllSessions() {
std::lock_guard<std::mutex> lock(m_sessions_mutex);
m_client_failed = false;
- for (auto [loop, dap] : m_active_sessions) {
- if (dap) {
- if (llvm::Error error = dap->Disconnect()) {
- m_client_failed = true;
- llvm::WithColor::error() << "DAP client disconnected failed: "
- << llvm::toString(std::move(error)) << "\n";
- }
- loop->AddPendingCallback(
- [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
+ for (auto *dap : m_active_sessions)
+ if (llvm::Error error = dap->Disconnect()) {
+ m_client_failed = true;
+ llvm::WithColor::error() << "DAP client disconnected failed: "
+ << llvm::toString(std::move(error)) << "\n";
}
- }
}
-llvm::Error DAPSessionManager::WaitForAllSessionsToDisconnect() {
+llvm::Error SessionManager::WaitForAllSessionsToDisconnect() {
std::unique_lock<std::mutex> lock(m_sessions_mutex);
m_sessions_condition.wait(lock, [this] { return m_active_sessions.empty(); });
@@ -92,8 +73,8 @@ llvm::Error DAPSessionManager::WaitForAllSessionsToDisconnect() {
}
std::shared_ptr<ManagedEventThread>
-DAPSessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger,
- DAP *requesting_dap) {
+SessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger,
+ DAP *requesting_dap) {
lldb::user_id_t debugger_id = debugger.GetID();
std::lock_guard<std::mutex> lock(m_sessions_mutex);
@@ -110,22 +91,23 @@ DAPSessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger,
auto new_thread_sp = std::make_shared<ManagedEventThread>(
requesting_dap->broadcaster,
std::thread(EventThread, debugger, requesting_dap->broadcaster,
- requesting_dap->m_client_name, requesting_dap->log));
+ requesting_dap->GetClientName(),
+ std::ref(requesting_dap->log)));
m_debugger_event_threads[debugger_id] = new_thread_sp;
return new_thread_sp;
}
-DAP *DAPSessionManager::FindDAPForTarget(lldb::SBTarget target) {
+DAP *SessionManager::FindDAPForTarget(lldb::SBTarget target) {
std::lock_guard<std::mutex> lock(m_sessions_mutex);
- for (const auto &[loop, dap] : m_active_sessions)
+ for (auto *dap : m_active_sessions)
if (dap && dap->target.IsValid() && dap->target == target)
return dap;
return nullptr;
}
-void DAPSessionManager::ReleaseExpiredEventThreads() {
+void SessionManager::ReleaseExpiredEventThreads() {
std::lock_guard<std::mutex> lock(m_sessions_mutex);
for (auto it = m_debugger_event_threads.begin();
it != m_debugger_event_threads.end();) {
diff --git a/lldb/tools/lldb-dap/DAPSessionManager.h b/lldb/tools/lldb-dap/DAPSessionManager.h
index ad76b081ad78b..b71de2e177731 100644
--- a/lldb/tools/lldb-dap/DAPSessionManager.h
+++ b/lldb/tools/lldb-dap/DAPSessionManager.h
@@ -16,25 +16,22 @@
#ifndef LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H
#define LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H
+#include "DAPForward.h"
#include "lldb/API/SBBroadcaster.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBTarget.h"
-#include "lldb/Host/MainLoop.h"
#include "lldb/lldb-types.h"
#include "llvm/Support/Error.h"
#include <condition_variable>
#include <map>
#include <memory>
#include <mutex>
-#include <optional>
+#include <set>
#include <thread>
#include <vector>
namespace lldb_dap {
-// Forward declarations
-struct DAP;
-
class ManagedEventThread {
public:
// Constructor declaration
@@ -54,17 +51,32 @@ class ManagedEventThread {
/// a single lldb-dap process. Handles session lifecycle tracking, coordinates
/// shared debugger event threads, and facilitates target handoff between
/// sessions for dynamically created targets.
-class DAPSessionManager {
+class SessionManager {
public:
/// Get the singleton instance of the DAP session manager.
- static DAPSessionManager &GetInstance();
+ static SessionManager &GetInstance();
- /// Register a DAP session.
- void RegisterSession(lldb_private::MainLoop *loop, DAP *dap);
+ /// RAII Session tracking.
+ class SessionHandle {
+ public:
+ SessionManager &manager;
+ DAP &dap;
+
+ SessionHandle(SessionManager &manager, DAP &dap)
+ : manager(manager), dap(dap) {}
- /// Unregister a DAP session. Called by sessions when they complete their
- /// disconnection, which unblocks WaitForAllSessionsToDisconnect().
- void UnregisterSession(lldb_private::MainLoop *loop);
+ ~SessionHandle() {
+ std::scoped_lock<std::mutex> lock(manager.m_sessions_mutex);
+
+ auto index = manager.m_active_sessions.find(&dap);
+ assert(index != manager.m_active_sessions.end());
+ manager.m_active_sessions.erase(index);
+ manager.m_sessions_condition.notify_all();
+ }
+ };
+
+ /// Register a DAP session.
+ SessionHandle Register(DAP &dap);
/// Get all active DAP sessions.
std::vector<DAP *> GetActiveSessions();
@@ -94,19 +106,19 @@ class DAPSessionManager {
void ReleaseExpiredEventThreads();
private:
- DAPSessionManager() = default;
- ~DAPSessionManager() = default;
+ SessionManager() = default;
+ ~SessionManager() = default;
// Non-copyable and non-movable.
- DAPSessionManager(const DAPSessionManager &) = delete;
- DAPSessionManager &operator=(const DAPSessionManager &) = delete;
- DAPSessionManager(DAPSessionManager &&) = delete;
- DAPSessionManager &operator=(DAPSessionManager &&) = delete;
+ SessionManager(const SessionManager &) = delete;
+ SessionManager &operator=(const SessionManager &) = delete;
+ SessionManager(SessionManager &&) = delete;
+ SessionManager &operator=(SessionManager &&) = delete;
bool m_client_failed = false;
std::mutex m_sessions_mutex;
std::condition_variable m_sessions_condition;
- std::map<lldb_private::MainLoop *, DAP *> m_active_sessions;
+ std::set<DAP *> m_active_sessions;
/// Map from debugger ID to its event thread, used when multiple DAP sessions
/// share the same debugger instance.
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index bdb6bb55fe168..6d7406bca6b5d 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -255,12 +255,14 @@ void SendTerminatedEvent(DAP &dap) { dap.SendTerminatedEvent(); }
// Grab any STDOUT and STDERR from the process and send it up to VS Code
// via an "output" event to the "stdout" and "stderr" categories.
void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
- char buffer[OutputBufferSize];
+ char buffer[OutputBufferSize] = {0};
size_t count;
while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0)
- dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count));
+ dap.SendOutput(protocol::eOutputCategoryStderr,
+ llvm::StringRef(buffer, count));
while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0)
- dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count));
+ dap.SendOutput(protocol::eOutputCategoryStderr,
+ llvm::StringRef(buffer, count));
}
// Send a "continued" event to indicate the process is in the running state.
@@ -296,7 +298,7 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
void SendInvalidatedEvent(
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas,
lldb::tid_t tid) {
- if (!dap.clientFeatures.contains(protocol::eClientFeatureInvalidatedEvent))
+ if (!dap.client_features.contains(protocol::eClientFeatureInvalidatedEvent))
return;
protocol::InvalidatedEventBody body;
body.areas = areas;
@@ -308,7 +310,7 @@ void SendInvalidatedEvent(
}
void SendMemoryEvent(DAP &dap, lldb::SBValue variable) {
- if (!dap.clientFeatures.contains(protocol::eClientFeatureMemoryEvent))
+ if (!dap.client_features.contains(protocol::eClientFeatureMemoryEvent))
return;
protocol::MemoryEventBody body;
body.memoryReference = variable.GetLoadAddress();
@@ -318,18 +320,12 @@ void SendMemoryEvent(DAP &dap, lldb::SBValue variable) {
dap.Send(protocol::Event{"memory", std::move(body)});
}
-// Event handler functions that are called by EventThread.
-// These handlers extract the necessary objects from events and find the
-// appropriate DAP instance to handle them, maintaining compatibility with
-// the original DAP::Handle*Event pattern while supporting multi-session
-// debugging.
-
-void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited,
- Log *log) {
+static void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited,
+ Log &log) {
lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event);
// Find the DAP instance that owns this process's target.
- DAP *dap = DAPSessionManager::FindDAP(process.GetTarget());
+ DAP *dap = SessionManager::FindDAP(process.GetTarget());
if (!dap) {
DAP_LOG(log, "Unable to find DAP instance for process {0}",
process.GetProcessID());
@@ -357,8 +353,7 @@ void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited,
SendStdOutStdErr(*dap, process);
if (llvm::Error err = SendThreadStoppedEvent(*dap))
DAP_LOG_ERROR(dap->log, std::move(err),
- "({1}) reporting thread stopped: {0}",
- dap->GetClientName());
+ "reporting thread stopped: {0}");
}
break;
case lldb::eStateRunning:
@@ -369,7 +364,7 @@ void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited,
case lldb::eStateExited:
lldb::SBStream stream;
process.GetStatus(stream);
- dap->SendOutput(OutputType::Console, stream.GetData());
+ dap->SendOutput(OutputCategory::eOutputCategoryConsole, 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
@@ -393,11 +388,11 @@ void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited,
}
}
-void HandleTargetEvent(const lldb::SBEvent &event, Log *log) {
+static void HandleTargetEvent(const lldb::SBEvent &event, Log &log) {
lldb::SBTarget target = lldb::SBTarget::GetTargetFromEvent(event);
// Find the DAP instance that owns this target.
- DAP *dap = DAPSessionManager::FindDAP(target);
+ DAP *dap = SessionManager::FindDAP(target);
if (!dap) {
DAP_LOG(log, "Unable to find DAP instance for target");
return;
@@ -480,7 +475,7 @@ void HandleTargetEvent(const lldb::SBEvent &event, Log *log) {
}
}
-void HandleBreakpointEvent(const lldb::SBEvent &event, Log *log) {
+static void HandleBreakpointEvent(const lldb::SBEvent &event, Log &log) {
const uint32_t event_mask = event.GetType();
if (!(event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged))
return;
@@ -490,7 +485,7 @@ void HandleBreakpointEvent(const lldb::SBEvent &event, Log *log) {
return;
// Find the DAP instance that owns this breakpoint's target.
- DAP *dap = DAPSessionManager::FindDAP(bp.GetTarget());
+ DAP *dap = SessionManager::FindDAP(bp.GetTarget());
if (!dap) {
DAP_LOG(log, "Unable to find DAP instance for breakpoint");
return;
@@ -529,7 +524,7 @@ void HandleBreakpointEvent(const lldb::SBEvent &event, Log *log) {
}
}
-void HandleThreadEvent(const lldb::SBEvent &event, Log *log) {
+static void HandleThreadEvent(const lldb::SBEvent &event, Log &log) {
uint32_t event_type = event.GetType();
if (!(event_type & lldb::SBThread::eBroadcastBitStackChanged))
@@ -540,7 +535,7 @@ void HandleThreadEvent(const lldb::SBEvent &event, Log *log) {
return;
// Find the DAP instance that owns this thread's process/target.
- DAP *dap = DAPSessionManager::FindDAP(thread.GetProcess().GetTarget());
+ DAP *dap = SessionManager::FindDAP(thread.GetProcess().GetTarget());
if (!dap) {
DAP_LOG(log, "Unable to find DAP instance for thread");
return;
@@ -550,10 +545,10 @@ void HandleThreadEvent(const lldb::SBEvent &event, Log *log) {
thread.GetThreadID());
}
-void HandleDiagnosticEvent(const lldb::SBEvent &event, Log *log) {
+static void HandleDiagnosticEvent(const lldb::SBEvent &event, Log &log) {
// Global debugger events - send to all DAP instances.
std::vector<DAP *> active_instances =
- DAPSessionManager::GetInstance().GetActiveSessions();
+ SessionManager::GetInstance().GetActiveSessions();
for (DAP *dap_instance : active_instances) {
if (!dap_instance)
continue;
@@ -565,7 +560,7 @@ void HandleDiagnosticEvent(const lldb::SBEvent &event, Log *log) {
std::string type = GetStringValue(data.GetValueForKey("type"));
std::string message = GetStringValue(data.GetValueForKey("message"));
- dap_instance->SendOutput(OutputType::Important,
+ dap_instance->SendOutput(OutputCategory::eOutputCategoryImportant,
llvm::formatv("{0}: {1}", type, message).str());
}
}
@@ -576,7 +571,7 @@ void HandleDiagnosticEvent(const lldb::SBEvent &event, Log *log) {
// shared event processing loop that:
// 1. Listens to events from a shared debugger instance
// 2. Dispatches events to the appropriate handler, which internally finds the
-// DAP instance using DAPSessionManager::FindDAP()
+// DAP instance using SessionManager::FindDAP()
// 3. Handles events for multiple different DAP sessions
// This allows multiple DAP sessions to share a single debugger and event
// thread, which is essential for the target handoff mechanism where child
@@ -588,8 +583,8 @@ void HandleDiagnosticEvent(const lldb::SBEvent &event, Log *log) {
// them prevent multiple threads from writing simultaneously so no locking
// is required.
void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster,
- llvm::StringRef client_name, Log *log) {
- llvm::set_thread_name("lldb.DAP.client." + client_name + ".event_handler");
+ llvm::StringRef client_name, Log &log) {
+ llvm::set_thread_name("lldb.DAP." + client_name + ".event_handler");
lldb::SBListener listener = debugger.GetListener();
broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
debugger.GetBroadcaster().AddListener(
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
index 3beba2629b2e3..b46d5aef3581f 100644
--- a/lldb/tools/lldb-dap/EventHelper.h
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -51,16 +51,7 @@ void SendMemoryEvent(DAP &dap, lldb::SBValue variable);
/// \param client_name The client name for thread naming/logging purposes.
/// \param log The log instance for logging.
void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster,
- llvm::StringRef client_name, Log *log);
-
-/// Event handler functions called by EventThread.
-/// These handlers extract the necessary objects from events and find the
-/// appropriate DAP instance to handle them.
-void HandleProcessEvent(const lldb::SBEvent &event, bool &done, Log *log);
-void HandleTargetEvent(const lldb::SBEvent &event, Log *log);
-void HandleBreakpointEvent(const lldb::SBEvent &event, Log *log);
-void HandleThreadEvent(const lldb::SBEvent &event, Log *log);
-void HandleDiagnosticEvent(const lldb::SBEvent &event, Log *log);
+ llvm::StringRef client_name, Log &log);
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp
index 24c0ca2111f40..6e4e7c70f1eb8 100644
--- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp
@@ -10,6 +10,7 @@
#include "EventHelper.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolRequests.h"
#include "RequestHandler.h"
#include "lldb/API/SBAttachInfo.h"
@@ -17,7 +18,6 @@
#include "lldb/lldb-defines.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
-#include <cstdint>
using namespace llvm;
using namespace lldb_dap::protocol;
@@ -30,20 +30,12 @@ namespace lldb_dap {
/// Since attaching is debugger/runtime specific, the arguments for this request
/// are not part of this specification.
Error AttachRequestHandler::Run(const AttachRequestArguments &args) const {
+ dap.source_init_files = args.configuration.sourceInitFiles;
// Initialize DAP debugger and related components if not sharing previously
// launched debugger.
- std::optional<int> debugger_id = args.debuggerId;
- std::optional<lldb::user_id_t> target_id = args.targetId;
-
- // Validate that both debugger_id and target_id are provided together.
- if (debugger_id.has_value() != target_id.has_value()) {
- return llvm::createStringError(
- "Both debuggerId and targetId must be specified together for debugger "
- "reuse, or both must be omitted to create a new debugger");
- }
-
- if (Error err = debugger_id && target_id
- ? dap.InitializeDebugger(*debugger_id, *target_id)
+ if (Error err = args.debuggerId != LLDB_DAP_INVALID_DEBUGGER_ID &&
+ args.targetId != LLDB_DAP_INVALID_TARGET_ID
+ ? dap.InitializeDebugger(args.debuggerId, args.targetId)
: dap.InitializeDebugger())
return err;
@@ -51,10 +43,11 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const {
if (args.attachCommands.empty() && args.coreFile.empty() &&
args.configuration.program.empty() &&
args.pid == LLDB_INVALID_PROCESS_ID &&
- args.gdbRemotePort == LLDB_DAP_INVALID_PORT && !target_id.has_value())
+ args.gdbRemotePort == LLDB_DAP_INVALID_PORT &&
+ args.targetId != LLDB_DAP_INVALID_TARGET_ID)
return make_error<DAPError>(
"expected one of 'pid', 'program', 'attachCommands', "
- "'coreFile', 'gdb-remote-port', or target_id to be specified");
+ "'coreFile', 'gdb-remote-port', or 'targetId' to be specified");
// Check if we have mutually exclusive arguments.
if ((args.pid != LLDB_INVALID_PROCESS_ID) &&
@@ -83,12 +76,12 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const {
lldb::SBError error;
lldb::SBTarget target;
- if (target_id) {
+ if (args.targetId != LLDB_DAP_INVALID_TARGET_ID) {
// Use the unique target ID to get the target.
- target = dap.debugger.FindTargetByGloballyUniqueID(*target_id);
+ target = dap.debugger.FindTargetByGloballyUniqueID(args.targetId);
if (!target.IsValid()) {
- error.SetErrorStringWithFormat("invalid target_id %lu in attach config",
- *target_id);
+ error.SetErrorStringWithFormat("invalid target_id %llu in attach config",
+ args.targetId);
}
} else {
target = dap.CreateTarget(error);
@@ -106,7 +99,7 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const {
if ((args.pid == LLDB_INVALID_PROCESS_ID ||
args.gdbRemotePort == LLDB_DAP_INVALID_PORT) &&
args.waitFor) {
- dap.SendOutput(OutputType::Console,
+ dap.SendOutput(eOutputCategoryConsole,
llvm::formatv("Waiting to attach to \"{0}\"...",
dap.target.GetExecutable().GetFilename())
.str());
@@ -143,7 +136,7 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const {
connect_url += std::to_string(args.gdbRemotePort);
dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote",
error);
- } else if (!target_id.has_value()) {
+ } else if (args.targetId != LLDB_DAP_INVALID_TARGET_ID) {
// Attach by pid or process name.
lldb::SBAttachInfo attach_info;
if (args.pid != LLDB_INVALID_PROCESS_ID)
diff --git a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
index de9a15dcb73f4..c8a221b4284de 100644
--- a/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/CompletionsHandler.cpp
@@ -7,10 +7,11 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
-#include "JSONUtils.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
+#include "lldb/API/SBCommandInterpreter.h"
+#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBStringList.h"
using namespace llvm;
diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
index 1bfe7b7f6ef5c..e6e2b55bb9d2d 100644
--- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp
@@ -13,6 +13,7 @@
#include "Protocol/ProtocolRequests.h"
#include "ProtocolUtils.h"
#include "RequestHandler.h"
+#include "lldb/API/SBCommandInterpreterRunOptions.h"
#include "lldb/API/SBDebugger.h"
using namespace llvm;
@@ -42,14 +43,16 @@ ConfigurationDoneRequestHandler::Run(const ConfigurationDoneArguments &) const {
"any debugger command scripts are not resuming the process during the "
"launch sequence.");
- // Waiting until 'configurationDone' to send target based capabilities in case
- // the launch or attach scripts adjust the target. The initial dummy target
- // may have different capabilities than the final target.
-
- /// Also send here custom capabilities to the client, which is consumed by the
- /// lldb-dap specific editor extension.
+ // Send custom capabilities to the client, which is consumed by the lldb-dap
+ // specific editor extension.
SendExtraCapabilities(dap);
+ PrintIntroductionMessage();
+
+ // Spawn the IOHandler thread.
+ dap.debugger.RunCommandInterpreter(/*auto_handle_events=*/false,
+ /*spawn_thread=*/true);
+
// Clients can request a baseline of currently existing threads after
// we acknowledge the configurationDone request.
// Client requests the baseline of currently existing threads after
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index ec26bb66e8aec..48e30ff5c43b5 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -10,12 +10,18 @@
#include "EventHelper.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
+#include "Variables.h"
+#include "lldb/API/SBValue.h"
+#include "lldb/Host/File.h"
+#include "lldb/Utility/Status.h"
#include "lldb/lldb-enumerations.h"
-#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
+#include <chrono>
+#include <cstddef>
using namespace llvm;
using namespace lldb_dap;
@@ -31,38 +37,44 @@ EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const {
EvaluateResponseBody body;
lldb::SBFrame frame = dap.GetLLDBFrame(arguments.frameId);
std::string expression = arguments.expression;
- bool repeat_last_command =
- expression.empty() && dap.last_nonempty_var_expression.empty();
-
- if (arguments.context == protocol::eEvaluateContextRepl &&
- (repeat_last_command ||
- (!expression.empty() &&
- dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
- // Since the current expression is not for a variable, clear the
- // last_nonempty_var_expression field.
- dap.last_nonempty_var_expression.clear();
+
+ if (arguments.context == eEvaluateContextRepl &&
+ dap.DetectReplMode(frame, expression, false) == ReplMode::Command) {
// If we're evaluating a command relative to the current frame, set the
// focus_tid to the current frame for any thread related events.
if (frame.IsValid()) {
dap.focus_tid = frame.GetThread().GetThreadID();
}
- bool required_command_failed = false;
- body.result = RunLLDBCommands(
- dap.debugger, llvm::StringRef(), {expression}, required_command_failed,
- /*parse_command_directives=*/false, /*echo_commands=*/false);
- return body;
- }
+ for (const auto &line_ref : llvm::split(expression, "\n")) {
+ ReplContext context{dap, line_ref};
+ if (llvm::Error err = context.Run())
+ return err;
+
+ if (!context.succeeded)
+ return llvm::make_error<DAPError>(std::string(context.output),
+ /*show_user=*/false);
+
+ body.result += std::string(context.output);
+
+ if (context.values && context.values.GetSize()) {
+ if (context.values.GetSize() == 1) {
+ lldb::SBValue v = context.values.GetValueAtIndex(0);
+ if (!IsPersistent(v))
+ v = v.Persist();
+ VariableDescription desc(
+ v, dap.configuration.enableAutoVariableSummaries);
+ body.type = desc.display_type_name;
+ if (v.MightHaveChildren() || ValuePointsToCode(v))
+ body.variablesReference = dap.variables.InsertVariable(v);
+ } else {
+ body.variablesReference =
+ dap.variables.InsertVariables(context.values);
+ }
+ }
+ }
- if (arguments.context == eEvaluateContextRepl) {
- // If the expression is empty and the last expression was for a
- // variable, set the expression to the previous expression (repeat the
- // evaluation); otherwise save the current non-empty expression for the
- // next (possibly empty) variable expression.
- if (expression.empty())
- expression = dap.last_nonempty_var_expression;
- else
- dap.last_nonempty_var_expression = expression;
+ return body;
}
// Always try to get the answer from the local variables if possible. If
@@ -91,11 +103,8 @@ EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const {
body.result = desc.GetResult(arguments.context);
body.type = desc.display_type_name;
-
if (value.MightHaveChildren() || ValuePointsToCode(value))
- body.variablesReference = dap.variables.InsertVariable(
- value, /*is_permanent=*/arguments.context == eEvaluateContextRepl);
-
+ body.variablesReference = dap.variables.InsertVariable(value);
if (lldb::addr_t addr = value.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS)
body.memoryReference = EncodeMemoryReference(addr);
diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
index 2d30e089447f1..3efbfdca6bc56 100644
--- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp
@@ -6,14 +6,10 @@
//
//===----------------------------------------------------------------------===//
-#include "CommandPlugins.h"
#include "DAP.h"
#include "EventHelper.h"
-#include "JSONUtils.h"
-#include "LLDBUtils.h"
#include "Protocol/ProtocolRequests.h"
#include "RequestHandler.h"
-#include "lldb/API/SBTarget.h"
using namespace lldb_dap;
using namespace lldb_dap::protocol;
@@ -22,8 +18,6 @@ using namespace lldb_dap::protocol;
llvm::Expected<InitializeResponse> InitializeRequestHandler::Run(
const InitializeRequestArguments &arguments) const {
// Store initialization arguments for later use in Launch/Attach.
- dap.clientFeatures = arguments.supportedFeatures;
- dap.sourceInitFile = arguments.lldbExtSourceInitFile;
-
+ dap.client_features = arguments.supportedFeatures;
return dap.GetCapabilities();
}
diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
index 329f0a7bf6453..e823aa8a9a10c 100644
--- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
@@ -22,6 +22,8 @@ namespace lldb_dap {
/// Launch request; value of command field is 'launch'.
Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const {
+ dap.source_init_files = arguments.configuration.sourceInitFiles;
+
// Initialize DAP debugger.
if (Error err = dap.InitializeDebugger())
return err;
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index d67437ad5b3ae..ef0d8ba5e2da5 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -13,10 +13,13 @@
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "Protocol/ProtocolBase.h"
+#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolRequests.h"
#include "RunInTerminal.h"
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBEnvironment.h"
+#include "lldb/API/SBStream.h"
+#include "lldb/lldb-types.h"
#include "llvm/Support/Error.h"
#include <mutex>
@@ -80,7 +83,7 @@ SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
static llvm::Error
RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
- if (!dap.clientFeatures.contains(
+ if (!dap.client_features.contains(
protocol::eClientFeatureRunInTerminalRequest))
return llvm::make_error<DAPError>("Cannot use runInTerminal, feature is "
"not supported by the connected client");
@@ -261,6 +264,26 @@ void BaseRequestHandler::PrintWelcomeMessage() const {
#endif
}
+void BaseRequestHandler::PrintIntroductionMessage() const {
+ lldb::SBStream msg;
+ msg.Print("To get started with the lldb-dap debug console try "
+ "\"<variable>\", \"help [<cmd-name>]\", or \"apropos "
+ "<search-word>\".\r\nFor more information visit "
+ "https://github.com/llvm/llvm-project/blob/main/lldb/tools/"
+ "lldb-dap/README.md\r\n");
+ if (dap.target && dap.target.GetExecutable()) {
+ char path[PATH_MAX] = {0};
+ dap.target.GetExecutable().GetPath(path, sizeof(path));
+ msg.Printf("Executable binary set to '%s' (%s).\r\n", path,
+ dap.target.GetTriple());
+ }
+ if (dap.target.GetProcess()) {
+ msg.Printf("Attached to process %llu.\r\n",
+ dap.target.GetProcess().GetProcessID());
+ }
+ dap.SendOutput(eOutputCategoryConsole, {msg.GetData(), msg.GetSize()});
+}
+
bool BaseRequestHandler::HasInstructionGranularity(
const llvm::json::Object &arguments) const {
if (std::optional<llvm::StringRef> value = arguments.getString("granularity"))
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index 5d235352b7738..317ef300d5fbe 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -64,6 +64,10 @@ class BaseRequestHandler {
/// LLDB_DAP_WELCOME_MESSAGE is defined.
void PrintWelcomeMessage() const;
+ /// Prints an introduction to the debug console and information about the
+ /// debug session.
+ void PrintIntroductionMessage() const;
+
// Takes a LaunchRequest object and launches the process, also handling
// runInTerminal if applicable. It doesn't do any of the additional
// initialization and bookkeeping stuff that is needed for `request_launch`.
@@ -187,26 +191,31 @@ class RequestHandler : public BaseRequestHandler {
response.message = lldb_dap::protocol::eResponseMessageNotStopped;
},
[&](const DAPError &err) {
- protocol::ErrorMessage error_message;
- error_message.sendTelemetry = false;
- error_message.format = err.getMessage();
- error_message.showUser = err.getShowUser();
- error_message.id = err.convertToErrorCode().value();
- error_message.url = err.getURL();
- error_message.urlLabel = err.getURLLabel();
- protocol::ErrorResponseBody body;
- body.error = error_message;
- response.body = body;
+ // If we don't need to show the user, then we can simply return a
+ // message instead.
+ if (err.getShowUser()) {
+ protocol::ErrorMessage error_message;
+ error_message.format = err.getMessage();
+ error_message.showUser = err.getShowUser();
+ error_message.id = err.convertToErrorCode().value();
+ error_message.url = err.getURL();
+ error_message.urlLabel = err.getURLLabel();
+ protocol::ErrorResponseBody body;
+ body.error = error_message;
+ response.body = body;
+ } else {
+ response.message = err.getMessage();
+ }
},
[&](const llvm::ErrorInfoBase &err) {
protocol::ErrorMessage error_message;
error_message.showUser = true;
- error_message.sendTelemetry = false;
error_message.format = err.message();
error_message.id = err.convertToErrorCode().value();
protocol::ErrorResponseBody body;
body.error = error_message;
response.body = body;
+ response.message = err.message();
});
}
};
diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
index b9ae28d6772ac..b942b023e4822 100644
--- a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
@@ -61,8 +61,7 @@ SetVariableRequestHandler::Run(const SetVariableArguments &args) const {
// so always insert a new one to get its variablesReference.
// is_permanent is false because debug console does not support
// setVariable request.
- const int64_t new_var_ref =
- dap.variables.InsertVariable(variable, /*is_permanent=*/false);
+ const int64_t new_var_ref = dap.variables.InsertVariable(variable);
if (variable.MightHaveChildren()) {
body.variablesReference = new_var_ref;
if (desc.type_obj.IsArrayType())
diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
index 5fa2b1ef5e20d..4a6fee81fa7db 100644
--- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp
@@ -106,8 +106,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
if (stop_return_value.MightHaveChildren() ||
stop_return_value.IsSynthetic()) {
- return_var_ref = dap.variables.InsertVariable(stop_return_value,
- /*is_permanent=*/false);
+ return_var_ref = dap.variables.InsertVariable(stop_return_value);
}
variables.emplace_back(CreateVariable(
renamed_return_value, return_var_ref, hex,
@@ -123,8 +122,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
if (!variable.IsValid())
break;
- const int64_t frame_var_ref =
- dap.variables.InsertVariable(variable, /*is_permanent=*/false);
+ const int64_t frame_var_ref = dap.variables.InsertVariable(variable);
variables.emplace_back(CreateVariable(
variable, frame_var_ref, hex,
dap.configuration.enableAutoVariableSummaries,
@@ -135,35 +133,36 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const {
// We are expanding a variable that has children, so we will return its
// children.
lldb::SBValue variable = dap.variables.GetVariable(var_ref);
- if (variable.IsValid()) {
- const bool is_permanent =
- dap.variables.IsPermanentVariableReference(var_ref);
- auto addChild = [&](lldb::SBValue child,
- std::optional<std::string> custom_name = {}) {
- if (!child.IsValid())
- return;
- const int64_t child_var_ref =
- dap.variables.InsertVariable(child, is_permanent);
- variables.emplace_back(
- CreateVariable(child, child_var_ref, hex,
- dap.configuration.enableAutoVariableSummaries,
- dap.configuration.enableSyntheticChildDebugging,
- /*is_name_duplicated=*/false, custom_name));
- };
- const int64_t num_children = variable.GetNumChildren();
- const int64_t end_idx = start + ((count == 0) ? num_children : count);
- int64_t i = start;
- for (; i < end_idx && i < num_children; ++i)
- addChild(variable.GetChildAtIndex(i));
-
- // If we haven't filled the count quota from the request, we insert a new
- // "[raw]" child that can be used to inspect the raw version of a
- // synthetic member. That eliminates the need for the user to go to the
- // debug console and type `frame var <variable> to get these values.
- if (dap.configuration.enableSyntheticChildDebugging &&
- variable.IsSynthetic() && i == num_children)
- addChild(variable.GetNonSyntheticValue(), "[raw]");
+ if (!variable.IsValid()) {
+ return llvm::make_error<DAPError>(llvm::formatv("").str(),
+ llvm::inconvertibleErrorCode(),
+ /*show_user=*/false);
}
+
+ auto addChild = [&](lldb::SBValue child,
+ std::optional<std::string> custom_name = {}) {
+ if (!child.IsValid())
+ return;
+ const int64_t child_var_ref = dap.variables.InsertVariable(child);
+ variables.emplace_back(
+ CreateVariable(child, child_var_ref, hex,
+ dap.configuration.enableAutoVariableSummaries,
+ dap.configuration.enableSyntheticChildDebugging,
+ /*is_name_duplicated=*/false, custom_name));
+ };
+ const int64_t num_children = variable.GetNumChildren();
+ const int64_t end_idx = start + ((count == 0) ? num_children : count);
+ int64_t i = start;
+ for (; i < end_idx && i < num_children; ++i)
+ addChild(variable.GetChildAtIndex(i));
+
+ // If we haven't filled the count quota from the request, we insert a new
+ // "[raw]" child that can be used to inspect the raw version of a
+ // synthetic member. That eliminates the need for the user to go to the
+ // debug console and type `frame var <variable> to get these values.
+ if (dap.configuration.enableSyntheticChildDebugging &&
+ variable.IsSynthetic() && i == num_children)
+ addChild(variable.GetNonSyntheticValue(), "[raw]");
}
return VariablesResponseBody{variables};
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp
index 72359214c8537..0fbd9546abfae 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp
@@ -188,9 +188,9 @@ bool operator==(const Response &a, const Response &b) {
json::Value toJSON(const ErrorMessage &EM) {
json::Object Result{{"id", EM.id}, {"format", EM.format}};
- if (EM.variables) {
+ if (!EM.variables.empty()) {
json::Object variables;
- for (auto &var : *EM.variables)
+ for (auto &var : EM.variables)
variables[var.first] = var.second;
Result.insert({"variables", std::move(variables)});
}
@@ -198,9 +198,9 @@ json::Value toJSON(const ErrorMessage &EM) {
Result.insert({"sendTelemetry", EM.sendTelemetry});
if (EM.showUser)
Result.insert({"showUser", EM.showUser});
- if (EM.url)
+ if (!EM.url.empty())
Result.insert({"url", EM.url});
- if (EM.urlLabel)
+ if (!EM.urlLabel.empty())
Result.insert({"urlLabel", EM.urlLabel});
return std::move(Result);
@@ -209,10 +209,10 @@ json::Value toJSON(const ErrorMessage &EM) {
bool fromJSON(json::Value const &Params, ErrorMessage &EM, json::Path P) {
json::ObjectMapper O(Params, P);
return O && O.map("id", EM.id) && O.map("format", EM.format) &&
- O.map("variables", EM.variables) &&
- O.map("sendTelemetry", EM.sendTelemetry) &&
- O.map("showUser", EM.showUser) && O.map("url", EM.url) &&
- O.map("urlLabel", EM.urlLabel);
+ O.mapOptional("variables", EM.variables) &&
+ O.mapOptional("sendTelemetry", EM.sendTelemetry) &&
+ O.mapOptional("showUser", EM.showUser) &&
+ O.mapOptional("url", EM.url) && O.mapOptional("urlLabel", EM.urlLabel);
}
json::Value toJSON(const Event &E) {
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
index 42c6c8890af24..30b55feda1ee4 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h
@@ -148,7 +148,7 @@ struct ErrorMessage {
/// An object used as a dictionary for looking up the variables in the format
/// string.
- std::optional<std::map<std::string, std::string>> variables;
+ std::map<std::string, std::string> variables;
/// If true send to telemetry.
bool sendTelemetry = false;
@@ -157,10 +157,10 @@ struct ErrorMessage {
bool showUser = false;
/// A url where additional information about this message can be found.
- std::optional<std::string> url;
+ std::string url;
/// A label that is presented to the user as the UI for opening the url.
- std::optional<std::string> urlLabel;
+ std::string urlLabel;
};
bool fromJSON(const llvm::json::Value &, ErrorMessage &, llvm::json::Path);
llvm::json::Value toJSON(const ErrorMessage &);
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
index df6be06637a13..6656b01f12164 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
@@ -64,4 +64,55 @@ llvm::json::Value toJSON(const MemoryEventBody &MEB) {
{"count", MEB.count}};
}
+static llvm::json::Value toJSON(const OutputCategory &OC) {
+ switch (OC) {
+ case eOutputCategoryConsole:
+ return "console";
+ case eOutputCategoryImportant:
+ return "important";
+ case eOutputCategoryStdout:
+ return "stdout";
+ case eOutputCategoryStderr:
+ return "stderr";
+ case eOutputCategoryTelemetry:
+ return "telemetry";
+ }
+ llvm_unreachable("unhandled output category!.");
+}
+
+static llvm::json::Value toJSON(const OutputGroup &OG) {
+ switch (OG) {
+ case eOutputGroupStart:
+ return "start";
+ case eOutputGroupStartCollapsed:
+ return "startCollapsed";
+ case eOutputGroupEnd:
+ return "end";
+ case eOutputGroupNone:
+ break;
+ }
+ llvm_unreachable("unhandled output category!.");
+}
+
+llvm::json::Value toJSON(const OutputEventBody &OEB) {
+ json::Object Result{{"output", OEB.output}, {"category", OEB.category}};
+
+ if (OEB.group != eOutputGroupNone)
+ Result.insert({"group", OEB.group});
+ if (OEB.variablesReference)
+ Result.insert({"variablesReference", OEB.variablesReference});
+ if (OEB.source)
+ Result.insert({"source", OEB.source});
+ if (OEB.line != LLDB_INVALID_LINE_NUMBER)
+ Result.insert({"line", OEB.line});
+ if (OEB.column != LLDB_INVALID_COLUMN_NUMBER)
+ Result.insert({"column", OEB.column});
+ if (OEB.data)
+ Result.insert({"data", OEB.data});
+ if (OEB.locationReference)
+ Result.insert({"locationReference", OEB.locationReference});
+
+ return Result;
+}
+
} // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
index 5cd5a843d284e..bf1857d5c7f08 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
@@ -117,6 +117,99 @@ struct MemoryEventBody {
};
llvm::json::Value toJSON(const MemoryEventBody &);
+/// The output category.
+enum OutputCategory : unsigned {
+ /// 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).
+ eOutputCategoryConsole,
+ /// 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.
+ eOutputCategoryImportant,
+ /// Show the output as normal program output from the debuggee.
+ eOutputCategoryStdout,
+ /// Show the output as error program output from the debuggee.
+ eOutputCategoryStderr,
+ /// Send the output to telemetry instead of showing it to the user
+ eOutputCategoryTelemetry,
+};
+
+enum OutputGroup : unsigned {
+ /// No grouping of output.
+ eOutputGroupNone,
+ /// 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.
+ eOutputGroupStart,
+ /// 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.
+ eOutputGroupStartCollapsed,
+ /// 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.
+ eOutputGroupEnd,
+};
+
+/// The event indicates that the target has produced some output.
+struct OutputEventBody {
+ /// The output category. If not specified or if the category is not understood
+ /// by the client, `console` is assumed.
+ OutputCategory category = eOutputCategoryConsole;
+
+ /// 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;
+
+ /// Support for keeping an output log organized by grouping related messages.
+ OutputGroup group = eOutputGroupNone;
+
+ /// 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.
+ uint64_t variablesReference = 0;
+
+ /// The source location where the output was produced.
+ std::optional<Source> source;
+
+ /// The source location's line where the output was produced.
+ uint32_t line = LLDB_INVALID_LINE_NUMBER;
+
+ /// 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.
+ uint32_t column = LLDB_INVALID_COLUMN_NUMBER;
+
+ /// 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<llvm::json::Value> 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.
+ int64_t locationReference = 0;
+};
+llvm::json::Value toJSON(const OutputEventBody &);
+
} // end namespace lldb_dap::protocol
#endif
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 0a1d580bffd68..d8a7fff629b10 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Protocol/ProtocolRequests.h"
+#include "DAP.h"
#include "JSONUtils.h"
#include "Protocol/ProtocolTypes.h"
#include "lldb/lldb-defines.h"
@@ -221,8 +222,7 @@ bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA,
OM.mapOptional("locale", IRA.locale) &&
OM.mapOptional("linesStartAt1", IRA.linesStartAt1) &&
OM.mapOptional("columnsStartAt1", IRA.columnsStartAt1) &&
- OM.mapOptional("pathFormat", IRA.pathFormat) &&
- OM.mapOptional("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile);
+ OM.mapOptional("pathFormat", IRA.pathFormat);
}
bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) {
@@ -235,6 +235,7 @@ bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) {
O.mapOptional("displayExtendedBacktrace",
C.displayExtendedBacktrace) &&
O.mapOptional("stopOnEntry", C.stopOnEntry) &&
+ O.mapOptional("sourceInitFiles", C.sourceInitFiles) &&
O.mapOptional("commandEscapePrefix", C.commandEscapePrefix) &&
O.mapOptional("customFrameFormat", C.customFrameFormat) &&
O.mapOptional("customThreadFormat", C.customThreadFormat) &&
@@ -312,15 +313,28 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA,
json::Path P) {
json::ObjectMapper O(Params, P);
- return O && fromJSON(Params, ARA.configuration, P) &&
+ if (!O || !O.mapOptional("targetId", ARA.targetId) ||
+ !O.mapOptional("debuggerId", ARA.debuggerId))
+ return false;
+
+ if (ARA.targetId != LLDB_DAP_INVALID_TARGET_ID &&
+ ARA.debuggerId == LLDB_DAP_INVALID_DEBUGGER_ID) {
+ P.field("debuggerId").report("must be set if 'targetId' is set");
+ return false;
+ }
+ if (ARA.targetId == LLDB_DAP_INVALID_TARGET_ID &&
+ ARA.debuggerId != LLDB_DAP_INVALID_DEBUGGER_ID) {
+ P.field("targetId").report("must be set if 'debuggerId' is set");
+ return false;
+ }
+
+ return fromJSON(Params, ARA.configuration, P) &&
O.mapOptional("attachCommands", ARA.attachCommands) &&
O.mapOptional("pid", ARA.pid) &&
O.mapOptional("waitFor", ARA.waitFor) &&
O.mapOptional("gdb-remote-port", ARA.gdbRemotePort) &&
O.mapOptional("gdb-remote-hostname", ARA.gdbRemoteHostname) &&
- O.mapOptional("coreFile", ARA.coreFile) &&
- O.mapOptional("targetId", ARA.targetId) &&
- O.mapOptional("debuggerId", ARA.debuggerId);
+ O.mapOptional("coreFile", ARA.coreFile);
}
bool fromJSON(const json::Value &Params, ContinueArguments &CA, json::Path P) {
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 6a85033ae7ef2..6224c8148fb72 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -128,14 +128,6 @@ struct InitializeRequestArguments {
/// The set of supported features reported by the client.
llvm::DenseSet<ClientFeature> supportedFeatures;
-
- /// lldb-dap Extensions
- /// @{
-
- /// Source init files when initializing lldb::SBDebugger.
- bool lldbExtSourceInitFile = true;
-
- /// @}
};
bool fromJSON(const llvm::json::Value &, InitializeRequestArguments &,
llvm::json::Path);
@@ -171,6 +163,9 @@ struct Configuration {
/// Stop at the entry point of the program when launching or attaching.
bool stopOnEntry = false;
+ /// Source init files (e.g. '~/.lldbinit') on launch or attach.
+ bool sourceInitFiles = true;
+
/// Optional timeout when waiting for the program to `runInTerminal` or
/// attach.
std::chrono::seconds timeout = std::chrono::seconds(30);
@@ -315,6 +310,8 @@ using LaunchResponse = VoidResponse;
#define LLDB_DAP_INVALID_PORT -1
/// An invalid 'frameId' default value.
#define LLDB_DAP_INVALID_FRAME_ID UINT64_MAX
+#define LLDB_DAP_INVALID_DEBUGGER_ID -1
+#define LLDB_DAP_INVALID_TARGET_ID UINT64_MAX
/// lldb-dap specific attach arguments.
struct AttachRequestArguments {
@@ -351,10 +348,10 @@ struct AttachRequestArguments {
std::string coreFile;
/// Unique ID of an existing target to attach to.
- std::optional<lldb::user_id_t> targetId;
+ lldb::user_id_t targetId = LLDB_DAP_INVALID_TARGET_ID;
/// ID of an existing debugger instance to use.
- std::optional<int> debuggerId;
+ int debuggerId = LLDB_DAP_INVALID_DEBUGGER_ID;
/// @}
};
diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
index 843a5eb09c7ae..4fb8c03f4ee99 100644
--- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp
+++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp
@@ -10,6 +10,7 @@
#include "BreakpointBase.h"
#include "DAP.h"
#include "JSONUtils.h"
+#include "Protocol/ProtocolEvents.h"
#include "ProtocolUtils.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBFileSpec.h"
@@ -377,7 +378,7 @@ void SourceBreakpoint::SetLogMessage() {
void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {
std::string message = "Log message has error: ";
message += error;
- m_dap.SendOutput(OutputType::Console, message);
+ m_dap.SendOutput(protocol::eOutputCategoryConsole, message);
}
/*static*/
@@ -411,7 +412,7 @@ bool SourceBreakpoint::BreakpointHitCallback(
}
if (!output.empty() && output.back() != '\n')
output.push_back('\n'); // Ensure log message has line break.
- bp->m_dap.SendOutput(OutputType::Console, output.c_str());
+ bp->m_dap.SendOutput(protocol::eOutputCategoryConsole, output.c_str());
// Do not stop.
return false;
diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp
index 8f71f88cae1f7..31aacd464e733 100644
--- a/lldb/tools/lldb-dap/Transport.cpp
+++ b/lldb/tools/lldb-dap/Transport.cpp
@@ -17,13 +17,10 @@ using namespace lldb_private;
namespace lldb_dap {
-Transport::Transport(llvm::StringRef client_name, lldb_dap::Log *log,
- lldb::IOObjectSP input, lldb::IOObjectSP output)
- : HTTPDelimitedJSONTransport(input, output), m_client_name(client_name),
- m_log(log) {}
+Transport::Transport(lldb_dap::Log &log, lldb::IOObjectSP input,
+ lldb::IOObjectSP output)
+ : HTTPDelimitedJSONTransport(input, output), m_log(log) {}
-void Transport::Log(llvm::StringRef message) {
- DAP_LOG(m_log, "({0}) {1}", m_client_name, message);
-}
+void Transport::Log(llvm::StringRef message) { m_log.Emit(message); }
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h
index 58c48c133f9cb..b20a93475d2dd 100644
--- a/lldb/tools/lldb-dap/Transport.h
+++ b/lldb/tools/lldb-dap/Transport.h
@@ -35,15 +35,14 @@ class Transport final
: public lldb_private::transport::HTTPDelimitedJSONTransport<
ProtocolDescriptor> {
public:
- Transport(llvm::StringRef client_name, lldb_dap::Log *log,
- lldb::IOObjectSP input, lldb::IOObjectSP output);
+ Transport(lldb_dap::Log &log, lldb::IOObjectSP input,
+ lldb::IOObjectSP output);
virtual ~Transport() = default;
void Log(llvm::StringRef message) override;
private:
- llvm::StringRef m_client_name;
- lldb_dap::Log *m_log;
+ lldb_dap::Log &m_log;
};
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp
index 777e3183d8c0d..18841cfacddfa 100644
--- a/lldb/tools/lldb-dap/Variables.cpp
+++ b/lldb/tools/lldb-dap/Variables.cpp
@@ -8,9 +8,17 @@
#include "Variables.h"
#include "JSONUtils.h"
+#include "lldb/API/SBValueList.h"
using namespace lldb_dap;
+bool lldb_dap::IsPersistent(lldb::SBValue &v) {
+ llvm::StringRef name = v.GetName();
+ // Variables stored by the REPL are permanent, like $0, $1, etc.
+ uint64_t dummy_idx;
+ return name.consume_front("$") && !name.consumeInteger(0, dummy_idx);
+}
+
lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
switch (variablesReference) {
case VARREF_LOCALS:
@@ -20,6 +28,9 @@ lldb::SBValueList *Variables::GetTopLevelScope(int64_t variablesReference) {
case VARREF_REGS:
return ®isters;
default:
+ if (m_referencedlists.contains(variablesReference) &&
+ m_referencedlists[variablesReference].IsValid())
+ return &m_referencedlists[variablesReference];
return nullptr;
}
}
@@ -54,12 +65,37 @@ lldb::SBValue Variables::GetVariable(int64_t var_ref) const {
return lldb::SBValue();
}
-int64_t Variables::InsertVariable(lldb::SBValue variable, bool is_permanent) {
- int64_t var_ref = GetNewVariableReference(is_permanent);
- if (is_permanent)
- m_referencedpermanent_variables.insert(std::make_pair(var_ref, variable));
- else
- m_referencedvariables.insert(std::make_pair(var_ref, variable));
+int64_t Variables::InsertVariable(lldb::SBValue variable) {
+ bool perm = IsPersistent(variable);
+
+ llvm::DenseMap<int64_t, lldb::SBValue> &var_map =
+ perm ? m_referencedpermanent_variables : m_referencedvariables;
+ for (auto &[var_ref, var] : var_map) {
+ if (var.IsValid() && var.GetID() == variable.GetID())
+ return var_ref;
+ }
+ int64_t var_ref = GetNewVariableReference(perm);
+ var_map.emplace_or_assign(var_ref, variable);
+ return var_ref;
+}
+
+int64_t Variables::InsertVariables(lldb::SBValueList variables) {
+ for (const auto &[var_ref, variable] : m_referencedlists)
+ if (variable.IsValid() && variable.GetSize() == variables.GetSize()) {
+ bool all_match = true;
+ for (uint32_t i = 0; i < variables.GetSize(); ++i) {
+ if (variable.GetValueAtIndex(i).GetID() !=
+ variables.GetValueAtIndex(i).GetID()) {
+ all_match = false;
+ break;
+ }
+ }
+ if (all_match)
+ return var_ref;
+ }
+
+ int64_t var_ref = GetNewVariableReference(true);
+ m_referencedlists.insert_or_assign(var_ref, variables);
return var_ref;
}
diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h
index 0ed84b36aef99..f8e83533c4e46 100644
--- a/lldb/tools/lldb-dap/Variables.h
+++ b/lldb/tools/lldb-dap/Variables.h
@@ -20,6 +20,8 @@
namespace lldb_dap {
+bool IsPersistent(lldb::SBValue &);
+
struct Variables {
lldb::SBValueList locals;
lldb::SBValueList globals;
@@ -41,7 +43,8 @@ struct Variables {
/// Insert a new \p variable.
/// \return variableReference assigned to this expandable variable.
- int64_t InsertVariable(lldb::SBValue variable, bool is_permanent);
+ int64_t InsertVariable(lldb::SBValue variable);
+ int64_t InsertVariables(lldb::SBValueList variables);
lldb::SBValueList *GetTopLevelScope(int64_t variablesReference);
@@ -62,6 +65,8 @@ struct Variables {
/// These are the variables evaluated from debug console REPL.
llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables;
+ llvm::DenseMap<int64_t, lldb::SBValueList> m_referencedlists;
+
int64_t m_next_temporary_var_ref{VARREF_FIRST_VAR_IDX};
int64_t m_next_permanent_var_ref{PermanentVariableStartIndex};
};
diff --git a/lldb/tools/lldb-dap/tool/Options.td b/lldb/tools/lldb-dap/tool/Options.td
index 339a64fed6c32..21b31dd4f3a4b 100644
--- a/lldb/tools/lldb-dap/tool/Options.td
+++ b/lldb/tools/lldb-dap/tool/Options.td
@@ -68,12 +68,6 @@ def: Separate<["-"], "c">,
Alias<pre_init_command>,
HelpText<"Alias for --pre-init-command">;
-def no_lldbinit: F<"no-lldbinit">,
- HelpText<"Do not automatically parse any '.lldbinit' files.">;
-def: Flag<["-"], "x">,
- Alias<no_lldbinit>,
- HelpText<"Alias for --no-lldbinit">;
-
def connection_timeout: S<"connection-timeout">,
MetaVarName<"<timeout>">,
HelpText<"When using --connection, the number of seconds to wait for new "
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 27516b2a25678..1434ab7c5bb2f 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -410,9 +410,9 @@ validateConnection(llvm::StringRef conn) {
}
static llvm::Error serveConnection(
- const Socket::SocketProtocol &protocol, const std::string &name, Log *log,
+ const Socket::SocketProtocol &protocol, const std::string &name, Log &log,
const ReplMode default_repl_mode,
- const std::vector<std::string> &pre_init_commands, bool no_lldbinit,
+ const std::vector<std::string> &pre_init_commands,
std::optional<std::chrono::seconds> connection_timeout_seconds) {
Status status;
static std::unique_ptr<Socket> listener = Socket::Create(protocol, status);
@@ -446,7 +446,7 @@ static llvm::Error serveConnection(
connection_timeout_seconds.value());
std::condition_variable dap_sessions_condition;
unsigned int clientCount = 0;
- auto handle = listener->Accept(g_loop, [=, &clientCount](
+ auto handle = listener->Accept(g_loop, [=, &log, &clientCount](
std::unique_ptr<Socket> sock) {
// Reset the keep alive timer, because we won't be killing the server
// while this connection is being served.
@@ -460,12 +460,12 @@ static llvm::Error serveConnection(
// Move the client into a background thread to unblock accepting the next
// client.
- std::thread client([=]() {
- llvm::set_thread_name(client_name + ".runloop");
+ std::thread client([=, log = log.WithPrefix(client_name)]() mutable {
+ llvm::set_thread_name("lldb.DAP." + client_name + ".runloop");
MainLoop loop;
- Transport transport(client_name, log, io, io);
- DAP dap(log, default_repl_mode, pre_init_commands, no_lldbinit,
- client_name, transport, loop);
+ Transport transport(log, io, io);
+ DAP dap(default_repl_mode, pre_init_commands, client_name, log, transport,
+ loop);
if (auto Err = dap.ConfigureIO()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
@@ -474,7 +474,7 @@ static llvm::Error serveConnection(
}
// Register the DAP session with the global manager.
- DAPSessionManager::GetInstance().RegisterSession(&loop, &dap);
+ auto session_handle = SessionManager::GetInstance().Register(dap);
if (auto Err = dap.Loop()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
@@ -482,9 +482,8 @@ static llvm::Error serveConnection(
") error: ");
}
- DAP_LOG(log, "({0}) client disconnected", client_name);
- // Unregister the DAP session from the global manager.
- DAPSessionManager::GetInstance().UnregisterSession(&loop);
+ DAP_LOG(log, "client disconnected");
+
// Start the countdown to kill the server at the end of each connection.
if (connection_timeout_seconds)
TrackConnectionTimeout(g_loop, g_connection_timeout_mutex,
@@ -508,10 +507,10 @@ static llvm::Error serveConnection(
"lldb-dap server shutdown requested, disconnecting remaining clients...");
// Disconnect all active sessions using the global manager.
- DAPSessionManager::GetInstance().DisconnectAllSessions();
+ SessionManager::GetInstance().DisconnectAllSessions();
// Wait for all clients to finish disconnecting and return any errors.
- return DAPSessionManager::GetInstance().WaitForAllSessionsToDisconnect();
+ return SessionManager::GetInstance().WaitForAllSessionsToDisconnect();
}
int main(int argc, char *argv[]) {
@@ -642,18 +641,6 @@ int main(int argc, char *argv[]) {
}
#endif
- std::unique_ptr<Log> log = nullptr;
- const char *log_file_path = getenv("LLDBDAP_LOG");
- if (log_file_path) {
- std::error_code EC;
- log = std::make_unique<Log>(log_file_path, EC);
- if (EC) {
- llvm::logAllUnhandledErrors(llvm::errorCodeToError(EC), llvm::errs(),
- "Failed to create log file: ");
- return EXIT_FAILURE;
- }
- }
-
// Initialize LLDB first before we do anything.
lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling();
if (error.Fail()) {
@@ -663,10 +650,25 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
+ StreamSP log_os = nullptr;
+ if (const char *log_file_path = getenv("LLDBDAP_LOG"); log_file_path) {
+ int FD;
+ if (std::error_code EC = llvm::sys::fs::openFileForWrite(
+ log_file_path, FD, llvm::sys::fs::CD_CreateAlways,
+ llvm::sys::fs::OF_Append)) {
+ llvm::errs() << "Failed to open log file: " << log_file_path << ": "
+ << EC.message() << "\n";
+ return EXIT_FAILURE;
+ }
+ log_os = std::make_shared<llvm::raw_fd_ostream>(FD, /*shouldClose=*/true);
+ }
+ Log::Mutex mutex;
+ Log log(log_os ? *log_os.get() : llvm::nulls(), mutex);
+
// 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([log = log.get()]() {
+ lldb_private::MemoryMonitor::Create([&log]() {
DAP_LOG(log, "memory pressure detected");
lldb::SBDebugger::MemoryPressureDetected();
});
@@ -687,11 +689,9 @@ int main(int argc, char *argv[]) {
pre_init_commands.push_back(arg);
}
- bool no_lldbinit = input_args.hasArg(OPT_no_lldbinit);
-
if (!connection.empty()) {
- auto maybeProtoclAndName = validateConnection(connection);
- if (auto Err = maybeProtoclAndName.takeError()) {
+ auto maybeProtocolAndName = validateConnection(connection);
+ if (auto Err = maybeProtocolAndName.takeError()) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"Invalid connection: ");
return EXIT_FAILURE;
@@ -699,10 +699,10 @@ int main(int argc, char *argv[]) {
Socket::SocketProtocol protocol;
std::string name;
- std::tie(protocol, name) = *maybeProtoclAndName;
- if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode,
- pre_init_commands, no_lldbinit,
- connection_timeout_seconds)) {
+ std::tie(protocol, name) = *maybeProtocolAndName;
+ if (auto Err =
+ serveConnection(protocol, name, log, default_repl_mode,
+ pre_init_commands, connection_timeout_seconds)) {
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"Connection failed: ");
return EXIT_FAILURE;
@@ -735,11 +735,11 @@ int main(int argc, char *argv[]) {
lldb::IOObjectSP output = std::make_shared<NativeFile>(
stdout_fd, File::eOpenOptionWriteOnly, NativeFile::Unowned);
- constexpr llvm::StringLiteral client_name = "stdio";
+ Log stdio_log = log.WithPrefix("(stdio) ");
MainLoop loop;
- Transport transport(client_name, log.get(), input, output);
- DAP dap(log.get(), default_repl_mode, pre_init_commands, no_lldbinit,
- client_name, transport, loop);
+ Transport transport(stdio_log, input, output);
+ DAP dap(default_repl_mode, pre_init_commands, "stdio", stdio_log, transport,
+ loop);
// stdout/stderr redirection to the IDE's console
if (auto Err = dap.ConfigureIO(stdout, stderr)) {
@@ -750,20 +750,18 @@ int main(int argc, char *argv[]) {
// Register the DAP session with the global manager for stdio mode.
// This is needed for the event handling to find the correct DAP instance.
- DAPSessionManager::GetInstance().RegisterSession(&loop, &dap);
+ auto session_handle = SessionManager::GetInstance().Register(dap);
// used only by TestVSCode_redirection_to_console.py
if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
redirection_test();
if (auto Err = dap.Loop()) {
- DAP_LOG(log.get(), "({0}) DAP session error: {1}", client_name,
+ DAP_LOG(log, "(stdio) DAP session error: {1}",
llvm::toStringWithoutConsuming(Err));
llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
"DAP session error: ");
- DAPSessionManager::GetInstance().UnregisterSession(&loop);
return EXIT_FAILURE;
}
- DAPSessionManager::GetInstance().UnregisterSession(&loop);
return EXIT_SUCCESS;
}
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index 0f8e9db2fab31..ff1d76c200fbd 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -13,6 +13,7 @@ add_lldb_unittest(DAPTests
ProtocolTypesTest.cpp
ProtocolUtilsTest.cpp
TestBase.cpp
+ TestFixtures.cpp
VariablesTest.cpp
SBAPITEST
diff --git a/lldb/unittests/DAP/DAPErrorTest.cpp b/lldb/unittests/DAP/DAPErrorTest.cpp
index 51138576458d4..f72aa56915e63 100644
--- a/lldb/unittests/DAP/DAPErrorTest.cpp
+++ b/lldb/unittests/DAP/DAPErrorTest.cpp
@@ -21,8 +21,8 @@ TEST(DAPErrorTest, DefaultConstructor) {
EXPECT_EQ(error.getMessage(), "Invalid thread");
EXPECT_EQ(error.convertToErrorCode(), llvm::inconvertibleErrorCode());
EXPECT_TRUE(error.getShowUser());
- EXPECT_EQ(error.getURL(), std::nullopt);
- EXPECT_EQ(error.getURLLabel(), std::nullopt);
+ EXPECT_TRUE(error.getURL().empty());
+ EXPECT_TRUE(error.getURLLabel().empty());
}
TEST(DAPErrorTest, FullConstructor) {
@@ -32,6 +32,6 @@ TEST(DAPErrorTest, FullConstructor) {
EXPECT_EQ(error.getMessage(), "Timed out");
EXPECT_EQ(error.convertToErrorCode(), timed_out);
EXPECT_FALSE(error.getShowUser());
- EXPECT_THAT(error.getURL(), testing::Optional<std::string>("URL"));
- EXPECT_THAT(error.getURLLabel(), testing::Optional<std::string>("URLLabel"));
+ EXPECT_EQ(error.getURL(), "URL");
+ EXPECT_EQ(error.getURLLabel(), "URLLabel");
}
diff --git a/lldb/unittests/DAP/DAPSessionManagerTest.cpp b/lldb/unittests/DAP/DAPSessionManagerTest.cpp
index b840d31ef116d..914cb10aba7be 100644
--- a/lldb/unittests/DAP/DAPSessionManagerTest.cpp
+++ b/lldb/unittests/DAP/DAPSessionManagerTest.cpp
@@ -1,4 +1,4 @@
-//===-- DAPSessionManagerTest.cpp ----------------------------------------===//
+//===-- SessionManagerTest.cpp ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -11,41 +11,39 @@
#include "lldb/API/SBDebugger.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <future>
using namespace lldb_dap;
using namespace lldb;
using namespace lldb_dap_tests;
-class DAPSessionManagerTest : public DAPTestBase {};
+class SessionManagerTest : public DAPTestBase {};
-TEST_F(DAPSessionManagerTest, GetInstanceReturnsSameSingleton) {
- DAPSessionManager &instance1 = DAPSessionManager::GetInstance();
- DAPSessionManager &instance2 = DAPSessionManager::GetInstance();
+TEST_F(SessionManagerTest, GetInstanceReturnsSameSingleton) {
+ SessionManager &instance1 = SessionManager::GetInstance();
+ SessionManager &instance2 = SessionManager::GetInstance();
EXPECT_EQ(&instance1, &instance2);
}
// UnregisterSession uses std::notify_all_at_thread_exit, so it must be called
// from a separate thread to properly release the mutex on thread exit.
-TEST_F(DAPSessionManagerTest, RegisterAndUnregisterSession) {
- DAPSessionManager &manager = DAPSessionManager::GetInstance();
+TEST_F(SessionManagerTest, RegisterAndUnregisterSession) {
+ SessionManager &manager = SessionManager::GetInstance();
// Initially not registered.
std::vector<DAP *> sessions_before = manager.GetActiveSessions();
EXPECT_EQ(
std::count(sessions_before.begin(), sessions_before.end(), dap.get()), 0);
- manager.RegisterSession(&loop, dap.get());
+ {
+ auto handle = manager.Register(*dap.get());
- // Should be in active sessions after registration.
- std::vector<DAP *> sessions_after = manager.GetActiveSessions();
- EXPECT_EQ(std::count(sessions_after.begin(), sessions_after.end(), dap.get()),
- 1);
-
- // Unregister.
- std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); });
-
- unregister_thread.join();
+ // Should be in active sessions after registration.
+ std::vector<DAP *> sessions_after = manager.GetActiveSessions();
+ EXPECT_EQ(
+ std::count(sessions_after.begin(), sessions_after.end(), dap.get()), 1);
+ }
// There should no longer be active sessions.
std::vector<DAP *> sessions_final = manager.GetActiveSessions();
@@ -53,10 +51,10 @@ TEST_F(DAPSessionManagerTest, RegisterAndUnregisterSession) {
0);
}
-TEST_F(DAPSessionManagerTest, DisconnectAllSessions) {
- DAPSessionManager &manager = DAPSessionManager::GetInstance();
+TEST_F(SessionManagerTest, DisconnectAllSessions) {
+ SessionManager &manager = SessionManager::GetInstance();
- manager.RegisterSession(&loop, dap.get());
+ auto handle = manager.Register(*dap.get());
std::vector<DAP *> sessions = manager.GetActiveSessions();
EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1);
@@ -67,37 +65,35 @@ TEST_F(DAPSessionManagerTest, DisconnectAllSessions) {
// sessions to complete or remove them from the active sessions map.
sessions = manager.GetActiveSessions();
EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1);
-
- std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); });
- unregister_thread.join();
}
-TEST_F(DAPSessionManagerTest, WaitForAllSessionsToDisconnect) {
- DAPSessionManager &manager = DAPSessionManager::GetInstance();
+TEST_F(SessionManagerTest, WaitForAllSessionsToDisconnect) {
+ SessionManager &manager = SessionManager::GetInstance();
- manager.RegisterSession(&loop, dap.get());
+ std::promise<void> registered_promise;
+ std::promise<void> unregistered_promise;
+
+ // Register the session after a delay to test blocking behavior.
+ std::thread session_thread([&]() {
+ auto handle = manager.Register(*dap.get());
+ registered_promise.set_value();
+ unregistered_promise.get_future().wait();
+ });
+
+ registered_promise.get_future().wait();
std::vector<DAP *> sessions = manager.GetActiveSessions();
EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1);
- // Unregister after a delay to test blocking behavior.
- std::thread unregister_thread([&]() {
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- manager.UnregisterSession(&loop);
- });
-
+ // Trigger the session_thread to return, which should unregister the session.
+ unregistered_promise.set_value();
// WaitForAllSessionsToDisconnect should block until unregistered.
- auto start = std::chrono::steady_clock::now();
llvm::Error err = manager.WaitForAllSessionsToDisconnect();
EXPECT_FALSE(err);
- auto duration = std::chrono::steady_clock::now() - start;
-
- // Verify it waited at least 100ms.
- EXPECT_GE(duration, std::chrono::milliseconds(100));
// Session should be unregistered now.
sessions = manager.GetActiveSessions();
EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 0);
- unregister_thread.join();
+ session_thread.join();
}
diff --git a/lldb/unittests/DAP/Handler/DisconnectTest.cpp b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
index 212c5698feea8..dd249c12e29f2 100644
--- a/lldb/unittests/DAP/Handler/DisconnectTest.cpp
+++ b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
@@ -38,20 +38,14 @@ TEST_F(DisconnectRequestHandlerTest, DisconnectTriggersTerminated) {
#ifndef __linux__
TEST_F(DisconnectRequestHandlerTest, DisconnectTriggersTerminateCommands) {
CreateDebugger();
-
- if (!GetDebuggerSupportsTarget("X86"))
- GTEST_SKIP() << "Unsupported platform";
-
LoadCore();
DisconnectRequestHandler handler(*dap);
- dap->configuration.terminateCommands = {"?script print(1)",
- "script print(2)"};
+ dap->configuration.terminateCommands = {"?help", "script print(2)"};
EXPECT_EQ(dap->target.GetProcess().GetState(), lldb::eStateStopped);
ASSERT_THAT_ERROR(handler.Run(std::nullopt), Succeeded());
- EXPECT_CALL(client, Received(Output("1\n")));
- EXPECT_CALL(client, Received(Output("2\n"))).Times(2);
+ EXPECT_CALL(client, Received(Output("2\n")));
EXPECT_CALL(client, Received(Output("(lldb) script print(2)\n")));
EXPECT_CALL(client, Received(Output("Running terminateCommands:\n")));
EXPECT_CALL(client, Received(IsEvent("terminated", _)));
diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp
index f4dde9559e9d3..3bafe3e04aec1 100644
--- a/lldb/unittests/DAP/TestBase.cpp
+++ b/lldb/unittests/DAP/TestBase.cpp
@@ -8,13 +8,12 @@
#include "TestBase.h"
#include "DAPLog.h"
-#include "TestingSupport/TestUtilities.h"
#include "lldb/API/SBDefines.h"
-#include "lldb/API/SBStructuredData.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/Pipe.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
#include <cstdio>
@@ -36,13 +35,12 @@ void TransportBase::SetUp() {
std::tie(to_client, to_server) = TestDAPTransport::createPair();
std::error_code EC;
- log = std::make_unique<Log>("-", EC);
+ log = std::make_unique<Log>(llvm::outs(), log_mutex);
dap = std::make_unique<DAP>(
- /*log=*/log.get(),
/*default_repl_mode=*/ReplMode::Auto,
/*pre_init_commands=*/std::vector<std::string>(),
- /*no_lldbinit=*/false,
/*client_name=*/"test_client",
+ /*log=*/*log.get(),
/*transport=*/*to_client, /*loop=*/loop);
auto server_handle = to_server->RegisterMessageHandler(loop, *dap);
@@ -61,79 +59,19 @@ void TransportBase::Run() {
EXPECT_THAT_ERROR(loop.Run().takeError(), llvm::Succeeded());
}
-void DAPTestBase::SetUp() { TransportBase::SetUp(); }
-
-void DAPTestBase::TearDown() {
- if (core)
- ASSERT_THAT_ERROR(core->discard(), Succeeded());
- if (binary)
- ASSERT_THAT_ERROR(binary->discard(), Succeeded());
-}
-
-void DAPTestBase::SetUpTestSuite() {
- lldb::SBError error = SBDebugger::InitializeWithErrorHandling();
- EXPECT_TRUE(error.IsValid());
- EXPECT_TRUE(error.Success());
-}
-void DAPTestBase::TeatUpTestSuite() { SBDebugger::Terminate(); }
-
-bool DAPTestBase::GetDebuggerSupportsTarget(StringRef platform) {
- EXPECT_TRUE(dap->debugger);
-
- lldb::SBStructuredData data = dap->debugger.GetBuildConfiguration()
- .GetValueForKey("targets")
- .GetValueForKey("value");
- for (size_t i = 0; i < data.GetSize(); i++) {
- char buf[100] = {0};
- size_t size = data.GetItemAtIndex(i).GetStringValue(buf, sizeof(buf));
- if (StringRef(buf, size) == platform)
- return true;
- }
-
- return false;
-}
-
void DAPTestBase::CreateDebugger() {
- dap->debugger = lldb::SBDebugger::Create();
+ ASSERT_THAT_ERROR(dap->InitializeDebugger(), Succeeded());
ASSERT_TRUE(dap->debugger);
- dap->target = dap->debugger.GetDummyTarget();
-
- Expected<lldb::FileUP> dev_null = FileSystem::Instance().Open(
- FileSpec(FileSystem::DEV_NULL), File::eOpenOptionReadWrite);
- ASSERT_THAT_EXPECTED(dev_null, Succeeded());
- lldb::FileSP dev_null_sp = std::move(*dev_null);
+ fixtures.debugger = dap->debugger;
- std::FILE *dev_null_stream = dev_null_sp->GetStream();
- ASSERT_THAT_ERROR(dap->ConfigureIO(dev_null_stream, dev_null_stream),
- Succeeded());
+ if (!fixtures.IsPlatformSupported("X86"))
+ GTEST_SKIP() << "Unsupported platform";
- dap->debugger.SetInputFile(dap->in);
- auto out_fd = dap->out.GetWriteFileDescriptor();
- ASSERT_THAT_EXPECTED(out_fd, Succeeded());
- dap->debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
- auto err_fd = dap->out.GetWriteFileDescriptor();
- ASSERT_THAT_EXPECTED(err_fd, Succeeded());
- dap->debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
+ dap->target = dap->debugger.GetDummyTarget();
}
void DAPTestBase::LoadCore() {
- ASSERT_TRUE(dap->debugger);
- llvm::Expected<lldb_private::TestFile> binary_yaml =
- lldb_private::TestFile::fromYamlFile(k_linux_binary);
- ASSERT_THAT_EXPECTED(binary_yaml, Succeeded());
- llvm::Expected<llvm::sys::fs::TempFile> binary_file =
- binary_yaml->writeToTemporaryFile();
- ASSERT_THAT_EXPECTED(binary_file, Succeeded());
- binary = std::move(*binary_file);
- dap->target = dap->debugger.CreateTarget(binary->TmpName.data());
- ASSERT_TRUE(dap->target);
- llvm::Expected<lldb_private::TestFile> core_yaml =
- lldb_private::TestFile::fromYamlFile(k_linux_core);
- ASSERT_THAT_EXPECTED(core_yaml, Succeeded());
- llvm::Expected<llvm::sys::fs::TempFile> core_file =
- core_yaml->writeToTemporaryFile();
- ASSERT_THAT_EXPECTED(core_file, Succeeded());
- this->core = std::move(*core_file);
- SBProcess process = dap->target.LoadCore(this->core->TmpName.data());
- ASSERT_TRUE(process);
-}
+ fixtures.LoadTarget();
+ fixtures.LoadProcess();
+ dap->target = fixtures.target;
+}
\ No newline at end of file
diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h
index c32f3a769c737..e7656323b4b3e 100644
--- a/lldb/unittests/DAP/TestBase.h
+++ b/lldb/unittests/DAP/TestBase.h
@@ -9,6 +9,7 @@
#include "DAP.h"
#include "DAPLog.h"
#include "Protocol/ProtocolBase.h"
+#include "TestFixtures.h"
#include "TestingSupport/Host/JSONTransportTestUtilities.h"
#include "TestingSupport/SubsystemRAII.h"
#include "Transport.h"
@@ -54,11 +55,11 @@ using TestDAPTransport = TestTransport<lldb_dap::ProtocolDescriptor>;
/// messages.
class TransportBase : public testing::Test {
protected:
- lldb_private::SubsystemRAII<lldb_private::FileSystem, lldb_private::HostInfo>
- subsystems;
+ lldb_private::SubsystemRAII<lldb::SBDebugger> subsystems;
lldb_private::MainLoop loop;
lldb_private::MainLoop::ReadHandleUP handles[2];
+ lldb_dap::Log::Mutex log_mutex;
std::unique_ptr<lldb_dap::Log> log;
std::unique_ptr<TestDAPTransport> to_client;
@@ -99,18 +100,8 @@ inline auto Output(llvm::StringRef o, llvm::StringRef cat = "console") {
/// A base class for tests that interact with a `lldb_dap::DAP` instance.
class DAPTestBase : public TransportBase {
protected:
- std::optional<llvm::sys::fs::TempFile> core;
- std::optional<llvm::sys::fs::TempFile> binary;
+ TestFixtures fixtures;
- static constexpr llvm::StringLiteral k_linux_binary = "linux-x86_64.out.yaml";
- static constexpr llvm::StringLiteral k_linux_core = "linux-x86_64.core.yaml";
-
- static void SetUpTestSuite();
- static void TeatUpTestSuite();
- void SetUp() override;
- void TearDown() override;
-
- bool GetDebuggerSupportsTarget(llvm::StringRef platform);
void CreateDebugger();
void LoadCore();
};
diff --git a/lldb/unittests/DAP/TestFixtures.cpp b/lldb/unittests/DAP/TestFixtures.cpp
new file mode 100644
index 0000000000000..c17c37df494b4
--- /dev/null
+++ b/lldb/unittests/DAP/TestFixtures.cpp
@@ -0,0 +1,85 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "TestFixtures.h"
+#include "TestingSupport/TestUtilities.h"
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBStream.h"
+#include "lldb/lldb-enumerations.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap_tests;
+
+TestFixtures::~TestFixtures() {
+ if (m_binary)
+ EXPECT_THAT_ERROR(m_binary->discard(), Succeeded());
+ if (m_core)
+ EXPECT_THAT_ERROR(m_core->discard(), Succeeded());
+}
+
+bool TestFixtures::IsPlatformSupported(StringRef platform) {
+ if (!debugger)
+ LoadDebugger();
+
+ SBStructuredData data =
+ debugger.GetBuildConfiguration().GetValueForKey("targets").GetValueForKey(
+ "value");
+ for (size_t i = 0; i < data.GetSize(); i++) {
+ char buf[100] = {0};
+ size_t size = data.GetItemAtIndex(i).GetStringValue(buf, sizeof(buf));
+ if (StringRef(buf, size) == platform)
+ return true;
+ }
+
+ return false;
+}
+
+void TestFixtures::LoadDebugger() {
+ ASSERT_FALSE(debugger) << "Debugger already loaded";
+ debugger = SBDebugger::Create(false);
+ ASSERT_TRUE(debugger);
+}
+
+void TestFixtures::LoadTarget(llvm::StringRef path) {
+ ASSERT_FALSE(target) << "Target already loaded";
+ ASSERT_TRUE(debugger) << "Debugger not loaded";
+
+ Expected<lldb_private::TestFile> binary_yaml =
+ lldb_private::TestFile::fromYamlFile(path);
+ ASSERT_THAT_EXPECTED(binary_yaml, Succeeded());
+ Expected<sys::fs::TempFile> binary_file = binary_yaml->writeToTemporaryFile();
+ ASSERT_THAT_EXPECTED(binary_file, Succeeded());
+ m_binary = std::move(*binary_file);
+ target = debugger.CreateTarget(m_binary->TmpName.data());
+ ASSERT_TRUE(target);
+}
+
+void TestFixtures::LoadProcess(llvm::StringRef path) {
+ llvm::errs() << "LoadProcess(" << path << ")\n";
+ ASSERT_FALSE(process) << "Process already loaded";
+ ASSERT_TRUE(target) << "Target not loaded";
+ ASSERT_TRUE(debugger) << "Debugger not loaded";
+ ASSERT_EQ(debugger.GetID(), target.GetDebugger().GetID())
+ << "Debugger mismatch";
+
+ Expected<lldb_private::TestFile> core_yaml =
+ lldb_private::TestFile::fromYamlFile(path);
+ ASSERT_THAT_EXPECTED(core_yaml, Succeeded());
+ Expected<sys::fs::TempFile> core_file = core_yaml->writeToTemporaryFile();
+ ASSERT_THAT_EXPECTED(core_file, Succeeded());
+ m_core = std::move(*core_file);
+ lldb::SBError error;
+ process = target.LoadCore(m_core->TmpName.data(), error);
+ lldb::SBStream str;
+ target.GetDescription(str, lldb::eDescriptionLevelFull);
+ ASSERT_TRUE(process) << "Failed to load " << m_core->TmpName.data() << " "
+ << error.GetCString() << " and " << str.GetData();
+}
diff --git a/lldb/unittests/DAP/TestFixtures.h b/lldb/unittests/DAP/TestFixtures.h
new file mode 100644
index 0000000000000..e3a76815db279
--- /dev/null
+++ b/lldb/unittests/DAP/TestFixtures.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_UNITTESTS_DAP_TESTFIXTURES_H
+#define LLDB_UNITTESTS_DAP_TESTFIXTURES_H
+
+#include "lldb/API/SBDebugger.h"
+#include "lldb/API/SBProcess.h"
+#include "lldb/API/SBTarget.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include <optional>
+
+namespace lldb_dap_tests {
+
+class DAPTestBase;
+
+struct TestFixtures {
+ TestFixtures() = default;
+ ~TestFixtures();
+ TestFixtures(const TestFixtures &) = delete;
+ TestFixtures &operator=(const TestFixtures &) = delete;
+
+ static constexpr llvm::StringLiteral k_linux_binary = "linux-x86_64.out.yaml";
+ static constexpr llvm::StringLiteral k_linux_core = "linux-x86_64.core.yaml";
+
+ bool IsPlatformSupported(llvm::StringRef platform);
+
+ lldb::SBDebugger debugger;
+ lldb::SBTarget target;
+ lldb::SBProcess process;
+
+ void LoadDebugger();
+ void LoadTarget(llvm::StringRef path = k_linux_binary);
+ void LoadProcess(llvm::StringRef path = k_linux_core);
+
+private:
+ friend DAPTestBase;
+
+ std::optional<llvm::sys::fs::TempFile> m_binary;
+ std::optional<llvm::sys::fs::TempFile> m_core;
+};
+
+} // namespace lldb_dap_tests
+
+#endif
diff --git a/lldb/unittests/DAP/VariablesTest.cpp b/lldb/unittests/DAP/VariablesTest.cpp
index 6b14fc6c3945d..ec21841858e3c 100644
--- a/lldb/unittests/DAP/VariablesTest.cpp
+++ b/lldb/unittests/DAP/VariablesTest.cpp
@@ -7,16 +7,38 @@
//===----------------------------------------------------------------------===//
#include "Variables.h"
+#include "TestFixtures.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "lldb/API/SBTarget.h"
#include "lldb/API/SBValue.h"
#include "lldb/API/SBValueList.h"
#include "gtest/gtest.h"
+using namespace llvm;
+using namespace lldb;
using namespace lldb_dap;
+using namespace lldb_private;
+using namespace lldb_dap_tests;
class VariablesTest : public ::testing::Test {
protected:
+ SubsystemRAII<SBDebugger> subsystems;
+
enum : bool { Permanent = true, Temporary = false };
+ TestFixtures fixtures;
+ SBValue temp_value;
+ SBValue perm_value;
Variables vars;
+
+ void SetUp() override {
+ fixtures.LoadDebugger();
+ fixtures.LoadTarget();
+ SBTarget &target = fixtures.target;
+ temp_value = target.CreateValueFromExpression("temp", "1");
+ ASSERT_TRUE(temp_value);
+ perm_value = temp_value.Persist();
+ ASSERT_TRUE(perm_value);
+ }
};
TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
@@ -32,19 +54,21 @@ TEST_F(VariablesTest, GetNewVariableReference_UniqueAndRanges) {
}
TEST_F(VariablesTest, InsertAndGetVariable_Temporary) {
- lldb::SBValue dummy;
- const int64_t ref = vars.InsertVariable(dummy, Temporary);
- lldb::SBValue out = vars.GetVariable(ref);
+ const int64_t ref = vars.InsertVariable(temp_value);
+ SBValue out = vars.GetVariable(ref);
- EXPECT_EQ(out.IsValid(), dummy.IsValid());
+ EXPECT_EQ(out.IsValid(), temp_value.IsValid());
+ EXPECT_EQ(out.GetName(), temp_value.GetName());
+ EXPECT_EQ(out.GetValue(), temp_value.GetValue());
}
TEST_F(VariablesTest, InsertAndGetVariable_Permanent) {
- lldb::SBValue dummy;
- const int64_t ref = vars.InsertVariable(dummy, Permanent);
- lldb::SBValue out = vars.GetVariable(ref);
+ const int64_t ref = vars.InsertVariable(perm_value);
+ SBValue out = vars.GetVariable(ref);
- EXPECT_EQ(out.IsValid(), dummy.IsValid());
+ EXPECT_EQ(out.IsValid(), perm_value.IsValid());
+ EXPECT_EQ(out.GetName(), perm_value.GetName());
+ EXPECT_EQ(out.GetValue(), perm_value.GetValue());
}
TEST_F(VariablesTest, IsPermanentVariableReference) {
@@ -56,19 +80,18 @@ TEST_F(VariablesTest, IsPermanentVariableReference) {
}
TEST_F(VariablesTest, Clear_RemovesTemporaryKeepsPermanent) {
- lldb::SBValue dummy;
- const int64_t temp = vars.InsertVariable(dummy, Temporary);
- const int64_t perm = vars.InsertVariable(dummy, Permanent);
+ const int64_t temp = vars.InsertVariable(temp_value);
+ const int64_t perm = vars.InsertVariable(perm_value);
vars.Clear();
EXPECT_FALSE(vars.GetVariable(temp).IsValid());
- EXPECT_EQ(vars.GetVariable(perm).IsValid(), dummy.IsValid());
+ EXPECT_EQ(vars.GetVariable(perm).IsValid(), perm_value.IsValid());
}
TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
- vars.locals.Append(lldb::SBValue());
- vars.globals.Append(lldb::SBValue());
- vars.registers.Append(lldb::SBValue());
+ vars.locals.Append(SBValue());
+ vars.globals.Append(SBValue());
+ vars.registers.Append(SBValue());
EXPECT_EQ(vars.GetTopLevelScope(VARREF_LOCALS), &vars.locals);
EXPECT_EQ(vars.GetTopLevelScope(VARREF_GLOBALS), &vars.globals);
@@ -77,9 +100,9 @@ TEST_F(VariablesTest, GetTopLevelScope_ReturnsCorrectScope) {
}
TEST_F(VariablesTest, FindVariable_LocalsByName) {
- lldb::SBValue dummy;
+ SBValue dummy;
vars.locals.Append(dummy);
- lldb::SBValue found = vars.FindVariable(VARREF_LOCALS, "");
+ SBValue found = vars.FindVariable(VARREF_LOCALS, "");
EXPECT_EQ(found.IsValid(), dummy.IsValid());
}
More information about the lldb-commits
mailing list