Skip to content

Commit 018bb06

Browse files
committed
[lldb-dap] Don't emit a removed module event for unseen modules
On macOS, lldb-dap is sending module events with reason "removed" for modules that we never told the client about in the first place. This is because when we create a target with a binary, by default all of its dependent libraries are loaded too. When we start executing and see the that the binary and dyld are loaded, we throw away the image list and then as dyld tell us about the correct binaries being loaded in the process, we add them. This PR addresses the issue by keeping track which modules the DAP client knows about. Clients can find out about modules either through the modules request, or through module events. Only when we have told a client about a module, we send module changed or module removed events. This PR also reduces the amount of data sent for removed module events. The DAP specification [1] says that for removed module events, only the ID is used. Fixes #139323 [1] https://microsoft.github.io/debug-adapter-protocol/specification#Events_Module
1 parent bd0d048 commit 018bb06

File tree

11 files changed

+161
-27
lines changed

11 files changed

+161
-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
@@ -134,7 +134,6 @@ def __init__(self, recv, send, init_commands, log_file=None):
134134
self.thread_stop_reasons = {}
135135
self.progress_events = []
136136
self.reverse_requests = []
137-
self.module_events = []
138137
self.sequence = 1
139138
self.threads = None
140139
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+
11+
def test_module_event(self):
12+
program = self.getBuildArtifact("a.out")
13+
self.build_and_launch(program, stopOnEntry=True)
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.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,6 @@ static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data,
105105
return keyValue.GetUnsignedIntegerValue();
106106
}
107107

108-
static llvm::StringRef GetModuleEventReason(uint32_t event_mask) {
109-
if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded)
110-
return "new";
111-
if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded)
112-
return "removed";
113-
assert(event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded ||
114-
event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged);
115-
return "changed";
116-
}
117-
118108
/// Return string with first character capitalized.
119109
static std::string capitalize(llvm::StringRef str) {
120110
if (str.empty())
@@ -1566,18 +1556,43 @@ void DAP::EventThread() {
15661556
event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded ||
15671557
event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded ||
15681558
event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) {
1569-
llvm::StringRef reason = GetModuleEventReason(event_mask);
15701559
const uint32_t num_modules =
15711560
lldb::SBTarget::GetNumModulesFromEvent(event);
15721561
for (uint32_t i = 0; i < num_modules; ++i) {
15731562
lldb::SBModule module =
15741563
lldb::SBTarget::GetModuleAtIndexFromEvent(i, event);
15751564
if (!module.IsValid())
15761565
continue;
1566+
llvm::StringRef module_id = module.GetUUIDString();
1567+
if (module_id.empty())
1568+
continue;
1569+
1570+
llvm::StringRef reason;
1571+
bool id_only = false;
1572+
{
1573+
std::lock_guard<std::mutex> guard(modules_mutex);
1574+
if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded) {
1575+
modules.insert(module_id);
1576+
reason = "new";
1577+
} else {
1578+
// If this is a module we've never told the client about, don't
1579+
// send an event.
1580+
if (!modules.contains(module_id))
1581+
continue;
1582+
1583+
if (event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded) {
1584+
modules.erase(module_id);
1585+
reason = "removed";
1586+
id_only = true;
1587+
} else {
1588+
reason = "changed";
1589+
}
1590+
}
1591+
}
15771592

15781593
llvm::json::Object body;
15791594
body.try_emplace("reason", reason);
1580-
body.try_emplace("module", CreateModule(target, module));
1595+
body.try_emplace("module", CreateModule(target, module, id_only));
15811596
llvm::json::Object module_event = CreateEventObject("module");
15821597
module_event.try_emplace("body", std::move(body));
15831598
SendJSON(llvm::json::Value(std::move(module_event)));

lldb/tools/lldb-dap/DAP.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "llvm/ADT/SmallSet.h"
4040
#include "llvm/ADT/StringMap.h"
4141
#include "llvm/ADT/StringRef.h"
42+
#include "llvm/ADT/StringSet.h"
4243
#include "llvm/Support/Error.h"
4344
#include "llvm/Support/JSON.h"
4445
#include "llvm/Support/Threading.h"
@@ -212,6 +213,13 @@ struct DAP {
212213
/// The initial thread list upon attaching.
213214
std::optional<llvm::json::Array> initial_thread_list;
214215

216+
/// Keep track of all the modules our client knows about: either through the
217+
/// modules request or the module events.
218+
/// @{
219+
std::mutex modules_mutex;
220+
llvm::StringSet<> modules;
221+
/// @}
222+
215223
/// Creates a new DAP sessions.
216224
///
217225
/// \param[in] log

lldb/tools/lldb-dap/Handler/ModulesRequestHandler.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,20 @@ void ModulesRequestHandler::operator()(
4545
FillResponse(request, response);
4646

4747
llvm::json::Array modules;
48-
for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
49-
lldb::SBModule module = dap.target.GetModuleAtIndex(i);
50-
modules.emplace_back(CreateModule(dap.target, module));
48+
49+
{
50+
std::lock_guard<std::mutex> guard(dap.modules_mutex);
51+
for (size_t i = 0; i < dap.target.GetNumModules(); i++) {
52+
lldb::SBModule module = dap.target.GetModuleAtIndex(i);
53+
if (!module.IsValid())
54+
continue;
55+
56+
llvm::StringRef module_id = module.GetUUIDString();
57+
if (!module_id.empty())
58+
dap.modules.insert(module_id);
59+
60+
modules.emplace_back(CreateModule(dap.target, module));
61+
}
5162
}
5263

5364
llvm::json::Object body;

lldb/tools/lldb-dap/JSONUtils.cpp

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

463-
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module) {
463+
llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module,
464+
bool id_only) {
464465
llvm::json::Object object;
465466
if (!target.IsValid() || !module.IsValid())
466467
return llvm::json::Value(std::move(object));
467468

468469
const char *uuid = module.GetUUIDString();
469470
object.try_emplace("id", uuid ? std::string(uuid) : std::string(""));
471+
472+
if (id_only)
473+
return llvm::json::Value(std::move(object));
474+
470475
object.try_emplace("name", std::string(module.GetFileSpec().GetFilename()));
471476
char module_path_arr[PATH_MAX];
472477
module.GetFileSpec().GetPath(module_path_arr, sizeof(module_path_arr));

0 commit comments

Comments
 (0)