[Lldb-commits] [lldb] [llvm] [lldb-dap] Add multi-session support with shared debugger instances (PR #163653)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Thu Oct 23 09:34:03 PDT 2025
================
@@ -295,4 +307,267 @@ void SendMemoryEvent(DAP &dap, lldb::SBValue variable) {
dap.Send(protocol::Event{"memory", std::move(body)});
}
+// Note: EventThread() is architecturally different from the other functions in
+// this file. While the functions above are event helpers that operate on a
+// single DAP instance (taking `DAP &dap` as a parameter), EventThread() is a
+// shared event processing loop that:
+// 1. Listens to events from a shared debugger instance
+// 2. Uses DAPSessionManager::FindDAP() to find the appropriate DAP instance
+// for each event
+// 3. Dispatches events to 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
+// processes/targets are debugged in separate DAP sessions.
+//
+// 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 EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster,
+ llvm::StringRef client_name, Log *log) {
+ llvm::set_thread_name("lldb.DAP.client." + client_name + ".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);
+ // Find the DAP instance that owns this process's target.
+ DAP *dap_instance = DAPSessionManager::FindDAP(process.GetTarget());
+ if (!dap_instance) {
+ DAP_LOG(log, "Unable to find DAP instance for process {0}",
+ process.GetProcessID());
+ continue;
+ }
+
+ 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(*dap_instance, process);
+ if (llvm::Error err = SendThreadStoppedEvent(*dap_instance))
+ DAP_LOG_ERROR(dap_instance->log, std::move(err),
+ "({1}) reporting thread stopped: {0}",
+ dap_instance->GetClientName());
+ }
+ break;
+ case lldb::eStateRunning:
+ case lldb::eStateStepping:
+ dap_instance->WillContinue();
+ SendContinuedEvent(*dap_instance);
+ break;
+ case lldb::eStateExited:
+ lldb::SBStream stream;
+ process.GetStatus(stream);
+ dap_instance->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_instance->restarting_process_id) {
+ dap_instance->restarting_process_id = LLDB_INVALID_PROCESS_ID;
+ } else {
+ // Run any exit LLDB commands the user specified in the
+ // launch.json
+ dap_instance->RunExitCommands();
+ SendProcessExitedEvent(*dap_instance, process);
+ dap_instance->SendTerminatedEvent();
+ done = true;
+ }
+ break;
+ }
+ } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) ||
+ (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) {
+ SendStdOutStdErr(*dap_instance, 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) {
+ lldb::SBTarget event_target =
+ lldb::SBTarget::GetTargetFromEvent(event);
+ // Find the DAP instance that owns this target.
+ DAP *dap_instance = DAPSessionManager::FindDAP(event_target);
+ if (!dap_instance)
+ continue;
+
+ const uint32_t num_modules =
+ lldb::SBTarget::GetNumModulesFromEvent(event);
+ const bool remove_module =
+ event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded;
+
+ std::lock_guard<std::mutex> guard(dap_instance->modules_mutex);
+ for (uint32_t i = 0; i < num_modules; ++i) {
+ lldb::SBModule module =
+ lldb::SBTarget::GetModuleAtIndexFromEvent(i, event);
+
+ std::optional<protocol::Module> p_module =
+ CreateModule(dap_instance->target, module, remove_module);
+ if (!p_module)
+ continue;
+
+ llvm::StringRef module_id = p_module->id;
+
+ const bool module_exists =
+ dap_instance->modules.contains(module_id);
+ if (remove_module && module_exists) {
+ dap_instance->modules.erase(module_id);
+ dap_instance->Send(protocol::Event{
+ "module", protocol::ModuleEventBody{
+ std::move(p_module).value(),
+ protocol::ModuleEventBody::eReasonRemoved}});
+ } else if (module_exists) {
+ dap_instance->Send(protocol::Event{
+ "module", protocol::ModuleEventBody{
+ std::move(p_module).value(),
+ protocol::ModuleEventBody::eReasonChanged}});
+ } else if (!remove_module) {
+ dap_instance->modules.insert(module_id);
+ dap_instance->Send(protocol::Event{
+ "module", protocol::ModuleEventBody{
+ std::move(p_module).value(),
+ protocol::ModuleEventBody::eReasonNew}});
+ }
+ }
+ } else if (event_mask & lldb::SBTarget::eBroadcastBitNewTargetCreated) {
+ auto target = lldb::SBTarget::GetTargetFromEvent(event);
+
+ // Find the DAP instance that owns this target to check if we should
+ // ignore this event.
+ DAP *dap_instance = DAPSessionManager::FindDAP(target);
+
+ // Get the target and debugger IDs for the new session to use.
+ lldb::user_id_t target_id = target.GetGloballyUniqueID();
+ lldb::SBDebugger target_debugger = target.GetDebugger();
+ int debugger_id = target_debugger.GetID();
+
+ // We create an attach config that contains the debugger ID and target
+ // ID. The new DAP instance will use these IDs to find the existing
+ // debugger and target via FindDebuggerWithID and
+ // FindTargetByGloballyUniqueID.
+ llvm::json::Object attach_config;
----------------
ashgti wrote:
You should be able to use `lldb_dap::protocol::AttachRequestArguments` for this type.
https://github.com/llvm/llvm-project/pull/163653
More information about the lldb-commits
mailing list