Skip to content

Commit 84b88d7

Browse files
qxy11丹治秀樹
authored andcommitted
[lldb-dap] Add multi-session support with shared debugger instances (llvm#163653)
## Summary: This change introduces a `DAPSessionManager` to enable multiple DAP sessions to share debugger instances when needed, for things like child process debugging and some scripting hooks that create dynamically new targets. Changes include: - Add `DAPSessionManager` singleton to track and coordinate all active DAP sessions - Support attaching to an existing target via its globally unique target ID (targetId parameter) - Share debugger instances across sessions when new targets are created dynamically - Refactor event thread management to allow sharing event threads between sessions and move event thread and event thread handlers to `EventHelpers` - Add `eBroadcastBitNewTargetCreated` event to notify when new targets are created - Extract session names from target creation events - Defer debugger initialization from 'initialize' request to 'launch'/'attach' requests. The only time the debugger is used currently in between its creation in `InitializeRequestHandler` and the `Launch` or `Attach` requests is during the `TelemetryDispatcher` destruction call at the end of the `DAP::HandleObject` call, so this is safe. This enables scenarios when new targets are created dynamically so that the debug adapter can automatically start a new debug session for the spawned target while sharing the debugger instance. ## Tests: The refactoring maintains backward compatibility. All existing DAP test cases pass. Also added a few basic unit tests for DAPSessionManager ``` >> ninja DAPTests >> ./tools/lldb/unittests/DAP/DAPTests >>./bin/llvm-lit -v ../llvm-project/lldb/test/API/tools/lldb-dap/ ```
1 parent 0635bdb commit 84b88d7

File tree

25 files changed

+1091
-319
lines changed

25 files changed

+1091
-319
lines changed

lldb/include/lldb/API/SBTarget.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class LLDB_API SBTarget {
4444
eBroadcastBitWatchpointChanged = (1 << 3),
4545
eBroadcastBitSymbolsLoaded = (1 << 4),
4646
eBroadcastBitSymbolsChanged = (1 << 5),
47+
eBroadcastBitNewTargetCreated = (1 << 6),
4748
};
4849

4950
// Constructors
@@ -64,6 +65,10 @@ class LLDB_API SBTarget {
6465

6566
static lldb::SBTarget GetTargetFromEvent(const lldb::SBEvent &event);
6667

68+
/// For eBroadcastBitNewTargetCreated events, returns the newly created
69+
/// target. For other event types, returns an invalid SBTarget.
70+
static lldb::SBTarget GetCreatedTargetFromEvent(const lldb::SBEvent &event);
71+
6772
static uint32_t GetNumModulesFromEvent(const lldb::SBEvent &event);
6873

6974
static lldb::SBModule GetModuleAtIndexFromEvent(const uint32_t idx,
@@ -365,6 +370,16 @@ class LLDB_API SBTarget {
365370
/// LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID if the target is invalid.
366371
lldb::user_id_t GetGloballyUniqueID() const;
367372

373+
/// Get the target session name for this target.
374+
///
375+
/// The target session name provides a meaningful name for IDEs or tools to
376+
/// display to help the user identify the origin and purpose of the target.
377+
///
378+
/// \return
379+
/// The target session name for this target, or nullptr if the target is
380+
/// invalid or has no target session name.
381+
const char *GetTargetSessionName() const;
382+
368383
SBError SetLabel(const char *label);
369384

370385
/// Architecture opcode byte size width accessor

lldb/include/lldb/Target/Target.h

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ class Target : public std::enable_shared_from_this<Target>,
537537
eBroadcastBitWatchpointChanged = (1 << 3),
538538
eBroadcastBitSymbolsLoaded = (1 << 4),
539539
eBroadcastBitSymbolsChanged = (1 << 5),
540+
eBroadcastBitNewTargetCreated = (1 << 6),
540541
};
541542

542543
// These two functions fill out the Broadcaster interface:
@@ -556,6 +557,13 @@ class Target : public std::enable_shared_from_this<Target>,
556557
TargetEventData(const lldb::TargetSP &target_sp,
557558
const ModuleList &module_list);
558559

560+
// Constructor for eBroadcastBitNewTargetCreated events. For this event
561+
// type:
562+
// - target_sp is the parent target (the subject/broadcaster of the event)
563+
// - created_target_sp is the newly created target
564+
TargetEventData(const lldb::TargetSP &target_sp,
565+
const lldb::TargetSP &created_target_sp);
566+
559567
~TargetEventData() override;
560568

561569
static llvm::StringRef GetFlavorString();
@@ -570,14 +578,23 @@ class Target : public std::enable_shared_from_this<Target>,
570578

571579
static lldb::TargetSP GetTargetFromEvent(const Event *event_ptr);
572580

581+
// For eBroadcastBitNewTargetCreated events, returns the newly created
582+
// target. For other event types, returns an invalid target.
583+
static lldb::TargetSP GetCreatedTargetFromEvent(const Event *event_ptr);
584+
573585
static ModuleList GetModuleListFromEvent(const Event *event_ptr);
574586

575587
const lldb::TargetSP &GetTarget() const { return m_target_sp; }
576588

589+
const lldb::TargetSP &GetCreatedTarget() const {
590+
return m_created_target_sp;
591+
}
592+
577593
const ModuleList &GetModuleList() const { return m_module_list; }
578594

579595
private:
580596
lldb::TargetSP m_target_sp;
597+
lldb::TargetSP m_created_target_sp;
581598
ModuleList m_module_list;
582599

583600
TargetEventData(const TargetEventData &) = delete;
@@ -622,6 +639,30 @@ class Target : public std::enable_shared_from_this<Target>,
622639
/// requirements.
623640
llvm::Error SetLabel(llvm::StringRef label);
624641

642+
/// Get the target session name for this target.
643+
///
644+
/// Provides a meaningful name for IDEs or tools to display for dynamically
645+
/// created targets. Defaults to "Session {ID}" based on the globally unique
646+
/// ID.
647+
///
648+
/// \return
649+
/// The target session name for this target.
650+
llvm::StringRef GetTargetSessionName() { return m_target_session_name; }
651+
652+
/// Set the target session name for this target.
653+
///
654+
/// This should typically be set along with the event
655+
/// eBroadcastBitNewTargetCreated. Useful for scripts or triggers that
656+
/// automatically create targets and want to provide meaningful names that
657+
/// IDEs or other tools can display to help users identify the origin and
658+
/// purpose of each target.
659+
///
660+
/// \param[in] target_session_name
661+
/// The target session name to set for this target.
662+
void SetTargetSessionName(llvm::StringRef target_session_name) {
663+
m_target_session_name = target_session_name.str();
664+
}
665+
625666
/// Find a binary on the system and return its Module,
626667
/// or return an existing Module that is already in the Target.
627668
///
@@ -1719,8 +1760,11 @@ class Target : public std::enable_shared_from_this<Target>,
17191760
bool m_is_dummy_target;
17201761
unsigned m_next_persistent_variable_index = 0;
17211762
lldb::user_id_t m_target_unique_id =
1722-
LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; /// The globally unique ID
1763+
LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; ///< The globally unique ID
17231764
/// assigned to this target
1765+
std::string m_target_session_name; ///< The target session name for this
1766+
/// target, used to name debugging
1767+
/// sessions in DAP.
17241768
/// An optional \a lldb_private::Trace object containing processor trace
17251769
/// information of this target.
17261770
lldb::TraceSP m_trace_sp;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,8 @@ def request_attach(
785785
*,
786786
program: Optional[str] = None,
787787
pid: Optional[int] = None,
788+
debuggerId: Optional[int] = None,
789+
targetId: Optional[int] = None,
788790
waitFor=False,
789791
initCommands: Optional[list[str]] = None,
790792
preRunCommands: Optional[list[str]] = None,
@@ -804,6 +806,10 @@ def request_attach(
804806
args_dict["pid"] = pid
805807
if program is not None:
806808
args_dict["program"] = program
809+
if debuggerId is not None:
810+
args_dict["debuggerId"] = debuggerId
811+
if targetId is not None:
812+
args_dict["targetId"] = targetId
807813
if waitFor:
808814
args_dict["waitFor"] = waitFor
809815
args_dict["initCommands"] = self.init_commands

lldb/source/API/SBTarget.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ SBTarget SBTarget::GetTargetFromEvent(const SBEvent &event) {
128128
return Target::TargetEventData::GetTargetFromEvent(event.get());
129129
}
130130

131+
SBTarget SBTarget::GetCreatedTargetFromEvent(const SBEvent &event) {
132+
LLDB_INSTRUMENT_VA(event);
133+
134+
return Target::TargetEventData::GetCreatedTargetFromEvent(event.get());
135+
}
136+
131137
uint32_t SBTarget::GetNumModulesFromEvent(const SBEvent &event) {
132138
LLDB_INSTRUMENT_VA(event);
133139

@@ -1641,6 +1647,14 @@ lldb::user_id_t SBTarget::GetGloballyUniqueID() const {
16411647
return LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID;
16421648
}
16431649

1650+
const char *SBTarget::GetTargetSessionName() const {
1651+
LLDB_INSTRUMENT_VA(this);
1652+
1653+
if (TargetSP target_sp = GetSP())
1654+
return ConstString(target_sp->GetTargetSessionName()).AsCString();
1655+
return nullptr;
1656+
}
1657+
16441658
SBError SBTarget::SetLabel(const char *label) {
16451659
LLDB_INSTRUMENT_VA(this, label);
16461660

lldb/source/Target/Target.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,16 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
185185
m_internal_stop_hooks(), m_latest_stop_hook_id(0), m_valid(true),
186186
m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target),
187187
m_target_unique_id(g_target_unique_id++),
188+
m_target_session_name(
189+
llvm::formatv("Session {0}", m_target_unique_id).str()),
188190
m_frame_recognizer_manager_up(
189191
std::make_unique<StackFrameRecognizerManager>()) {
190192
SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed");
191193
SetEventName(eBroadcastBitModulesLoaded, "modules-loaded");
192194
SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded");
193195
SetEventName(eBroadcastBitWatchpointChanged, "watchpoint-changed");
194196
SetEventName(eBroadcastBitSymbolsLoaded, "symbols-loaded");
197+
SetEventName(eBroadcastBitNewTargetCreated, "new-target-created");
195198

196199
CheckInWithManager();
197200

@@ -5198,6 +5201,11 @@ Target::TargetEventData::TargetEventData(const lldb::TargetSP &target_sp,
51985201
const ModuleList &module_list)
51995202
: EventData(), m_target_sp(target_sp), m_module_list(module_list) {}
52005203

5204+
Target::TargetEventData::TargetEventData(
5205+
const lldb::TargetSP &target_sp, const lldb::TargetSP &created_target_sp)
5206+
: EventData(), m_target_sp(target_sp),
5207+
m_created_target_sp(created_target_sp), m_module_list() {}
5208+
52015209
Target::TargetEventData::~TargetEventData() = default;
52025210

52035211
llvm::StringRef Target::TargetEventData::GetFlavorString() {
@@ -5232,6 +5240,15 @@ TargetSP Target::TargetEventData::GetTargetFromEvent(const Event *event_ptr) {
52325240
return target_sp;
52335241
}
52345242

5243+
TargetSP
5244+
Target::TargetEventData::GetCreatedTargetFromEvent(const Event *event_ptr) {
5245+
TargetSP created_target_sp;
5246+
const TargetEventData *event_data = GetEventDataFromEvent(event_ptr);
5247+
if (event_data)
5248+
created_target_sp = event_data->m_created_target_sp;
5249+
return created_target_sp;
5250+
}
5251+
52355252
ModuleList
52365253
Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) {
52375254
ModuleList module_list;

lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,38 @@ def test_by_name_waitFor(self):
7575
self.spawn_thread.start()
7676
self.attach(program=program, waitFor=True)
7777
self.continue_and_verify_pid()
78+
79+
def test_attach_with_missing_debuggerId_or_targetId(self):
80+
"""
81+
Test that attaching with only one of debuggerId/targetId specified
82+
fails with the expected error message.
83+
"""
84+
self.build_and_create_debug_adapter()
85+
86+
# Test with only targetId specified (no debuggerId)
87+
resp = self.attach(targetId=99999, expectFailure=True)
88+
self.assertFalse(resp["success"])
89+
self.assertIn(
90+
"Both debuggerId and targetId must be specified together",
91+
resp["body"]["error"]["format"],
92+
)
93+
94+
def test_attach_with_invalid_debuggerId_and_targetId(self):
95+
"""
96+
Test that attaching with both debuggerId and targetId specified but
97+
invalid fails with an appropriate error message.
98+
"""
99+
self.build_and_create_debug_adapter()
100+
101+
# Attach with both debuggerId=9999 and targetId=99999 (both invalid).
102+
# Since debugger ID 9999 likely doesn't exist in the global registry,
103+
# we expect a validation error.
104+
resp = self.attach(debuggerId=9999, targetId=99999, expectFailure=True)
105+
self.assertFalse(resp["success"])
106+
error_msg = resp["body"]["error"]["format"]
107+
# Either error is acceptable - both indicate the debugger reuse
108+
# validation is working correctly
109+
self.assertTrue(
110+
"Unable to find existing debugger" in error_msg
111+
or f"Expected debugger/target not found error, got: {error_msg}"
112+
)

lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,54 @@ def test_startDebugging(self):
3636
request = self.dap_server.reverse_requests[0]
3737
self.assertEqual(request["arguments"]["configuration"]["pid"], 321)
3838
self.assertEqual(request["arguments"]["request"], "attach")
39+
40+
def test_startDebugging_debugger_reuse(self):
41+
"""
42+
Tests that debugger and target IDs can be passed through startDebugging
43+
for debugger reuse. This verifies the infrastructure for child DAP
44+
sessions to reuse the parent's debugger and attach to an existing target.
45+
"""
46+
program = self.getBuildArtifact("a.out")
47+
source = "main.c"
48+
self.build_and_launch(program)
49+
50+
breakpoint_line = line_number(source, "// breakpoint")
51+
self.set_source_breakpoints(source, [breakpoint_line])
52+
self.continue_to_next_stop()
53+
54+
# Use mock IDs to test the infrastructure
55+
# In a real scenario, these would come from the parent session
56+
test_debugger_id = 1
57+
test_target_id = 100
58+
59+
# Send a startDebugging request with debuggerId and targetId
60+
# This simulates creating a child DAP session that reuses the debugger
61+
self.dap_server.request_evaluate(
62+
f'`lldb-dap start-debugging attach \'{{"debuggerId":{test_debugger_id},"targetId":{test_target_id}}}\'',
63+
context="repl",
64+
)
65+
66+
self.continue_to_exit()
67+
68+
# Verify the reverse request was sent with the correct IDs
69+
self.assertEqual(
70+
len(self.dap_server.reverse_requests),
71+
1,
72+
"Should have received one startDebugging reverse request",
73+
)
74+
75+
request = self.dap_server.reverse_requests[0]
76+
self.assertEqual(request["command"], "startDebugging")
77+
self.assertEqual(request["arguments"]["request"], "attach")
78+
79+
config = request["arguments"]["configuration"]
80+
self.assertEqual(
81+
config["debuggerId"],
82+
test_debugger_id,
83+
"Reverse request should include debugger ID",
84+
)
85+
self.assertEqual(
86+
config["targetId"],
87+
test_target_id,
88+
"Reverse request should include target ID",
89+
)

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_lldb_library(lldbDAP
1010
DAP.cpp
1111
DAPError.cpp
1212
DAPLog.cpp
13+
DAPSessionManager.cpp
1314
EventHelper.cpp
1415
ExceptionBreakpoint.cpp
1516
FifoFiles.cpp

0 commit comments

Comments
 (0)