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(); } // 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() { 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 #include #include #include #include #include #include +#include #include -#include #include #include #include @@ -67,7 +67,6 @@ #if defined(_WIN32) #define NOMINMAX -#include #include #include #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 = '' + // + // ```` + // + // 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 pre_init_commands, bool no_lldbinit, - llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop) +DAP::DAP(const ReplMode default_repl_mode, + const std::vector &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(&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(&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(actual_length, sizeof(buffer)))); -} - int32_t DAP::CreateSourceReference(lldb::addr_t address) { std::lock_guard 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 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 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 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 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 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( + pty.ReleasePrimaryFileDescriptor(), + lldb_private::NativeFile::eOpenOptionReadWrite | + lldb_private::NativeFile::eOpenOptionNonBlocking, + NativeFile::Owned); + + int pty_replica = pty.ReleaseSecondaryFileDescriptor(); + lldb::FileSP in = std::make_shared( + pty_replica, lldb_private::NativeFile::eOpenOptionReadOnly, + NativeFile::Owned); + lldb::FileSP out = std::make_shared( + 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 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(baton); + lldb::SBMutex lock = dap->GetAPIMutex(); + std::lock_guard 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 out_fd = out.GetWriteFileDescriptor(); - if (!out_fd) - return out_fd.takeError(); - debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); - - llvm::Expected 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 #include #include #include @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -67,8 +68,7 @@ typedef llvm::DenseMap 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; @@ -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> 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 clientFeatures; - - /// Whether to disable sourcing .lldbinit files. - bool no_lldbinit; + llvm::DenseSet 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 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 pre_init_commands, bool no_lldbinit, - llvm::StringRef client_name, DAPTransport &transport, + DAP(const ReplMode default_repl_mode, + const std::vector &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 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 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 url, - std::optional 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 { public: static char ID; - DAPError(std::string message, - std::error_code EC = llvm::inconvertibleErrorCode(), - bool show_user = true, std::optional url = std::nullopt, - std::optional 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 &getURL() const { return m_url; } - const std::optional &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 m_url; - std::optional 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 + // IWYU pragma: begin_exports namespace lldb_dap { @@ -22,6 +25,8 @@ class ResponseHandler; class SourceBreakpoint; class Watchpoint; struct DAP; +using LogSP = std::shared_ptr; +using StreamSP = std::shared_ptr; } // 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 #include -#include 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 lock(m_mutex); + std::chrono::duration 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 lock(m_mutex); +void Log::Emit(StringRef file, size_t line, StringRef message) { + std::lock_guard lock(m_mutex); std::chrono::duration 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 #include -#include // 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 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 #include 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 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 lock(m_sessions_mutex); - m_active_sessions.erase(loop); - std::notify_all_at_thread_exit(m_sessions_condition, std::move(lock)); -} - -std::vector DAPSessionManager::GetActiveSessions() { +std::vector SessionManager::GetActiveSessions() { std::lock_guard lock(m_sessions_mutex); - std::vector sessions; - for (const auto &[loop, dap] : m_active_sessions) - if (dap) - sessions.emplace_back(dap); - return sessions; + return std::vector(m_active_sessions.begin(), m_active_sessions.end()); } -void DAPSessionManager::DisconnectAllSessions() { +void SessionManager::DisconnectAllSessions() { std::lock_guard 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 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 -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 lock(m_sessions_mutex); @@ -110,22 +91,23 @@ DAPSessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger, auto new_thread_sp = std::make_shared( 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 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 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 #include #include #include -#include +#include #include #include 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 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 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 m_active_sessions; + std::set 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 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 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 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 debugger_id = args.debuggerId; - std::optional 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( "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 +#include 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(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 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 @@ -80,7 +83,7 @@ SetupIORedirection(const std::vector> &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("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 " + "\"\", \"help []\", or \"apropos " + "\".\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 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 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 to get these values. - if (dap.configuration.enableSyntheticChildDebugging && - variable.IsSynthetic() && i == num_children) - addChild(variable.GetNonSyntheticValue(), "[raw]"); + if (!variable.IsValid()) { + return llvm::make_error(llvm::formatv("").str(), + llvm::inconvertibleErrorCode(), + /*show_user=*/false); } + + auto addChild = [&](lldb::SBValue child, + std::optional 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 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> variables; + std::map 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 url; + std::string url; /// A label that is presented to the user as the UI for opening the url. - std::optional 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; + + /// 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 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 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 targetId; + lldb::user_id_t targetId = LLDB_DAP_INVALID_TARGET_ID; /// ID of an existing debugger instance to use. - std::optional 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 &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 m_referencedpermanent_variables; + llvm::DenseMap 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, HelpText<"Alias for --pre-init-command">; -def no_lldbinit: F<"no-lldbinit">, - HelpText<"Do not automatically parse any '.lldbinit' files.">; -def: Flag<["-"], "x">, - Alias, - HelpText<"Alias for --no-lldbinit">; - def connection_timeout: S<"connection-timeout">, MetaVarName<"">, 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 &pre_init_commands, bool no_lldbinit, + const std::vector &pre_init_commands, std::optional connection_timeout_seconds) { Status status; static std::unique_ptr 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 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 = nullptr; - const char *log_file_path = getenv("LLDBDAP_LOG"); - if (log_file_path) { - std::error_code EC; - log = std::make_unique(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(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 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( 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("URL")); - EXPECT_THAT(error.getURLLabel(), testing::Optional("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 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 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 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 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 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 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 registered_promise; + std::promise 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 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 @@ -36,13 +35,12 @@ void TransportBase::SetUp() { std::tie(to_client, to_server) = TestDAPTransport::createPair(); std::error_code EC; - log = std::make_unique("-", EC); + log = std::make_unique(llvm::outs(), log_mutex); dap = std::make_unique( - /*log=*/log.get(), /*default_repl_mode=*/ReplMode::Auto, /*pre_init_commands=*/std::vector(), - /*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 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 binary_yaml = - lldb_private::TestFile::fromYamlFile(k_linux_binary); - ASSERT_THAT_EXPECTED(binary_yaml, Succeeded()); - llvm::Expected 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 core_yaml = - lldb_private::TestFile::fromYamlFile(k_linux_core); - ASSERT_THAT_EXPECTED(core_yaml, Succeeded()); - llvm::Expected 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; /// messages. class TransportBase : public testing::Test { protected: - lldb_private::SubsystemRAII - subsystems; + lldb_private::SubsystemRAII subsystems; lldb_private::MainLoop loop; lldb_private::MainLoop::ReadHandleUP handles[2]; + lldb_dap::Log::Mutex log_mutex; std::unique_ptr log; std::unique_ptr 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 core; - std::optional 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 binary_yaml = + lldb_private::TestFile::fromYamlFile(path); + ASSERT_THAT_EXPECTED(binary_yaml, Succeeded()); + Expected 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 core_yaml = + lldb_private::TestFile::fromYamlFile(path); + ASSERT_THAT_EXPECTED(core_yaml, Succeeded()); + Expected 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 + +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 m_binary; + std::optional 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 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()); }