llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

On macOS, lldb-dap is sending module events with reason "removed" for modules 
that we never told the client about in the first place. This is because when we 
 create a target with a binary, by default we load all of its dependent 
libraries too. When we start executing and see the that the binary and dyld are 
loaded, we throw away the image list and let dyld tell us about the correct 
binaries to load.

This PR addresses the issue by keeping track which modules the DAP client knows 
about. Clients can find out about modules either through the modules request, 
or through module events. Only when we have told a client about a module, we 
send module changed or module removed events.

This PR also reduces the amount of data sent for removed module events. The DAP 
specification [1] says that for removed module events, only the ID is used. It 
also makes the testing more robust.

Fixes #<!-- -->139323

[1] 
https://microsoft.github.io/debug-adapter-protocol/specification#Events_Module

---
Full diff: https://github.com/llvm/llvm-project/pull/139324.diff


11 Files Affected:

- (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py 
(-6) 
- (added) lldb/test/API/tools/lldb-dap/module-event/Makefile (+12) 
- (added) lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py 
(+55) 
- (added) lldb/test/API/tools/lldb-dap/module-event/main.cpp (+22) 
- (added) lldb/test/API/tools/lldb-dap/module-event/other.c (+5) 
- (modified) lldb/test/API/tools/lldb-dap/module/TestDAP_module.py (+6-4) 
- (modified) lldb/tools/lldb-dap/DAP.cpp (+27-12) 
- (modified) lldb/tools/lldb-dap/DAP.h (+8) 
- (modified) lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp (+14-3) 
- (modified) lldb/tools/lldb-dap/JSONUtils.cpp (+6-1) 
- (modified) lldb/tools/lldb-dap/JSONUtils.h (+6-1) 


``````````diff
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 e10342b72f4f0..c974866306d2a 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
@@ -134,7 +134,6 @@ def __init__(self, recv, send, init_commands, 
log_file=None):
         self.thread_stop_reasons = {}
         self.progress_events = []
         self.reverse_requests = []
-        self.module_events = []
         self.sequence = 1
         self.threads = None
         self.recv_thread.start()
@@ -248,11 +247,6 @@ def handle_recv_packet(self, packet):
                 # and 'progressEnd' events. Keep these around in case test
                 # cases want to verify them.
                 self.progress_events.append(packet)
-            elif event == "module":
-                # Module events indicate that some information about a module 
has changed.
-                self.module_events.append(packet)
-                # no need to add 'module' event packets to our packets list
-                return keepGoing
 
         elif packet_type == "response":
             if packet["command"] == "disconnect":
diff --git a/lldb/test/API/tools/lldb-dap/module-event/Makefile 
b/lldb/test/API/tools/lldb-dap/module-event/Makefile
new file mode 100644
index 0000000000000..99d79b8053878
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/module-event/Makefile
@@ -0,0 +1,12 @@
+CXX_SOURCES := main.cpp
+LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
+USE_LIBDL :=1
+
+a.out: libother
+
+include Makefile.rules
+
+# The following shared library will be used to test breakpoints under dynamic 
loading
+libother:  other.c
+       "$(MAKE)" -f $(MAKEFILE_RULES) \
+               DYLIB_ONLY=YES DYLIB_C_SOURCES=other.c DYLIB_NAME=other
diff --git a/lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py 
b/lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py
new file mode 100644
index 0000000000000..819640e5598bd
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py
@@ -0,0 +1,55 @@
+import dap_server
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbdap_testcase
+import re
+
+
+class TestDAP_module_event(lldbdap_testcase.DAPTestCaseBase):
+
+    def test_module_event(self):
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program, stopOnEntry=True)
+
+        source = "main.cpp"
+        breakpoint1_line = line_number(source, "// breakpoint 1")
+        breakpoint2_line = line_number(source, "// breakpoint 2")
+        breakpoint3_line = line_number(source, "// breakpoint 3")
+
+        breakpoint_ids = self.set_source_breakpoints(
+            source, [breakpoint1_line, breakpoint2_line, breakpoint3_line]
+        )
+        self.continue_to_breakpoints(breakpoint_ids)
+
+        # We're now stopped at breakpoint 1 before the dlopen. Flush all the 
module events.
+        event = self.dap_server.wait_for_event("module", 0.25)
+        while event is not None:
+            event = self.dap_server.wait_for_event("module", 0.25)
+
+        # Continue to the second breakpoint, before the dlclose.
+        self.continue_to_breakpoints(breakpoint_ids)
+
+        # Make sure we got a module event for libother.
+        event = self.dap_server.wait_for_event("module", 5)
+        self.assertTrue(event, "didn't get a module event")
+        module_name = event["body"]["module"]["name"]
+        module_id = event["body"]["module"]["id"]
+        self.assertEqual(event["body"]["reason"], "new")
+        self.assertIn("libother", module_name)
+
+        # Continue to the third breakpoint, after the dlclose.
+        self.continue_to_breakpoints(breakpoint_ids)
+
+        # Make sure we got a module event for libother.
+        event = self.dap_server.wait_for_event("module", 5)
+        self.assertTrue(event, "didn't get a module event")
+        reason = event["body"]["reason"]
+        self.assertEqual(event["body"]["reason"], "removed")
+        self.assertEqual(event["body"]["module"]["id"], module_id)
+
+        # The removed module event should omit everything but the module id.
+        # Check that there's no module name in the event.
+        self.assertNotIn("name", event["body"]["module"])
+
+        self.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/module-event/main.cpp 
b/lldb/test/API/tools/lldb-dap/module-event/main.cpp
new file mode 100644
index 0000000000000..711471b9fadd0
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/module-event/main.cpp
@@ -0,0 +1,22 @@
+#include <dlfcn.h>
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+
+#if defined(__APPLE__)
+  const char *libother_name = "libother.dylib";
+#else
+  const char *libother_name = "libother.so";
+#endif
+
+  printf("before dlopen\n"); // breakpoint 1
+  void *handle = dlopen(libother_name, RTLD_NOW);
+  int (*foo)(int) = (int (*)(int))dlsym(handle, "foo");
+  foo(12);
+
+  printf("before dlclose\n"); // breakpoint 2
+  dlclose(handle);
+  printf("after dlclose\n"); // breakpoint 3
+
+  return 0; // breakpoint 1
+}
diff --git a/lldb/test/API/tools/lldb-dap/module-event/other.c 
b/lldb/test/API/tools/lldb-dap/module-event/other.c
new file mode 100644
index 0000000000000..dd164597269dc
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/module-event/other.c
@@ -0,0 +1,5 @@
+extern int foo(int x) {
+  int y = x + 42; // break other
+  int z = y + 42;
+  return z;
+}
diff --git a/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py 
b/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
index 210819cfdd732..3fc0f752ee39e 100644
--- a/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
+++ b/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
@@ -60,13 +60,15 @@ def checkSymbolsLoadedWithSize():
         # Collect all the module names we saw as events.
         module_new_names = []
         module_changed_names = []
-        for module_event in self.dap_server.module_events:
-            module_name = module_event["body"]["module"]["name"]
+        module_event = self.dap_server.wait_for_event("module", 1)
+        while module_event is not None:
             reason = module_event["body"]["reason"]
             if reason == "new":
-                module_new_names.append(module_name)
+                module_new_names.append(module_event["body"]["module"]["name"])
             elif reason == "changed":
-                module_changed_names.append(module_name)
+                
module_changed_names.append(module_event["body"]["module"]["name"])
+
+            module_event = self.dap_server.wait_for_event("module", 1)
 
         # Make sure we got an event for every active module.
         self.assertNotEqual(len(module_new_names), 0)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f6754b1f8d7a3..828d7d4873156 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -105,16 +105,6 @@ static uint64_t 
GetUintFromStructuredData(lldb::SBStructuredData &data,
   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())
@@ -1566,7 +1556,6 @@ void DAP::EventThread() {
             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) {
@@ -1574,10 +1563,36 @@ void DAP::EventThread() {
                 lldb::SBTarget::GetModuleAtIndexFromEvent(i, event);
             if (!module.IsValid())
               continue;
+            llvm::StringRef module_id = module.GetUUIDString();
+            if (module_id.empty())
+              continue;
+
+            llvm::StringRef reason;
+            bool id_only = false;
+            {
+              std::lock_guard<std::mutex> guard(modules_mutex);
+              if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) {
+                modules.insert(module_id);
+                reason = "new";
+              } else {
+                // If this is a module we've never told the client about, don't
+                // send an event.
+                if (!modules.contains(module_id))
+                  continue;
+
+                if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) 
{
+                  modules.erase(module_id);
+                  reason = "removed";
+                  id_only = true;
+                } else {
+                  reason = "changed";
+                }
+              }
+            }
 
             llvm::json::Object body;
             body.try_emplace("reason", reason);
-            body.try_emplace("module", CreateModule(target, module));
+            body.try_emplace("module", CreateModule(target, module, id_only));
             llvm::json::Object module_event = CreateEventObject("module");
             module_event.try_emplace("body", std::move(body));
             SendJSON(llvm::json::Value(std::move(module_event)));
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index afeda8d81efb0..75e4ab0e23616 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -39,6 +39,7 @@
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/Threading.h"
@@ -212,6 +213,13 @@ struct DAP {
   /// The initial thread list upon attaching.
   std::optional<llvm::json::Array> initial_thread_list;
 
+  /// Keep track of all the modules our client knows about: either through the
+  /// modules request or the module events.
+  /// @{
+  std::mutex modules_mutex;
+  llvm::StringSet<> modules;
+  /// @}
+
   /// Creates a new DAP sessions.
   ///
   /// \param[in] log
diff --git a/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp 
b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
index ed51d395768c4..d37f302b06958 100644
--- a/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
@@ -45,9 +45,20 @@ void ModulesRequestHandler::operator()(
   FillResponse(request, response);
 
   llvm::json::Array modules;
-  for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
-    lldb::SBModule module = dap.target.GetModuleAtIndex(i);
-    modules.emplace_back(CreateModule(dap.target, module));
+
+  {
+    std::lock_guard<std::mutex> guard(dap.modules_mutex);
+    for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
+      lldb::SBModule module = dap.target.GetModuleAtIndex(i);
+      if (!module.IsValid())
+        continue;
+
+      llvm::StringRef module_id = module.GetUUIDString();
+      if (!module_id.empty())
+        dap.modules.insert(module_id);
+
+      modules.emplace_back(CreateModule(dap.target, module));
+    }
   }
 
   llvm::json::Object body;
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp 
b/lldb/tools/lldb-dap/JSONUtils.cpp
index 4409cf5b27e5b..e647bd23582c4 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -460,13 +460,18 @@ static std::string ConvertDebugInfoSizeToString(uint64_t 
debug_info) {
   return oss.str();
 }
 
-llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module) 
{
+llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
+                               bool id_only) {
   llvm::json::Object object;
   if (!target.IsValid() || !module.IsValid())
     return llvm::json::Value(std::move(object));
 
   const char *uuid = module.GetUUIDString();
   object.try_emplace("id", uuid ? std::string(uuid) : std::string(""));
+
+  if (id_only)
+    return llvm::json::Value(std::move(object));
+
   object.try_emplace("name", std::string(module.GetFileSpec().GetFilename()));
   char module_path_arr[PATH_MAX];
   module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr));
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index d0e20729f4ed9..a5baf61c90f60 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -267,10 +267,15 @@ CreateBreakpoint(BreakpointBase *bp,
 /// \param[in] module
 ///     A LLDB module object to convert into a JSON value
 ///
+/// \param[in] id_only
+///     Only include the module ID in the JSON value. This is used when sending
+///     a "removed" module event.
+///
 /// \return
 ///     A "Module" JSON object with that follows the formal JSON
 ///     definition outlined by Microsoft.
-llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module);
+llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
+                               bool id_only = false);
 
 /// Create a "Event" JSON object using \a event_name as the event name
 ///

``````````

</details>


https://github.com/llvm/llvm-project/pull/139324
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to