[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);
----------------
ashgti wrote:
I'm not seeing where we ignore this if the dap_instance isn't at some point, like checking `dap.configuration_done`
https://github.com/llvm/llvm-project/pull/163653
    
    
More information about the lldb-commits
mailing list