Skip to content

Commit cdf44ea

Browse files
Merge pull request #10669 from swiftlang/lldb-dap-modules-event-backport
[lldb-dap] Don't emit a removed module event for unseen modules
2 parents c467511 + fbdc243 commit cdf44ea

File tree

10 files changed

+159
-27
lines changed

10 files changed

+159
-27
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ def __init__(self, recv, send, init_commands, log_file=None):
127127
self.breakpoint_events = []
128128
self.progress_events = []
129129
self.reverse_requests = []
130-
self.module_events = []
131130
self.sequence = 1
132131
self.threads = None
133132
self.recv_thread.start()
@@ -248,11 +247,6 @@ def handle_recv_packet(self, packet):
248247
# and 'progressEnd' events. Keep these around in case test
249248
# cases want to verify them.
250249
self.progress_events.append(packet)
251-
elif event == "module":
252-
# Module events indicate that some information about a module has changed.
253-
self.module_events.append(packet)
254-
# no need to add 'module' event packets to our packets list
255-
return keepGoing
256250

257251
elif packet_type == "response":
258252
if packet["command"] == "disconnect":
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CXX_SOURCES := main.cpp
2+
LD_EXTRAS := -Wl,-rpath "-Wl,$(shell pwd)"
3+
USE_LIBDL :=1
4+
5+
a.out: libother
6+
7+
include Makefile.rules
8+
9+
# The following shared library will be used to test breakpoints under dynamic loading
10+
libother: other.c
11+
"$(MAKE)" -f $(MAKEFILE_RULES) \
12+
DYLIB_ONLY=YES DYLIB_C_SOURCES=other.c DYLIB_NAME=other
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import dap_server
2+
from lldbsuite.test.decorators import *
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test import lldbutil
5+
import lldbdap_testcase
6+
import re
7+
8+
9+
class TestDAP_module_event(lldbdap_testcase.DAPTestCaseBase):
10+
@skipIfWindows
11+
def test_module_event(self):
12+
program = self.getBuildArtifact("a.out")
13+
self.build_and_launch(program)
14+
15+
source = "main.cpp"
16+
breakpoint1_line = line_number(source, "// breakpoint 1")
17+
breakpoint2_line = line_number(source, "// breakpoint 2")
18+
breakpoint3_line = line_number(source, "// breakpoint 3")
19+
20+
breakpoint_ids = self.set_source_breakpoints(
21+
source, [breakpoint1_line, breakpoint2_line, breakpoint3_line]
22+
)
23+
self.continue_to_breakpoints(breakpoint_ids)
24+
25+
# We're now stopped at breakpoint 1 before the dlopen. Flush all the module events.
26+
event = self.dap_server.wait_for_event("module", 0.25)
27+
while event is not None:
28+
event = self.dap_server.wait_for_event("module", 0.25)
29+
30+
# Continue to the second breakpoint, before the dlclose.
31+
self.continue_to_breakpoints(breakpoint_ids)
32+
33+
# Make sure we got a module event for libother.
34+
event = self.dap_server.wait_for_event("module", 5)
35+
self.assertTrue(event, "didn't get a module event")
36+
module_name = event["body"]["module"]["name"]
37+
module_id = event["body"]["module"]["id"]
38+
self.assertEqual(event["body"]["reason"], "new")
39+
self.assertIn("libother", module_name)
40+
41+
# Continue to the third breakpoint, after the dlclose.
42+
self.continue_to_breakpoints(breakpoint_ids)
43+
44+
# Make sure we got a module event for libother.
45+
event = self.dap_server.wait_for_event("module", 5)
46+
self.assertTrue(event, "didn't get a module event")
47+
reason = event["body"]["reason"]
48+
self.assertEqual(event["body"]["reason"], "removed")
49+
self.assertEqual(event["body"]["module"]["id"], module_id)
50+
51+
# The removed module event should omit everything but the module id.
52+
# Check that there's no module name in the event.
53+
self.assertNotIn("name", event["body"]["module"])
54+
55+
self.continue_to_exit()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <dlfcn.h>
2+
#include <stdio.h>
3+
4+
int main(int argc, char const *argv[]) {
5+
6+
#if defined(__APPLE__)
7+
const char *libother_name = "libother.dylib";
8+
#else
9+
const char *libother_name = "libother.so";
10+
#endif
11+
12+
printf("before dlopen\n"); // breakpoint 1
13+
void *handle = dlopen(libother_name, RTLD_NOW);
14+
int (*foo)(int) = (int (*)(int))dlsym(handle, "foo");
15+
foo(12);
16+
17+
printf("before dlclose\n"); // breakpoint 2
18+
dlclose(handle);
19+
printf("after dlclose\n"); // breakpoint 3
20+
21+
return 0; // breakpoint 1
22+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extern int foo(int x) {
2+
int y = x + 42; // break other
3+
int z = y + 42;
4+
return z;
5+
}

lldb/test/API/tools/lldb-dap/module/TestDAP_module.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,15 @@ def checkSymbolsLoadedWithSize():
6060
# Collect all the module names we saw as events.
6161
module_new_names = []
6262
module_changed_names = []
63-
for module_event in self.dap_server.module_events:
64-
module_name = module_event["body"]["module"]["name"]
63+
module_event = self.dap_server.wait_for_event("module", 1)
64+
while module_event is not None:
6565
reason = module_event["body"]["reason"]
6666
if reason == "new":
67-
module_new_names.append(module_name)
67+
module_new_names.append(module_event["body"]["module"]["name"])
6868
elif reason == "changed":
69-
module_changed_names.append(module_name)
69+
module_changed_names.append(module_event["body"]["module"]["name"])
70+
71+
module_event = self.dap_server.wait_for_event("module", 1)
7072

7173
# Make sure we got an event for every active module.
7274
self.assertNotEqual(len(module_new_names), 0)

lldb/tools/lldb-dap/DAP.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "llvm/ADT/DenseSet.h"
3535
#include "llvm/ADT/StringMap.h"
3636
#include "llvm/ADT/StringRef.h"
37+
#include "llvm/ADT/StringSet.h"
3738
#include "llvm/Support/Error.h"
3839
#include "llvm/Support/JSON.h"
3940
#include "llvm/Support/Threading.h"
@@ -205,6 +206,13 @@ struct DAP {
205206
// will contain that expression.
206207
std::string last_nonempty_var_expression;
207208

209+
/// Keep track of all the modules our client knows about: either through the
210+
/// modules request or the module events.
211+
/// @{
212+
std::mutex modules_mutex;
213+
llvm::StringSet<> modules;
214+
/// @}
215+
208216
DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
209217
StreamDescriptor input, StreamDescriptor output);
210218
~DAP();

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,18 @@ static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) {
477477
return oss.str();
478478
}
479479

480-
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module) {
480+
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
481+
bool id_only) {
481482
llvm::json::Object object;
482483
if (!target.IsValid() || !module.IsValid())
483484
return llvm::json::Value(std::move(object));
484485

485486
const char *uuid = module.GetUUIDString();
486487
object.try_emplace("id", uuid ? std::string(uuid) : std::string(""));
488+
489+
if (id_only)
490+
return llvm::json::Value(std::move(object));
491+
487492
object.try_emplace("name", std::string(module.GetFileSpec().GetFilename()));
488493
char module_path_arr[PATH_MAX];
489494
module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr));

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,15 @@ CreateBreakpoint(BreakpointBase *bp,
274274
/// \param[in] module
275275
/// A LLDB module object to convert into a JSON value
276276
///
277+
/// \param[in] id_only
278+
/// Only include the module ID in the JSON value. This is used when sending
279+
/// a "removed" module event.
280+
///
277281
/// \return
278282
/// A "Module" JSON object with that follows the formal JSON
279283
/// definition outlined by Microsoft.
280-
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module);
284+
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
285+
bool id_only = false);
281286

282287
/// Create a "Event" JSON object using \a event_name as the event name
283288
///

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -440,16 +440,6 @@ void ProgressEventThreadFunction(DAP &dap) {
440440
}
441441
}
442442

443-
static llvm::StringRef GetModuleEventReason(uint32_t event_mask) {
444-
if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded)
445-
return "new";
446-
if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded)
447-
return "removed";
448-
assert(event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded ||
449-
event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged);
450-
return "changed";
451-
}
452-
453443
// All events from the debugger, target, process, thread and frames are
454444
// received in this function that runs in its own thread. We are using a
455445
// "FILE *" to output packets back to VS Code and they have mutexes in them
@@ -536,18 +526,41 @@ void EventThreadFunction(DAP &dap) {
536526
event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded ||
537527
event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded ||
538528
event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) {
539-
llvm::StringRef reason = GetModuleEventReason(event_mask);
540529
const uint32_t num_modules =
541530
lldb::SBTarget::GetNumModulesFromEvent(event);
531+
std::lock_guard<std::mutex> guard(dap.modules_mutex);
542532
for (uint32_t i = 0; i < num_modules; ++i) {
543533
lldb::SBModule module =
544534
lldb::SBTarget::GetModuleAtIndexFromEvent(i, event);
545535
if (!module.IsValid())
546536
continue;
537+
llvm::StringRef module_id = module.GetUUIDString();
538+
if (module_id.empty())
539+
continue;
540+
541+
llvm::StringRef reason;
542+
bool id_only = false;
543+
if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) {
544+
dap.modules.insert(module_id);
545+
reason = "new";
546+
} else {
547+
// If this is a module we've never told the client about, don't
548+
// send an event.
549+
if (!dap.modules.contains(module_id))
550+
continue;
551+
552+
if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) {
553+
dap.modules.erase(module_id);
554+
reason = "removed";
555+
id_only = true;
556+
} else {
557+
reason = "changed";
558+
}
559+
}
547560

548561
llvm::json::Object body;
549562
body.try_emplace("reason", reason);
550-
body.try_emplace("module", CreateModule(dap.target, module));
563+
body.try_emplace("module", CreateModule(dap.target, module, id_only));
551564
llvm::json::Object module_event = CreateEventObject("module");
552565
module_event.try_emplace("body", std::move(body));
553566
dap.SendJSON(llvm::json::Value(std::move(module_event)));
@@ -2001,9 +2014,20 @@ void request_modules(DAP &dap, const llvm::json::Object &request) {
20012014
FillResponse(request, response);
20022015

20032016
llvm::json::Array modules;
2004-
for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
2005-
lldb::SBModule module = dap.target.GetModuleAtIndex(i);
2006-
modules.emplace_back(CreateModule(dap.target, module));
2017+
2018+
{
2019+
std::lock_guard<std::mutex> guard(dap.modules_mutex);
2020+
for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
2021+
lldb::SBModule module = dap.target.GetModuleAtIndex(i);
2022+
if (!module.IsValid())
2023+
continue;
2024+
2025+
llvm::StringRef module_id = module.GetUUIDString();
2026+
if (!module_id.empty())
2027+
dap.modules.insert(module_id);
2028+
2029+
modules.emplace_back(CreateModule(dap.target, module));
2030+
}
20072031
}
20082032

20092033
llvm::json::Object body;

0 commit comments

Comments
 (0)