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