https://github.com/da-viper updated 
https://github.com/llvm/llvm-project/pull/146966

>From cc0846ca1aa8e302806154b3576698aa1c97dd0d Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimy...@gmail.com>
Date: Tue, 8 Jul 2025 13:57:57 +0100
Subject: [PATCH] [lldb-dap] Use protocol-types for modules request

---
 .../test/tools/lldb-dap/dap_server.py         |  14 ++-
 .../module-event/TestDAP_module_event.py      |  19 ++--
 .../tools/lldb-dap/module/TestDAP_module.py   |  15 ++-
 lldb/tools/lldb-dap/DAP.cpp                   |  47 ++++----
 .../Handler/ModulesRequestHandler.cpp         |  76 +++++--------
 lldb/tools/lldb-dap/Handler/RequestHandler.h  |   9 +-
 lldb/tools/lldb-dap/JSONUtils.cpp             |  95 ----------------
 lldb/tools/lldb-dap/JSONUtils.h               |  18 ----
 .../lldb-dap/Protocol/ProtocolEvents.cpp      |  16 +++
 lldb/tools/lldb-dap/Protocol/ProtocolEvents.h |  15 +++
 .../lldb-dap/Protocol/ProtocolRequests.cpp    |  14 +++
 .../lldb-dap/Protocol/ProtocolRequests.h      |  21 ++++
 .../tools/lldb-dap/Protocol/ProtocolTypes.cpp |  27 +++++
 lldb/tools/lldb-dap/Protocol/ProtocolTypes.h  |  46 ++++++++
 lldb/tools/lldb-dap/ProtocolUtils.cpp         | 102 ++++++++++++++++++
 lldb/tools/lldb-dap/ProtocolUtils.h           |  30 ++++++
 lldb/unittests/DAP/CMakeLists.txt             |   1 +
 lldb/unittests/DAP/JSONUtilsTest.cpp          |  11 --
 lldb/unittests/DAP/ProtocolTypesTest.cpp      |  91 ++++++++++++++++
 lldb/unittests/DAP/ProtocolUtilsTest.cpp      |  24 +++++
 20 files changed, 476 insertions(+), 215 deletions(-)
 create mode 100644 lldb/unittests/DAP/ProtocolUtilsTest.cpp

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 0fe36cd4bc71f..d227a66a703c1 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
@@ -199,8 +199,8 @@ def _read_packet_thread(self):
         finally:
             dump_dap_log(self.log_file)
 
-    def get_modules(self):
-        module_list = self.request_modules()["body"]["modules"]
+    def get_modules(self, startModule: int = 0, moduleCount: int = 0):
+        module_list = self.request_modules(startModule, 
moduleCount)["body"]["modules"]
         modules = {}
         for module in module_list:
             modules[module["name"]] = module
@@ -1143,8 +1143,14 @@ def request_completions(self, text, frameId=None):
         }
         return self.send_recv(command_dict)
 
-    def request_modules(self):
-        return self.send_recv({"command": "modules", "type": "request"})
+    def request_modules(self, startModule: int, moduleCount: int):
+        return self.send_recv(
+            {
+                "command": "modules",
+                "type": "request",
+                "arguments": {"startModule": startModule, "moduleCount": 
moduleCount},
+            }
+        )
 
     def request_stackTrace(
         self, threadId=None, startFrame=None, levels=None, format=None, 
dump=False
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
index 1ef2f2a8235a4..64ed4154b035d 100644
--- 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
@@ -32,7 +32,7 @@ def test_module_event(self):
 
         # 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")
+        self.assertIsNotNone(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")
@@ -43,13 +43,20 @@ def test_module_event(self):
 
         # 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")
+        self.assertIsNotNone(event, "didn't get a module event")
         reason = event["body"]["reason"]
-        self.assertEqual(event["body"]["reason"], "removed")
+        self.assertEqual(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"])
+        # The removed module event should omit everything but the module id 
and name
+        # as they are required fields.
+        module_data = event["body"]["module"]
+        required_keys = ["id", "name"]
+        self.assertListEqual(list(module_data.keys()), required_keys)
+        self.assertEqual(module_data["name"], "", "expects empty name.")
+
+        # Make sure we do not send another event
+        event = self.dap_server.wait_for_event("module", 3)
+        self.assertIsNone(event, "expects no events.")
 
         self.continue_to_exit()
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 4fc221668a8ee..c9091df64f487 100644
--- a/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
+++ b/lldb/test/API/tools/lldb-dap/module/TestDAP_module.py
@@ -45,16 +45,20 @@ def run_test(self, symbol_basename, expect_debug_info_size):
             context="repl",
         )
 
-        def checkSymbolsLoadedWithSize():
+        def check_symbols_loaded_with_size():
             active_modules = self.dap_server.get_modules()
             program_module = active_modules[program_basename]
             self.assertIn("symbolFilePath", program_module)
             self.assertIn(symbols_path, program_module["symbolFilePath"])
-            symbol_regex = re.compile(r"[0-9]+(\.[0-9]*)?[KMG]?B")
-            return symbol_regex.match(program_module["symbolStatus"])
+            size_regex = re.compile(r"[0-9]+(\.[0-9]*)?[KMG]?B")
+            return size_regex.match(program_module["debugInfoSize"])
 
         if expect_debug_info_size:
-            self.waitUntil(checkSymbolsLoadedWithSize)
+            self.assertTrue(
+                self.waitUntil(check_symbols_loaded_with_size),
+                "expect has debug info size",
+            )
+
         active_modules = self.dap_server.get_modules()
         program_module = active_modules[program_basename]
         self.assertEqual(program_basename, program_module["name"])
@@ -83,6 +87,7 @@ def checkSymbolsLoadedWithSize():
         # symbols got added.
         self.assertNotEqual(len(module_changed_names), 0)
         self.assertIn(program_module["name"], module_changed_names)
+        self.continue_to_exit()
 
     @skipIfWindows
     def test_modules(self):
@@ -124,3 +129,5 @@ def test_compile_units(self):
         self.assertTrue(response["body"])
         cu_paths = [cu["compileUnitPath"] for cu in 
response["body"]["compileUnits"]]
         self.assertIn(main_source_path, cu_paths, "Real path to main.cpp 
matches")
+
+        self.continue_to_exit()
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a26beed491e98..60a32393a9b91 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -16,6 +16,7 @@
 #include "LLDBUtils.h"
 #include "OutputRedirector.h"
 #include "Protocol/ProtocolBase.h"
+#include "Protocol/ProtocolEvents.h"
 #include "Protocol/ProtocolRequests.h"
 #include "Protocol/ProtocolTypes.h"
 #include "ProtocolUtils.h"
@@ -1353,37 +1354,37 @@ void DAP::EventThread() {
             event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) {
           const uint32_t num_modules =
               lldb::SBTarget::GetNumModulesFromEvent(event);
+          const bool remove_module =
+              event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded;
+
           std::lock_guard<std::mutex> guard(modules_mutex);
           for (uint32_t i = 0; i < num_modules; ++i) {
             lldb::SBModule module =
                 lldb::SBTarget::GetModuleAtIndexFromEvent(i, event);
-            if (!module.IsValid())
-              continue;
-            llvm::StringRef module_id = module.GetUUIDString();
-            if (module_id.empty())
+
+            std::optional<protocol::Module> p_module =
+                CreateModule(target, module, remove_module);
+            if (!p_module)
               continue;
 
-            llvm::StringRef reason;
-            bool id_only = false;
-            if (modules.contains(module_id)) {
-              if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) {
-                modules.erase(module_id);
-                reason = "removed";
-                id_only = true;
-              } else {
-                reason = "changed";
-              }
-            } else {
+            llvm::StringRef module_id = p_module->id;
+
+            const bool module_exists = modules.contains(module_id);
+            if (remove_module && module_exists) {
+              modules.erase(module_id);
+              Send(protocol::Event{
+                  "module", ModuleEventBody{std::move(p_module).value(),
+                                            ModuleEventBody::eReasonRemoved}});
+            } else if (module_exists) {
+              Send(protocol::Event{
+                  "module", ModuleEventBody{std::move(p_module).value(),
+                                            ModuleEventBody::eReasonChanged}});
+            } else if (!remove_module) {
               modules.insert(module_id);
-              reason = "new";
+              Send(protocol::Event{
+                  "module", ModuleEventBody{std::move(p_module).value(),
+                                            ModuleEventBody::eReasonNew}});
             }
-
-            llvm::json::Object body;
-            body.try_emplace("reason", reason);
-            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)));
           }
         }
       } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) {
diff --git a/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp 
b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
index d37f302b06958..697166a2a1a92 100644
--- a/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp
@@ -7,64 +7,38 @@
 
//===----------------------------------------------------------------------===//
 
 #include "DAP.h"
-#include "EventHelper.h"
-#include "JSONUtils.h"
+#include "ProtocolUtils.h"
 #include "RequestHandler.h"
 
+using namespace lldb_dap::protocol;
 namespace lldb_dap {
 
-// "modulesRequest": {
-//   "allOf": [ { "$ref": "#/definitions/Request" }, {
-//     "type": "object",
-//     "description": "Modules request; value of command field is
-//                     'modules'.",
-//     "properties": {
-//       "command": {
-//         "type": "string",
-//         "enum": [ "modules" ]
-//       },
-//     },
-//     "required": [ "command" ]
-//   }]
-// },
-// "modulesResponse": {
-//   "allOf": [ { "$ref": "#/definitions/Response" }, {
-//     "type": "object",
-//     "description": "Response to 'modules' request.",
-//     "properties": {
-//       "body": {
-//         "description": "Response to 'modules' request. Array of
-//                         module objects."
-//       }
-//     }
-//   }]
-// }
-void ModulesRequestHandler::operator()(
-    const llvm::json::Object &request) const {
-  llvm::json::Object response;
-  FillResponse(request, response);
-
-  llvm::json::Array modules;
-
-  {
-    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));
+/// Modules can be retrieved from the debug adapter with this request which can
+/// either return all modules or a range of modules to support paging.
+///
+/// Clients should only call this request if the corresponding capability
+/// `supportsModulesRequest` is true.
+llvm::Expected<ModulesResponseBody>
+ModulesRequestHandler::Run(const std::optional<ModulesArguments> &args) const {
+  ModulesResponseBody response;
+
+  std::vector<Module> &modules = response.modules;
+  std::lock_guard<std::mutex> guard(dap.modules_mutex);
+  const uint32_t total_modules = dap.target.GetNumModules();
+  response.totalModules = total_modules;
+
+  modules.reserve(total_modules);
+  for (uint32_t i = 0; i < total_modules; i++) {
+    lldb::SBModule module = dap.target.GetModuleAtIndex(i);
+
+    std::optional<Module> result = CreateModule(dap.target, module);
+    if (result && !result->id.empty()) {
+      dap.modules.insert(result->id);
+      modules.emplace_back(std::move(result).value());
     }
   }
 
-  llvm::json::Object body;
-  body.try_emplace("modules", std::move(modules));
-  response.try_emplace("body", std::move(body));
-  dap.SendJSON(llvm::json::Value(std::move(response)));
+  return response;
 }
 
 } // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h 
b/lldb/tools/lldb-dap/Handler/RequestHandler.h
index e35b9830ab60f..3b910abe81992 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.h
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h
@@ -466,14 +466,17 @@ class CompileUnitsRequestHandler : public 
LegacyRequestHandler {
   void operator()(const llvm::json::Object &request) const override;
 };
 
-class ModulesRequestHandler : public LegacyRequestHandler {
+class ModulesRequestHandler final
+    : public RequestHandler<std::optional<protocol::ModulesArguments>,
+                            llvm::Expected<protocol::ModulesResponseBody>> {
 public:
-  using LegacyRequestHandler::LegacyRequestHandler;
+  using RequestHandler::RequestHandler;
   static llvm::StringLiteral GetCommand() { return "modules"; }
   FeatureSet GetSupportedFeatures() const override {
     return {protocol::eAdapterFeatureModulesRequest};
   }
-  void operator()(const llvm::json::Object &request) const override;
+  llvm::Expected<protocol::ModulesResponseBody>
+  Run(const std::optional<protocol::ModulesArguments> &args) const override;
 };
 
 class PauseRequestHandler : public LegacyRequestHandler {
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp 
b/lldb/tools/lldb-dap/JSONUtils.cpp
index e72d93ee34571..553c52605c998 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -341,101 +341,6 @@ llvm::json::Value CreateScope(const llvm::StringRef name,
   return llvm::json::Value(std::move(object));
 }
 
-static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) {
-  uint64_t debug_info_size = 0;
-  llvm::StringRef section_name(section.GetName());
-  if (section_name.starts_with(".debug") ||
-      section_name.starts_with("__debug") ||
-      section_name.starts_with(".apple") || 
section_name.starts_with("__apple"))
-    debug_info_size += section.GetFileByteSize();
-  size_t num_sub_sections = section.GetNumSubSections();
-  for (size_t i = 0; i < num_sub_sections; i++) {
-    debug_info_size +=
-        GetDebugInfoSizeInSection(section.GetSubSectionAtIndex(i));
-  }
-  return debug_info_size;
-}
-
-static uint64_t GetDebugInfoSize(lldb::SBModule module) {
-  uint64_t debug_info_size = 0;
-  size_t num_sections = module.GetNumSections();
-  for (size_t i = 0; i < num_sections; i++) {
-    debug_info_size += GetDebugInfoSizeInSection(module.GetSectionAtIndex(i));
-  }
-  return debug_info_size;
-}
-
-static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) {
-  std::ostringstream oss;
-  oss << std::fixed << std::setprecision(1);
-  if (debug_info < 1024) {
-    oss << debug_info << "B";
-  } else if (debug_info < 1024 * 1024) {
-    double kb = double(debug_info) / 1024.0;
-    oss << kb << "KB";
-  } else if (debug_info < 1024 * 1024 * 1024) {
-    double mb = double(debug_info) / (1024.0 * 1024.0);
-    oss << mb << "MB";
-  } else {
-    double gb = double(debug_info) / (1024.0 * 1024.0 * 1024.0);
-    oss << gb << "GB";
-  }
-  return oss.str();
-}
-
-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));
-  std::string module_path(module_path_arr);
-  object.try_emplace("path", module_path);
-  if (module.GetNumCompileUnits() > 0) {
-    std::string symbol_str = "Symbols loaded.";
-    std::string debug_info_size;
-    uint64_t debug_info = GetDebugInfoSize(module);
-    if (debug_info > 0) {
-      debug_info_size = ConvertDebugInfoSizeToString(debug_info);
-    }
-    object.try_emplace("symbolStatus", symbol_str);
-    object.try_emplace("debugInfoSize", debug_info_size);
-    char symbol_path_arr[PATH_MAX];
-    module.GetSymbolFileSpec().GetPath(symbol_path_arr,
-                                       sizeof(symbol_path_arr));
-    std::string symbol_path(symbol_path_arr);
-    object.try_emplace("symbolFilePath", symbol_path);
-  } else {
-    object.try_emplace("symbolStatus", "Symbols not found.");
-  }
-  std::string load_address =
-      llvm::formatv("{0:x}",
-                    module.GetObjectFileHeaderAddress().GetLoadAddress(target))
-          .str();
-  object.try_emplace("addressRange", load_address);
-  std::string version_str;
-  uint32_t version_nums[3];
-  uint32_t num_versions =
-      module.GetVersion(version_nums, sizeof(version_nums) / sizeof(uint32_t));
-  for (uint32_t i = 0; i < num_versions; ++i) {
-    if (!version_str.empty())
-      version_str += ".";
-    version_str += std::to_string(version_nums[i]);
-  }
-  if (!version_str.empty())
-    object.try_emplace("version", version_str);
-  return llvm::json::Value(std::move(object));
-}
-
 // "Event": {
 //   "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, {
 //     "type": "object",
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index fd9a06931ebff..0424438ad5b72 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -196,24 +196,6 @@ GetStringMap(const llvm::json::Object &obj, 
llvm::StringRef key);
 void FillResponse(const llvm::json::Object &request,
                   llvm::json::Object &response);
 
-/// Converts a LLDB module to a VS Code DAP module for use in "modules" events.
-///
-/// \param[in] target
-///     A LLDB target object to convert into a JSON value.
-///
-/// \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,
-                               bool id_only = false);
-
 /// Create a "Event" JSON object using \a event_name as the event name
 ///
 /// \param[in] event_name
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp 
b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
index ad6e305d09fed..4faf65567c3ea 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
@@ -17,4 +17,20 @@ json::Value toJSON(const CapabilitiesEventBody &CEB) {
   return json::Object{{"capabilities", CEB.capabilities}};
 }
 
+json::Value toJSON(const ModuleEventBody::Reason &MEBR) {
+  switch (MEBR) {
+  case ModuleEventBody::eReasonNew:
+    return "new";
+  case ModuleEventBody::eReasonChanged:
+    return "changed";
+  case ModuleEventBody::eReasonRemoved:
+    return "removed";
+  }
+  llvm_unreachable("unhandled module event reason!.");
+}
+
+json::Value toJSON(const ModuleEventBody &MEB) {
+  return json::Object{{"reason", MEB.reason}, {"module", MEB.module}};
+}
+
 } // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h 
b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
index 512106222362c..ee9e03c499eae 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.h
@@ -41,6 +41,21 @@ struct CapabilitiesEventBody {
 };
 llvm::json::Value toJSON(const CapabilitiesEventBody &);
 
+/// The event indicates that some information about a module has changed.
+struct ModuleEventBody {
+  enum Reason : unsigned { eReasonNew, eReasonChanged, eReasonRemoved };
+
+  /// The new, changed, or removed module. In case of `removed` only the module
+  /// id is used.
+  Module module;
+
+  /// The reason for the event.
+  /// Values: 'new', 'changed', 'removed'
+  Reason reason;
+};
+llvm::json::Value toJSON(const ModuleEventBody::Reason &);
+llvm::json::Value toJSON(const ModuleEventBody &);
+
 } // 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 9bd84a6c898f9..124dbace97dd6 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -517,4 +517,18 @@ json::Value toJSON(const ReadMemoryResponseBody &RMR) {
   return result;
 }
 
+bool fromJSON(const json::Value &Params, ModulesArguments &MA, json::Path P) {
+  json::ObjectMapper O(Params, P);
+  return O && O.mapOptional("startModule", MA.startModule) &&
+         O.mapOptional("moduleCount", MA.moduleCount);
+}
+
+json::Value toJSON(const ModulesResponseBody &MR) {
+  json::Object result{{"modules", MR.modules}};
+  if (MR.totalModules != 0)
+    result.insert({"totalModules", MR.totalModules});
+
+  return result;
+}
+
 } // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h 
b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index d4b816c72679b..26eb3cb396ad1 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -875,6 +875,27 @@ struct ReadMemoryResponseBody {
 };
 llvm::json::Value toJSON(const ReadMemoryResponseBody &);
 
+/// Arguments for `modules` request.
+struct ModulesArguments {
+  /// The index of the first module to return; if omitted modules start at 0.
+  uint32_t startModule = 0;
+
+  /// The number of modules to return. If `moduleCount` is not specified or 0,
+  /// all modules are returned.
+  uint32_t moduleCount = 0;
+};
+bool fromJSON(const llvm::json::Value &, ModulesArguments &, llvm::json::Path);
+
+/// Response to `modules` request.
+struct ModulesResponseBody {
+  /// All modules or range of modules.
+  std::vector<Module> modules;
+
+  /// The total number of modules available.
+  uint32_t totalModules = 0;
+};
+llvm::json::Value toJSON(const ModulesResponseBody &);
+
 } // namespace lldb_dap::protocol
 
 #endif
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp 
b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
index f3635202175a7..9b5c9ef348ca4 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp
@@ -8,6 +8,7 @@
 
 #include "Protocol/ProtocolTypes.h"
 #include "JSONUtils.h"
+#include "ProtocolUtils.h"
 #include "lldb/lldb-types.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
@@ -926,4 +927,30 @@ llvm::json::Value toJSON(const DisassembledInstruction 
&DI) {
   return result;
 }
 
+json::Value toJSON(const Module &M) {
+  json::Object result{{"id", M.id}, {"name", M.name}};
+
+  if (!M.path.empty())
+    result.insert({"path", M.path});
+  if (M.isOptimized)
+    result.insert({"isOptimized", M.isOptimized});
+  if (M.isUserCode)
+    result.insert({"isUserCode", M.isUserCode});
+  if (!M.version.empty())
+    result.insert({"version", M.version});
+  if (!M.symbolStatus.empty())
+    result.insert({"symbolStatus", M.symbolStatus});
+  if (!M.symbolFilePath.empty())
+    result.insert({"symbolFilePath", M.symbolFilePath});
+  if (!M.dateTimeStamp.empty())
+    result.insert({"dateTimeStamp", M.dateTimeStamp});
+  if (!M.addressRange.empty())
+    result.insert({"addressRange", M.addressRange});
+  if (M.debugInfoSizeBytes != 0)
+    result.insert(
+        {"debugInfoSize", ConvertDebugInfoSizeToString(M.debugInfoSizeBytes)});
+
+  return result;
+}
+
 } // namespace lldb_dap::protocol
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h 
b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
index 6adfe3b7211b1..2bb765e956256 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h
@@ -743,6 +743,52 @@ bool fromJSON(const llvm::json::Value &, 
DisassembledInstruction &,
               llvm::json::Path);
 llvm::json::Value toJSON(const DisassembledInstruction &);
 
+struct Module {
+  /// Unique identifier for the module.
+  std::string id;
+
+  /// A name of the module.
+  std::string name;
+
+  /// Logical full path to the module. The exact definition is implementation
+  /// defined, but usually this would be a full path to the on-disk file for 
the
+  /// module.
+  std::string path;
+
+  /// True if the module is optimized.
+  bool isOptimized = false;
+
+  /// True if the module is considered 'user code' by a debugger that supports
+  /// 'Just My Code'.
+  bool isUserCode = false;
+
+  /// Version of Module.
+  std::string version;
+
+  /// User-understandable description of if symbols were found for the module
+  /// (ex: 'Symbols Loaded', 'Symbols not found', etc.)
+  std::string symbolStatus;
+
+  /// Logical full path to the symbol file. The exact definition is
+  /// implementation defined.
+  std::string symbolFilePath;
+
+  /// Module created or modified, encoded as an RFC 3339 timestamp.
+  std::string dateTimeStamp;
+
+  /// Address range covered by this module.
+  std::string addressRange;
+
+  /// Custom fields
+  /// @{
+
+  /// Size of the debug_info sections in the module in bytes.
+  uint64_t debugInfoSizeBytes = 0;
+
+  //// @}
+};
+llvm::json::Value toJSON(const Module &);
+
 } // namespace lldb_dap::protocol
 
 #endif
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp 
b/lldb/tools/lldb-dap/ProtocolUtils.cpp
index 724d851107928..d13968c98a387 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.cpp
+++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp
@@ -16,6 +16,8 @@
 #include "lldb/API/SBTarget.h"
 #include "lldb/API/SBThread.h"
 #include "lldb/Host/PosixApi.h" // Adds PATH_MAX for windows
+
+#include <iomanip>
 #include <optional>
 
 using namespace lldb_dap::protocol;
@@ -46,6 +48,106 @@ static bool ShouldDisplayAssemblySource(
   return false;
 }
 
+static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) {
+  uint64_t debug_info_size = 0;
+  const llvm::StringRef section_name(section.GetName());
+  if (section_name.starts_with(".debug") ||
+      section_name.starts_with("__debug") ||
+      section_name.starts_with(".apple") || 
section_name.starts_with("__apple"))
+    debug_info_size += section.GetFileByteSize();
+
+  const size_t num_sub_sections = section.GetNumSubSections();
+  for (size_t i = 0; i < num_sub_sections; i++)
+    debug_info_size +=
+        GetDebugInfoSizeInSection(section.GetSubSectionAtIndex(i));
+
+  return debug_info_size;
+}
+
+static uint64_t GetDebugInfoSize(lldb::SBModule module) {
+  uint64_t debug_info_size = 0;
+  const size_t num_sections = module.GetNumSections();
+  for (size_t i = 0; i < num_sections; i++)
+    debug_info_size += GetDebugInfoSizeInSection(module.GetSectionAtIndex(i));
+
+  return debug_info_size;
+}
+
+std::string ConvertDebugInfoSizeToString(uint64_t debug_size) {
+  std::ostringstream oss;
+  oss << std::fixed << std::setprecision(1);
+  if (debug_size < 1024) {
+    oss << debug_size << "B";
+  } else if (debug_size < static_cast<uint64_t>(1024 * 1024)) {
+    double kb = double(debug_size) / 1024.0;
+    oss << kb << "KB";
+  } else if (debug_size < 1024 * 1024 * 1024) {
+    double mb = double(debug_size) / (1024.0 * 1024.0);
+    oss << mb << "MB";
+  } else {
+    double gb = double(debug_size) / (1024.0 * 1024.0 * 1024.0);
+    oss << gb << "GB";
+  }
+  return oss.str();
+}
+
+std::optional<protocol::Module> CreateModule(const lldb::SBTarget &target,
+                                             lldb::SBModule &module,
+                                             bool id_only) {
+  if (!target.IsValid() || !module.IsValid())
+    return std::nullopt;
+
+  const llvm::StringRef uuid = module.GetUUIDString();
+  if (uuid.empty())
+    return std::nullopt;
+
+  protocol::Module p_module;
+  p_module.id = uuid;
+
+  if (id_only)
+    return p_module;
+
+  std::array<char, PATH_MAX> path_buffer{};
+  if (const lldb::SBFileSpec file_spec = module.GetFileSpec()) {
+    p_module.name = file_spec.GetFilename();
+
+    const uint32_t path_size =
+        file_spec.GetPath(path_buffer.data(), path_buffer.size());
+    p_module.path = std::string(path_buffer.data(), path_size);
+  }
+
+  if (const uint32_t num_compile_units = module.GetNumCompileUnits();
+      num_compile_units > 0) {
+    p_module.symbolStatus = "Symbols loaded.";
+
+    p_module.debugInfoSizeBytes = GetDebugInfoSize(module);
+
+    if (const lldb::SBFileSpec symbol_fspec = module.GetSymbolFileSpec()) {
+      const uint32_t path_size =
+          symbol_fspec.GetPath(path_buffer.data(), path_buffer.size());
+      p_module.symbolFilePath = std::string(path_buffer.data(), path_size);
+    }
+  } else {
+    p_module.symbolStatus = "Symbols not found.";
+  }
+
+  const auto load_address = module.GetObjectFileHeaderAddress();
+  if (const lldb::addr_t raw_address = load_address.GetLoadAddress(target);
+      raw_address != LLDB_INVALID_ADDRESS)
+    p_module.addressRange = llvm::formatv("{0:x}", raw_address);
+
+  std::array<uint32_t, 3> version_nums{};
+  const uint32_t num_versions =
+      module.GetVersion(version_nums.data(), version_nums.size());
+  if (num_versions > 0) {
+    p_module.version = llvm::formatv(
+        "{:$[.]}", llvm::make_range(version_nums.begin(),
+                                    version_nums.begin() + num_versions));
+  }
+
+  return p_module;
+}
+
 std::optional<protocol::Source> CreateSource(const lldb::SBFileSpec &file) {
   if (!file.IsValid())
     return std::nullopt;
diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h 
b/lldb/tools/lldb-dap/ProtocolUtils.h
index f36bf0fb60a87..d906d8e881158 100644
--- a/lldb/tools/lldb-dap/ProtocolUtils.h
+++ b/lldb/tools/lldb-dap/ProtocolUtils.h
@@ -20,6 +20,26 @@
 
 namespace lldb_dap {
 
+/// Converts a LLDB module to a DAP protocol module for use in `module events 
or
+/// request.
+///
+/// \param[in] target
+///     The target that has the module
+///
+/// \param[in] module
+///     A LLDB module object to convert into a protocol module
+///
+/// \param[in] id_only
+///     Only initialize the module ID in the return type. This is used when
+///     sending a "removed" module event.
+///
+/// \return
+///     A `protocol::Module` that follows the formal Module
+///     definition outlined by the DAP protocol.
+std::optional<protocol::Module> CreateModule(const lldb::SBTarget &target,
+                                             lldb::SBModule &module,
+                                             bool id_only = false);
+
 /// Create a "Source" JSON object as described in the debug adapter definition.
 ///
 /// \param[in] file
@@ -76,6 +96,16 @@ std::vector<protocol::Thread> GetThreads(lldb::SBProcess 
process,
 protocol::ExceptionBreakpointsFilter
 CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp);
 
+/// Converts a size in bytes to a human-readable string format.
+///
+/// \param[in] debug_size
+///     Size of the debug information in bytes (uint64_t).
+///
+/// \return
+///     A string representing the size in a readable format (e.g., "1 KB",
+///     "2 MB").
+std::string ConvertDebugInfoSizeToString(uint64_t debug_size);
+
 } // namespace lldb_dap
 
 #endif
diff --git a/lldb/unittests/DAP/CMakeLists.txt 
b/lldb/unittests/DAP/CMakeLists.txt
index d5824f4b38a5e..156cd625546bd 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -7,6 +7,7 @@ add_lldb_unittest(DAPTests
   JSONUtilsTest.cpp
   LLDBUtilsTest.cpp
   ProtocolTypesTest.cpp
+  ProtocolUtilsTest.cpp
   TestBase.cpp
   VariablesTest.cpp
 
diff --git a/lldb/unittests/DAP/JSONUtilsTest.cpp 
b/lldb/unittests/DAP/JSONUtilsTest.cpp
index ce4be085965d5..876980eb4bf4a 100644
--- a/lldb/unittests/DAP/JSONUtilsTest.cpp
+++ b/lldb/unittests/DAP/JSONUtilsTest.cpp
@@ -139,17 +139,6 @@ TEST(JSONUtilsTest, GetInteger_DifferentTypes) {
   EXPECT_EQ(result.value(), static_cast<int16_t>(789));
 }
 
-TEST(JSONUtilsTest, CreateModule) {
-  SBTarget target;
-  SBModule module;
-
-  json::Value value = CreateModule(target, module);
-  json::Object *object = value.getAsObject();
-
-  ASSERT_NE(object, nullptr);
-  EXPECT_EQ(object->size(), 0UL);
-}
-
 TEST(JSONUtilsTest, GetStrings_EmptyArray) {
   llvm::json::Object obj;
   obj.try_emplace("key", llvm::json::Array());
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp 
b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index 085348ffc519d..b5cf06bd6f0b6 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -792,3 +792,94 @@ TEST(ProtocolTypesTest, ReadMemoryResponseBody) {
   ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
   EXPECT_EQ(pp(*expected), pp(response));
 }
+
+TEST(ProtocolTypesTest, Modules) {
+  Module module;
+  module.id = "AC805E8E-B6A4-CD92-4B05-5CFA7CE24AE8-8926C776";
+  module.name = "libm.so.6";
+  module.path = "/some/path/to/libm.so.6";
+  module.isOptimized = true;
+  module.isUserCode = true;
+  module.version = "0.0.1";
+  module.symbolStatus = "Symbol not found.";
+  module.symbolFilePath = "/some/file/path/to/the/symbol/module";
+  module.dateTimeStamp = "2020-12-09T16:09:53+00:00";
+  module.addressRange = "0xcafeface";
+  module.debugInfoSizeBytes = 1572864;
+
+  Expected<json::Value> expected = json::parse(
+      R"({
+                  "id" : "AC805E8E-B6A4-CD92-4B05-5CFA7CE24AE8-8926C776",
+                  "name": "libm.so.6",
+                  "path": "/some/path/to/libm.so.6",
+                  "isOptimized": true,
+                  "isUserCode": true,
+                  "version": "0.0.1",
+                  "symbolStatus": "Symbol not found.",
+                  "symbolFilePath": "/some/file/path/to/the/symbol/module",
+                  "dateTimeStamp": "2020-12-09T16:09:53+00:00",
+                  "addressRange": "0xcafeface",
+                  "debugInfoSize": "1.5MB" })");
+  ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
+  EXPECT_EQ(pp(*expected), pp(module));
+
+  // Test without optional values.
+  module.path.clear();
+  module.isOptimized = false;
+  module.isUserCode = false;
+  module.version.clear();
+  module.symbolStatus.clear();
+  module.symbolFilePath.clear();
+  module.dateTimeStamp.clear();
+  module.addressRange.clear();
+  module.debugInfoSizeBytes = 0;
+  EXPECT_NE(pp(*expected), pp(module));
+
+  Expected<json::Value> expected_no_opt = json::parse(
+      R"({
+                  "id" : "AC805E8E-B6A4-CD92-4B05-5CFA7CE24AE8-8926C776",
+                  "name": "libm.so.6"})");
+  ASSERT_THAT_EXPECTED(expected_no_opt, llvm::Succeeded());
+  EXPECT_EQ(pp(*expected_no_opt), pp(module));
+}
+
+TEST(ProtocolTypesTest, ModulesArguments) {
+  ModulesArguments args;
+
+  llvm::Expected<ModulesArguments> expected = parse<ModulesArguments>(R"({})");
+  ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
+  EXPECT_EQ(args.startModule, expected->startModule);
+  EXPECT_EQ(args.moduleCount, expected->moduleCount);
+
+  // Non Default values.
+  args.startModule = 1;
+  args.moduleCount = 2;
+  llvm::Expected<ModulesArguments> expected_no_default =
+      parse<ModulesArguments>(R"({ "startModule": 1, "moduleCount": 2})");
+  ASSERT_THAT_EXPECTED(expected_no_default, llvm::Succeeded());
+  EXPECT_EQ(args.startModule, expected_no_default->startModule);
+  EXPECT_EQ(args.moduleCount, expected_no_default->moduleCount);
+}
+
+TEST(ProtocolTypesTest, ModulesResponseBody) {
+  ModulesResponseBody response;
+  Module module1;
+  module1.id = "first id";
+  module1.name = "first name";
+
+  Module module2;
+  module2.id = "second id";
+  module2.name = "second name";
+  response.modules = {std::move(module1), std::move(module2)};
+  response.totalModules = 2;
+
+  Expected<json::Value> expected = json::parse(
+      R"({
+                  "modules": [
+                    { "id": "first id", "name": "first name"},
+                    { "id": "second id", "name": "second name"}
+                  ],
+                  "totalModules": 2 })");
+  ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
+  EXPECT_EQ(pp(*expected), pp(response));
+}
diff --git a/lldb/unittests/DAP/ProtocolUtilsTest.cpp 
b/lldb/unittests/DAP/ProtocolUtilsTest.cpp
new file mode 100644
index 0000000000000..62bd885af594e
--- /dev/null
+++ b/lldb/unittests/DAP/ProtocolUtilsTest.cpp
@@ -0,0 +1,24 @@
+//===-- ProtocolUtilsTest.cpp 
---------------------------------------------===//
+//
+// 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 "ProtocolUtils.h"
+#include "JSONUtils.h"
+#include "lldb/API/LLDB.h"
+#include "gtest/gtest.h"
+#include <optional>
+
+using namespace lldb;
+using namespace lldb_dap;
+
+TEST(ProtocolUtilsTest, CreateModule) {
+  SBTarget target;
+  SBModule module;
+
+  std::optional<protocol::Module> module_opt = CreateModule(target, module);
+  EXPECT_EQ(module_opt, std::nullopt);
+}

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to