Author: Jonas Devlieghere Date: 2025-05-09T00:29:59-07:00 New Revision: 60b62c65bd693fa104d913bb401de3b992902520
URL: https://github.com/llvm/llvm-project/commit/60b62c65bd693fa104d913bb401de3b992902520 DIFF: https://github.com/llvm/llvm-project/commit/60b62c65bd693fa104d913bb401de3b992902520.diff LOG: [lldb-dap] Move the event and progress event threads into DAP (NFC) (#139167) Added: Modified: lldb/tools/lldb-dap/DAP.cpp lldb/tools/lldb-dap/DAP.h lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp Removed: ################################################################################ diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 1b118bb8c6a2b..f6754b1f8d7a3 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -8,6 +8,7 @@ #include "DAP.h" #include "DAPLog.h" +#include "EventHelper.h" #include "Handler/RequestHandler.h" #include "Handler/ResponseHandler.h" #include "JSONUtils.h" @@ -20,6 +21,7 @@ #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/SBProcess.h" @@ -52,6 +54,7 @@ #include <mutex> #include <optional> #include <string> +#include <thread> #include <utility> #include <variant> @@ -77,6 +80,48 @@ const char DEV_NULL[] = "/dev/null"; namespace lldb_dap { +static std::string GetStringFromStructuredData(lldb::SBStructuredData &data, + const char *key) { + lldb::SBStructuredData keyValue = data.GetValueForKey(key); + if (!keyValue) + return std::string(); + + const size_t length = keyValue.GetStringValue(nullptr, 0); + + if (length == 0) + return std::string(); + + std::string str(length + 1, 0); + keyValue.GetStringValue(&str[0], length + 1); + return str; +} + +static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data, + const char *key) { + lldb::SBStructuredData keyValue = data.GetValueForKey(key); + + if (!keyValue.IsValid()) + return 0; + return keyValue.GetUnsignedIntegerValue(); +} + +static llvm::StringRef GetModuleEventReason(uint32_t event_mask) { + if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) + return "new"; + if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) + return "removed"; + assert(event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged); + return "changed"; +} + +/// Return string with first character capitalized. +static std::string capitalize(llvm::StringRef str) { + if (str.empty()) + return ""; + return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str(); +} + llvm::StringRef DAP::debug_adapter_path = ""; DAP::DAP(Log *log, const ReplMode default_repl_mode, @@ -94,13 +139,6 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode, DAP::~DAP() = default; -/// Return string with first character capitalized. -static std::string capitalize(llvm::StringRef str) { - if (str.empty()) - return ""; - return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str(); -} - void DAP::PopulateExceptionBreakpoints() { llvm::call_once(init_exception_breakpoints_flag, [this]() { exception_breakpoints = std::vector<ExceptionBreakpoint>{}; @@ -1387,4 +1425,213 @@ protocol::Capabilities DAP::GetCapabilities() { return capabilities; } +void DAP::StartEventThread() { + event_thread = std::thread(&DAP::EventThread, this); +} + +void DAP::StartProgressEventThread() { + progress_event_thread = std::thread(&DAP::ProgressEventThread, this); +} + +void DAP::ProgressEventThread() { + lldb::SBListener listener("lldb-dap.progress.listener"); + debugger.GetBroadcaster().AddListener( + listener, lldb::SBDebugger::eBroadcastBitProgress | + lldb::SBDebugger::eBroadcastBitExternalProgress); + broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); + lldb::SBEvent event; + bool done = false; + while (!done) { + if (listener.WaitForEvent(1, event)) { + const auto event_mask = event.GetType(); + if (event.BroadcasterMatchesRef(broadcaster)) { + if (event_mask & eBroadcastBitStopProgressThread) { + done = true; + } + } else { + lldb::SBStructuredData data = + lldb::SBDebugger::GetProgressDataFromEvent(event); + + const uint64_t progress_id = + GetUintFromStructuredData(data, "progress_id"); + const uint64_t completed = GetUintFromStructuredData(data, "completed"); + const uint64_t total = GetUintFromStructuredData(data, "total"); + const std::string details = + GetStringFromStructuredData(data, "details"); + + 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 + // non-deterministic progress will be the completed event So there + // will be no need to update the detail. + const std::string message = + GetStringFromStructuredData(data, "message"); + SendProgressEvent(progress_id, message.c_str(), completed, total); + } 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. + const std::string title = + GetStringFromStructuredData(data, "title"); + SendProgressEvent(progress_id, title.c_str(), completed, total); + if (!details.empty()) + 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. + SendProgressEvent(progress_id, details.c_str(), completed, total); + } + } + } + } +} + +// All events from the debugger, target, process, thread and frames are +// received in this function that runs in its own thread. We are using a +// "FILE *" to output packets back to VS Code and they have mutexes in them +// them prevent multiple threads from writing simultaneously so no locking +// is required. +void DAP::EventThread() { + llvm::set_thread_name(transport.GetClientName() + ".event_handler"); + lldb::SBEvent event; + lldb::SBListener listener = debugger.GetListener(); + broadcaster.AddListener(listener, eBroadcastBitStopEventThread); + debugger.GetBroadcaster().AddListener( + listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); + bool done = false; + while (!done) { + if (listener.WaitForEvent(1, event)) { + const auto event_mask = event.GetType(); + if (lldb::SBProcess::EventIsProcessEvent(event)) { + lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); + if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { + auto state = lldb::SBProcess::GetStateFromEvent(event); + switch (state) { + case lldb::eStateConnected: + case lldb::eStateDetached: + case lldb::eStateInvalid: + case lldb::eStateUnloaded: + break; + case lldb::eStateAttaching: + case lldb::eStateCrashed: + case lldb::eStateLaunching: + case lldb::eStateStopped: + case lldb::eStateSuspended: + // Only report a stopped event if the process was not + // automatically restarted. + if (!lldb::SBProcess::GetRestartedFromEvent(event)) { + SendStdOutStdErr(*this, process); + SendThreadStoppedEvent(*this); + } + break; + case lldb::eStateRunning: + case lldb::eStateStepping: + WillContinue(); + SendContinuedEvent(*this); + break; + case lldb::eStateExited: + lldb::SBStream stream; + process.GetStatus(stream); + SendOutput(OutputType::Console, 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 + // we don't have to terminate the session. + if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || + process.GetProcessID() == restarting_process_id) { + restarting_process_id = LLDB_INVALID_PROCESS_ID; + } else { + // Run any exit LLDB commands the user specified in the + // launch.json + RunExitCommands(); + SendProcessExitedEvent(*this, process); + SendTerminatedEvent(); + done = true; + } + break; + } + } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || + (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { + SendStdOutStdErr(*this, process); + } + } else if (lldb::SBTarget::EventIsTargetEvent(event)) { + if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || + event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { + llvm::StringRef reason = GetModuleEventReason(event_mask); + const uint32_t num_modules = + lldb::SBTarget::GetNumModulesFromEvent(event); + for (uint32_t i = 0; i < num_modules; ++i) { + lldb::SBModule module = + lldb::SBTarget::GetModuleAtIndexFromEvent(i, event); + if (!module.IsValid()) + continue; + + llvm::json::Object body; + body.try_emplace("reason", reason); + body.try_emplace("module", CreateModule(target, module)); + llvm::json::Object module_event = CreateEventObject("module"); + module_event.try_emplace("body", std::move(body)); + SendJSON(llvm::json::Value(std::move(module_event))); + } + } + } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { + if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { + auto event_type = + lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); + auto bp = Breakpoint( + *this, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); + // If the breakpoint was set through DAP, it will have the + // BreakpointBase::kDAPBreakpointLabel. Regardless of whether + // locations were added, removed, or resolved, the breakpoint isn't + // going away and the reason is always "changed". + if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || + event_type & lldb::eBreakpointEventTypeLocationsRemoved || + event_type & lldb::eBreakpointEventTypeLocationsResolved) && + bp.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { + // As the DAP client already knows the path of this breakpoint, we + // don't need to send it back as part of the "changed" event. This + // avoids sending paths that should be source mapped. Note that + // CreateBreakpoint doesn't apply source mapping and certain + // implementation ignore the source part of this event anyway. + llvm::json::Value source_bp = CreateBreakpoint(&bp); + source_bp.getAsObject()->erase("source"); + + llvm::json::Object body; + body.try_emplace("breakpoint", source_bp); + body.try_emplace("reason", "changed"); + + llvm::json::Object bp_event = CreateEventObject("breakpoint"); + bp_event.try_emplace("body", std::move(body)); + + SendJSON(llvm::json::Value(std::move(bp_event))); + } + } + } else if (event_mask & lldb::eBroadcastBitError || + event_mask & lldb::eBroadcastBitWarning) { + lldb::SBStructuredData data = + lldb::SBDebugger::GetDiagnosticFromEvent(event); + if (!data.IsValid()) + continue; + std::string type = GetStringValue(data.GetValueForKey("type")); + std::string message = GetStringValue(data.GetValueForKey("message")); + SendOutput(OutputType::Important, + llvm::formatv("{0}: {1}", type, message).str()); + } else if (event.BroadcasterMatchesRef(broadcaster)) { + if (event_mask & eBroadcastBitStopEventThread) { + done = true; + } + } + } + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index b581ae759b1bc..afeda8d81efb0 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -167,8 +167,6 @@ struct DAP { lldb::SBTarget target; Variables variables; lldb::SBBroadcaster broadcaster; - std::thread event_thread; - std::thread progress_event_thread; llvm::StringMap<SourceBreakpointMap> source_breakpoints; FunctionBreakpointMap function_breakpoints; InstructionBreakpointMap instruction_breakpoints; @@ -418,7 +416,19 @@ struct DAP { lldb::SBMutex GetAPIMutex() const { return target.GetAPIMutex(); } + void StartEventThread(); + void StartProgressEventThread(); + private: + /// Event threads. + /// @{ + void EventThread(); + void ProgressEventThread(); + + std::thread event_thread; + std::thread progress_event_thread; + /// @} + /// Queue for all incoming messages. std::deque<protocol::Message> m_queue; std::deque<protocol::Message> m_pending_queue; diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 6d1e2c7402f4c..b64987746b3d5 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -12,255 +12,11 @@ #include "LLDBUtils.h" #include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" -#include "lldb/API/SBEvent.h" -#include "lldb/API/SBListener.h" -#include "lldb/API/SBStream.h" #include "lldb/API/SBTarget.h" -#include <cstdint> -using namespace lldb; +using namespace lldb_dap; using namespace lldb_dap::protocol; -namespace lldb_dap { - -static std::string GetStringFromStructuredData(lldb::SBStructuredData &data, - const char *key) { - lldb::SBStructuredData keyValue = data.GetValueForKey(key); - if (!keyValue) - return std::string(); - - const size_t length = keyValue.GetStringValue(nullptr, 0); - - if (length == 0) - return std::string(); - - std::string str(length + 1, 0); - keyValue.GetStringValue(&str[0], length + 1); - return str; -} - -static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data, - const char *key) { - lldb::SBStructuredData keyValue = data.GetValueForKey(key); - - if (!keyValue.IsValid()) - return 0; - return keyValue.GetUnsignedIntegerValue(); -} - -void ProgressEventThreadFunction(DAP &dap) { - lldb::SBListener listener("lldb-dap.progress.listener"); - dap.debugger.GetBroadcaster().AddListener( - listener, lldb::SBDebugger::eBroadcastBitProgress | - lldb::SBDebugger::eBroadcastBitExternalProgress); - dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); - lldb::SBEvent event; - bool done = false; - while (!done) { - if (listener.WaitForEvent(1, event)) { - const auto event_mask = event.GetType(); - if (event.BroadcasterMatchesRef(dap.broadcaster)) { - if (event_mask & eBroadcastBitStopProgressThread) { - done = true; - } - } else { - lldb::SBStructuredData data = - lldb::SBDebugger::GetProgressDataFromEvent(event); - - const uint64_t progress_id = - GetUintFromStructuredData(data, "progress_id"); - const uint64_t completed = GetUintFromStructuredData(data, "completed"); - const uint64_t total = GetUintFromStructuredData(data, "total"); - const std::string details = - GetStringFromStructuredData(data, "details"); - - 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 - // non-deterministic progress will be the completed event So there - // will be no need to update the detail. - const std::string message = - GetStringFromStructuredData(data, "message"); - dap.SendProgressEvent(progress_id, message.c_str(), completed, - total); - } 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. - const std::string title = - GetStringFromStructuredData(data, "title"); - dap.SendProgressEvent(progress_id, title.c_str(), completed, total); - if (!details.empty()) - dap.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. - dap.SendProgressEvent(progress_id, details.c_str(), completed, total); - } - } - } - } -} - -static llvm::StringRef GetModuleEventReason(uint32_t event_mask) { - if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) - return "new"; - if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) - return "removed"; - assert(event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged); - return "changed"; -} - -// All events from the debugger, target, process, thread and frames are -// received in this function that runs in its own thread. We are using a -// "FILE *" to output packets back to VS Code and they have mutexes in them -// them prevent multiple threads from writing simultaneously so no locking -// is required. -static void EventThreadFunction(DAP &dap) { - llvm::set_thread_name(dap.transport.GetClientName() + ".event_handler"); - lldb::SBEvent event; - lldb::SBListener listener = dap.debugger.GetListener(); - dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread); - dap.debugger.GetBroadcaster().AddListener(listener, eBroadcastBitError | - eBroadcastBitWarning); - bool done = false; - while (!done) { - if (listener.WaitForEvent(1, event)) { - const auto event_mask = event.GetType(); - if (lldb::SBProcess::EventIsProcessEvent(event)) { - lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); - if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { - auto state = lldb::SBProcess::GetStateFromEvent(event); - - DAP_LOG(dap.log, "State = {0}", state); - switch (state) { - case lldb::eStateConnected: - case lldb::eStateDetached: - case lldb::eStateInvalid: - case lldb::eStateUnloaded: - break; - case lldb::eStateAttaching: - case lldb::eStateCrashed: - case lldb::eStateLaunching: - case lldb::eStateStopped: - case lldb::eStateSuspended: - // Only report a stopped event if the process was not - // automatically restarted. - if (!lldb::SBProcess::GetRestartedFromEvent(event)) { - SendStdOutStdErr(dap, process); - SendThreadStoppedEvent(dap); - } - break; - case lldb::eStateRunning: - case lldb::eStateStepping: - dap.WillContinue(); - SendContinuedEvent(dap); - break; - case lldb::eStateExited: - lldb::SBStream stream; - process.GetStatus(stream); - dap.SendOutput(OutputType::Console, 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 - // we don't have to terminate the session. - if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || - process.GetProcessID() == dap.restarting_process_id) { - dap.restarting_process_id = LLDB_INVALID_PROCESS_ID; - } else { - // Run any exit LLDB commands the user specified in the - // launch.json - dap.RunExitCommands(); - SendProcessExitedEvent(dap, process); - dap.SendTerminatedEvent(); - done = true; - } - break; - } - } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || - (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { - SendStdOutStdErr(dap, process); - } - } else if (lldb::SBTarget::EventIsTargetEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || - event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { - llvm::StringRef reason = GetModuleEventReason(event_mask); - const uint32_t num_modules = SBTarget::GetNumModulesFromEvent(event); - for (uint32_t i = 0; i < num_modules; ++i) { - lldb::SBModule module = - SBTarget::GetModuleAtIndexFromEvent(i, event); - if (!module.IsValid()) - continue; - - llvm::json::Object body; - body.try_emplace("reason", reason); - body.try_emplace("module", CreateModule(dap.target, module)); - llvm::json::Object module_event = CreateEventObject("module"); - module_event.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(module_event))); - } - } - } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { - auto event_type = - lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - auto bp = Breakpoint( - dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); - // If the breakpoint was set through DAP, it will have the - // BreakpointBase::kDAPBreakpointLabel. Regardless of whether - // locations were added, removed, or resolved, the breakpoint isn't - // going away and the reason is always "changed". - if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || - event_type & lldb::eBreakpointEventTypeLocationsRemoved || - event_type & lldb::eBreakpointEventTypeLocationsResolved) && - bp.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { - // As the DAP client already knows the path of this breakpoint, we - // don't need to send it back as part of the "changed" event. This - // avoids sending paths that should be source mapped. Note that - // CreateBreakpoint doesn't apply source mapping and certain - // implementation ignore the source part of this event anyway. - llvm::json::Value source_bp = CreateBreakpoint(&bp); - source_bp.getAsObject()->erase("source"); - - llvm::json::Object body; - body.try_emplace("breakpoint", source_bp); - body.try_emplace("reason", "changed"); - - llvm::json::Object bp_event = CreateEventObject("breakpoint"); - bp_event.try_emplace("body", std::move(body)); - - dap.SendJSON(llvm::json::Value(std::move(bp_event))); - } - } - } else if (event_mask & eBroadcastBitError || - event_mask & eBroadcastBitWarning) { - SBStructuredData data = SBDebugger::GetDiagnosticFromEvent(event); - if (!data.IsValid()) - continue; - std::string type = GetStringValue(data.GetValueForKey("type")); - std::string message = GetStringValue(data.GetValueForKey("message")); - dap.SendOutput(OutputType::Important, - llvm::formatv("{0}: {1}", type, message).str()); - } else if (event.BroadcasterMatchesRef(dap.broadcaster)) { - if (event_mask & eBroadcastBitStopEventThread) { - done = true; - } - } - } - } -} - /// Initialize request; value of command field is 'initialize'. llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( const InitializeRequestArguments &arguments) const { @@ -314,12 +70,11 @@ llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( "Sends an DAP event to the client."); if (arguments.supportedFeatures.contains(eClientFeatureProgressReporting)) - dap.progress_event_thread = - std::thread(ProgressEventThreadFunction, std::ref(dap)); + dap.StartProgressEventThread(); // Start our event thread so we can receive events from the debugger, target, // process and more. - dap.event_thread = std::thread(EventThreadFunction, std::ref(dap)); + dap.StartEventThread(); return dap.GetCapabilities(); } @@ -327,5 +82,3 @@ llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( void InitializeRequestHandler::PostRun() const { dap.SendJSON(CreateEventObject("initialized")); } - -} // namespace lldb_dap _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits