Author: Jonas Devlieghere Date: 2025-05-09T23:34:05-07:00 New Revision: b7c449ac0b0c4ccbe99937052c9428960cea7664
URL: https://github.com/llvm/llvm-project/commit/b7c449ac0b0c4ccbe99937052c9428960cea7664 DIFF: https://github.com/llvm/llvm-project/commit/b7c449ac0b0c4ccbe99937052c9428960cea7664.diff LOG: [lldb-dap] Don't emit a removed module event for unseen modules (#139324) Added: lldb/test/API/tools/lldb-dap/module-event/Makefile lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py lldb/test/API/tools/lldb-dap/module-event/main.cpp lldb/test/API/tools/lldb-dap/module-event/other.c Modified: lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py lldb/test/API/tools/lldb-dap/module/TestDAP_module.py lldb/tools/lldb-dap/DAP.cpp lldb/tools/lldb-dap/DAP.h lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp lldb/tools/lldb-dap/JSONUtils.cpp lldb/tools/lldb-dap/JSONUtils.h Removed: ################################################################################ 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..c216b5d92823c --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/module-event/TestDAP_module_event.py @@ -0,0 +1,54 @@ +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 80c150018f0b3..9a3cc32d8c324 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -106,16 +106,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()) @@ -1571,18 +1561,41 @@ 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); + 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()) + continue; + + llvm::StringRef reason; + bool id_only = false; + 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 ecde222c9263c..cbda57d27e8d9 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 e184e7602ef14..bd2e6630a126e 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -395,13 +395,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 7c7cf620f0c06..9c4dd0584bd21 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -206,10 +206,15 @@ void FillResponse(const llvm::json::Object &request, /// \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 /// _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits