Skip to content

Commit 7546bd3

Browse files
authored
[lldb] Add support for unique target ids (#160736)
### Summary Add support for unique target ids per Target instance. This is needed for upcoming changes to allow debugger instances to be shared across separate DAP instances for child process debugging. We want the IDE to be able to attach to existing targets in an already runny lldb-dap session, and having a unique ID per target would make that easier. Each Target instance will have its own unique id, and uses a function-local counter in `TargetList::CreateTargetInternal` to assign incremental unique ids. ### Tests Added several unit tests to test basic functionality, uniqueness of targets, and target deletion doesn't affect the uniqueness. ``` bin/lldb-dotest -p TestDebuggerAPI ```
1 parent 981dadc commit 7546bd3

File tree

10 files changed

+218
-0
lines changed

10 files changed

+218
-0
lines changed

lldb/include/lldb/API/SBDebugger.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ class LLDB_API SBDebugger {
359359
lldb::SBTarget FindTargetWithFileAndArch(const char *filename,
360360
const char *arch);
361361

362+
/// Find a target with the specified unique ID.
363+
lldb::SBTarget FindTargetByGloballyUniqueID(lldb::user_id_t id);
364+
362365
/// Get the number of targets in the debugger.
363366
uint32_t GetNumTargets();
364367

lldb/include/lldb/API/SBTarget.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ class LLDB_API SBTarget {
357357

358358
const char *GetLabel() const;
359359

360+
/// Get the globally unique ID for this target. This ID is unique
361+
/// across all debugger instances within the same lldb process.
362+
///
363+
/// \return
364+
/// The globally unique ID for this target, or
365+
/// LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID if the target is invalid.
366+
lldb::user_id_t GetGloballyUniqueID() const;
367+
360368
SBError SetLabel(const char *label);
361369

362370
/// Architecture opcode byte size width accessor

lldb/include/lldb/Target/Target.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,17 @@ class Target : public std::enable_shared_from_this<Target>,
600600

601601
bool IsDummyTarget() const { return m_is_dummy_target; }
602602

603+
/// Get the globally unique ID for this target.
604+
///
605+
/// This ID is unique across all debugger instances and all targets,
606+
/// within the same lldb process. The ID is assigned
607+
/// during target construction and remains constant for the target's lifetime.
608+
/// The first target created (typically the dummy target) gets ID 1.
609+
///
610+
/// \return
611+
/// The globally unique ID for this target.
612+
lldb::user_id_t GetGloballyUniqueID() const { return m_target_unique_id; }
613+
603614
const std::string &GetLabel() const { return m_label; }
604615

605616
/// Set a label for a target.
@@ -1651,6 +1662,9 @@ class Target : public std::enable_shared_from_this<Target>,
16511662
bool m_suppress_stop_hooks; /// Used to not run stop hooks for expressions
16521663
bool m_is_dummy_target;
16531664
unsigned m_next_persistent_variable_index = 0;
1665+
lldb::user_id_t m_target_unique_id =
1666+
LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; /// The globally unique ID
1667+
/// assigned to this target
16541668
/// An optional \a lldb_private::Trace object containing processor trace
16551669
/// information of this target.
16561670
lldb::TraceSP m_trace_sp;

lldb/include/lldb/Target/TargetList.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ class TargetList : public Broadcaster {
159159

160160
lldb::TargetSP FindTargetWithProcess(lldb_private::Process *process) const;
161161

162+
/// Find the target that has a globally unique ID that matches ID \a id.
163+
///
164+
/// \param[in] id
165+
/// The globally unique target ID to search our target list for.
166+
///
167+
/// \return
168+
/// A shared pointer to a target object. The returned shared
169+
/// pointer will contain nullptr if no target objects has a
170+
/// matching target ID.
171+
lldb::TargetSP FindTargetByGloballyUniqueID(lldb::user_id_t id) const;
172+
162173
lldb::TargetSP GetTargetSP(Target *target) const;
163174

164175
/// Send an async interrupt to one or all processes.

lldb/include/lldb/lldb-defines.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
#define LLDB_INVALID_QUEUE_ID 0
9797
#define LLDB_INVALID_CPU_ID UINT32_MAX
9898
#define LLDB_INVALID_WATCHPOINT_RESOURCE_ID UINT32_MAX
99+
#define LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID 0
99100

100101
/// CPU Type definitions
101102
#define LLDB_ARCH_DEFAULT "systemArch"

lldb/source/API/SBDebugger.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,17 @@ uint32_t SBDebugger::GetIndexOfTarget(lldb::SBTarget target) {
983983
return m_opaque_sp->GetTargetList().GetIndexOfTarget(target.GetSP());
984984
}
985985

986+
SBTarget SBDebugger::FindTargetByGloballyUniqueID(lldb::user_id_t id) {
987+
LLDB_INSTRUMENT_VA(this, id);
988+
SBTarget sb_target;
989+
if (m_opaque_sp) {
990+
// No need to lock, the target list is thread safe
991+
sb_target.SetSP(
992+
m_opaque_sp->GetTargetList().FindTargetByGloballyUniqueID(id));
993+
}
994+
return sb_target;
995+
}
996+
986997
SBTarget SBDebugger::FindTargetWithProcessID(lldb::pid_t pid) {
987998
LLDB_INSTRUMENT_VA(this, pid);
988999

lldb/source/API/SBTarget.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,14 @@ const char *SBTarget::GetLabel() const {
16331633
return nullptr;
16341634
}
16351635

1636+
lldb::user_id_t SBTarget::GetGloballyUniqueID() const {
1637+
LLDB_INSTRUMENT_VA(this);
1638+
1639+
if (TargetSP target_sp = GetSP())
1640+
return target_sp->GetGloballyUniqueID();
1641+
return LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID;
1642+
}
1643+
16361644
SBError SBTarget::SetLabel(const char *label) {
16371645
LLDB_INSTRUMENT_VA(this, label);
16381646

lldb/source/Target/Target.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ struct MainExecutableInstaller {
139139
};
140140
} // namespace
141141

142+
static std::atomic<lldb::user_id_t> g_target_unique_id{1};
143+
142144
template <typename Installer>
143145
static Status installExecutable(const Installer &installer) {
144146
if (!installer.m_local_file || !installer.m_remote_file)
@@ -183,6 +185,7 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
183185
m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0),
184186
m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false),
185187
m_is_dummy_target(is_dummy_target),
188+
m_target_unique_id(g_target_unique_id++),
186189
m_frame_recognizer_manager_up(
187190
std::make_unique<StackFrameRecognizerManager>()) {
188191
SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed");

lldb/source/Target/TargetList.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,18 @@ TargetSP TargetList::FindTargetWithProcess(Process *process) const {
428428
return target_sp;
429429
}
430430

431+
TargetSP TargetList::FindTargetByGloballyUniqueID(lldb::user_id_t id) const {
432+
std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
433+
auto it = llvm::find_if(m_target_list, [id](const TargetSP &item) {
434+
return item->GetGloballyUniqueID() == id;
435+
});
436+
437+
if (it != m_target_list.end())
438+
return *it;
439+
440+
return TargetSP();
441+
}
442+
431443
TargetSP TargetList::GetTargetSP(Target *target) const {
432444
TargetSP target_sp;
433445
if (!target)

lldb/test/API/python_api/debugger/TestDebuggerAPI.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,150 @@ def test_version(self):
294294

295295
self.assertEqual(instance_str, class_str)
296296
self.assertEqual(class_str, property_str)
297+
298+
def test_find_target_with_unique_id(self):
299+
"""Test SBDebugger.FindTargetByGloballyUniqueID() functionality."""
300+
301+
# Test with invalid ID - should return invalid target
302+
invalid_target = self.dbg.FindTargetByGloballyUniqueID(999999)
303+
self.assertFalse(invalid_target.IsValid())
304+
305+
# Test with ID 0 - should return invalid target
306+
zero_target = self.dbg.FindTargetByGloballyUniqueID(0)
307+
self.assertFalse(zero_target.IsValid())
308+
309+
# Build a real executable and create target with it
310+
self.build()
311+
exe = self.getBuildArtifact("a.out")
312+
target = self.dbg.CreateTarget(exe)
313+
self.assertTrue(target.IsValid())
314+
315+
# Find the target using its unique ID
316+
unique_id = target.GetGloballyUniqueID()
317+
self.assertNotEqual(unique_id, lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID)
318+
found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id)
319+
self.assertTrue(found_target.IsValid())
320+
self.assertEqual(
321+
self.dbg.GetIndexOfTarget(target), self.dbg.GetIndexOfTarget(found_target)
322+
)
323+
self.assertEqual(found_target.GetGloballyUniqueID(), unique_id)
324+
325+
def test_target_unique_id_uniqueness(self):
326+
"""Test that Target.GetGloballyUniqueID() returns unique values across multiple targets."""
327+
328+
# Create multiple targets and verify they all have unique IDs
329+
self.build()
330+
exe = self.getBuildArtifact("a.out")
331+
targets = []
332+
unique_ids = set()
333+
334+
for i in range(10):
335+
target = self.dbg.CreateTarget(exe)
336+
self.assertTrue(target.IsValid())
337+
338+
unique_id = target.GetGloballyUniqueID()
339+
self.assertNotEqual(unique_id, 0)
340+
341+
# Verify this ID hasn't been used before
342+
self.assertNotIn(
343+
unique_id, unique_ids, f"Duplicate unique ID found: {unique_id}"
344+
)
345+
346+
unique_ids.add(unique_id)
347+
targets.append(target)
348+
349+
# Verify all targets can still be found by their IDs
350+
for target in targets:
351+
unique_id = target.GetGloballyUniqueID()
352+
found = self.dbg.FindTargetByGloballyUniqueID(unique_id)
353+
self.assertTrue(found.IsValid())
354+
self.assertEqual(found.GetGloballyUniqueID(), unique_id)
355+
356+
def test_target_unique_id_uniqueness_after_deletion(self):
357+
"""Test finding targets have unique ID after target deletion."""
358+
# Create two targets
359+
self.build()
360+
exe = self.getBuildArtifact("a.out")
361+
target1 = self.dbg.CreateTarget(exe)
362+
target2 = self.dbg.CreateTarget(exe)
363+
self.assertTrue(target1.IsValid())
364+
self.assertTrue(target2.IsValid())
365+
366+
unique_id1 = target1.GetGloballyUniqueID()
367+
unique_id2 = target2.GetGloballyUniqueID()
368+
self.assertNotEqual(unique_id1, 0)
369+
self.assertNotEqual(unique_id2, 0)
370+
self.assertNotEqual(unique_id1, unique_id2)
371+
372+
# Verify we can find them initially
373+
found_target1 = self.dbg.FindTargetByGloballyUniqueID(unique_id1)
374+
found_target2 = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
375+
self.assertTrue(found_target1.IsValid())
376+
self.assertTrue(found_target2.IsValid())
377+
target2_index = self.dbg.GetIndexOfTarget(target2)
378+
379+
# Delete target 2
380+
deleted = self.dbg.DeleteTarget(target2)
381+
self.assertTrue(deleted)
382+
383+
# Try to find the deleted target - should not be found
384+
not_found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
385+
self.assertFalse(not_found_target.IsValid())
386+
387+
# Create a new target
388+
target3 = self.dbg.CreateTarget(exe)
389+
self.assertTrue(target3.IsValid())
390+
# Target list index of target3 should be the same as target2's
391+
# since it was deleted, but it should have a distinct unique ID
392+
target3_index = self.dbg.GetIndexOfTarget(target3)
393+
unique_id3 = target3.GetGloballyUniqueID()
394+
self.assertEqual(target3_index, target2_index)
395+
self.assertNotEqual(unique_id3, unique_id2)
396+
self.assertNotEqual(unique_id3, unique_id1)
397+
# Make sure we can find the new target
398+
found_target3 = self.dbg.FindTargetByGloballyUniqueID(
399+
target3.GetGloballyUniqueID()
400+
)
401+
self.assertTrue(found_target3.IsValid())
402+
403+
def test_target_globally_unique_id_across_debuggers(self):
404+
"""Test that target IDs are globally unique across multiple debuggers."""
405+
self.build()
406+
exe = self.getBuildArtifact("a.out")
407+
408+
# Create two debuggers with targets each
409+
debugger1 = lldb.SBDebugger.Create()
410+
debugger2 = lldb.SBDebugger.Create()
411+
self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger1))
412+
self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger2))
413+
414+
# Create 2 targets per debugger
415+
targets_d1 = [debugger1.CreateTarget(exe), debugger1.CreateTarget(exe)]
416+
targets_d2 = [debugger2.CreateTarget(exe), debugger2.CreateTarget(exe)]
417+
targets = targets_d1 + targets_d2
418+
419+
# Get all IDs and verify they're unique
420+
ids = [target.GetGloballyUniqueID() for target in targets]
421+
self.assertEqual(
422+
len(set(ids)), len(ids), f"IDs should be globally unique: {ids}"
423+
)
424+
self.assertTrue(
425+
all(uid != lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID for uid in ids),
426+
"All IDs should be valid",
427+
)
428+
429+
# Verify targets can be found by their IDs in respective debuggers
430+
for debugger, target_pair in [
431+
(debugger1, targets[:2]),
432+
(debugger2, targets[2:]),
433+
]:
434+
for target in target_pair:
435+
found = debugger.FindTargetByGloballyUniqueID(
436+
target.GetGloballyUniqueID()
437+
)
438+
self.assertTrue(
439+
found.IsValid(), "Target should be found by its unique ID"
440+
)
441+
self.assertEqual(
442+
found.GetGloballyUniqueID(), target.GetGloballyUniqueID()
443+
)

0 commit comments

Comments
 (0)