[Lldb-commits] [lldb] 7546bd3 - [lldb] Add support for unique target ids (#160736)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Oct 7 12:36:51 PDT 2025
Author: Janet Yang
Date: 2025-10-07T15:36:47-04:00
New Revision: 7546bd38041612e8b768f4b315e491cd549d608c
URL: https://github.com/llvm/llvm-project/commit/7546bd38041612e8b768f4b315e491cd549d608c
DIFF: https://github.com/llvm/llvm-project/commit/7546bd38041612e8b768f4b315e491cd549d608c.diff
LOG: [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
```
Added:
Modified:
lldb/include/lldb/API/SBDebugger.h
lldb/include/lldb/API/SBTarget.h
lldb/include/lldb/Target/Target.h
lldb/include/lldb/Target/TargetList.h
lldb/include/lldb/lldb-defines.h
lldb/source/API/SBDebugger.cpp
lldb/source/API/SBTarget.cpp
lldb/source/Target/Target.cpp
lldb/source/Target/TargetList.cpp
lldb/test/API/python_api/debugger/TestDebuggerAPI.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h
index f77b0c1d7f0ee..7a08a08262207 100644
--- a/lldb/include/lldb/API/SBDebugger.h
+++ b/lldb/include/lldb/API/SBDebugger.h
@@ -359,6 +359,9 @@ class LLDB_API SBDebugger {
lldb::SBTarget FindTargetWithFileAndArch(const char *filename,
const char *arch);
+ /// Find a target with the specified unique ID.
+ lldb::SBTarget FindTargetByGloballyUniqueID(lldb::user_id_t id);
+
/// Get the number of targets in the debugger.
uint32_t GetNumTargets();
diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h
index 62cdd342a05e4..173fd05b54a13 100644
--- a/lldb/include/lldb/API/SBTarget.h
+++ b/lldb/include/lldb/API/SBTarget.h
@@ -357,6 +357,14 @@ class LLDB_API SBTarget {
const char *GetLabel() const;
+ /// Get the globally unique ID for this target. This ID is unique
+ /// across all debugger instances within the same lldb process.
+ ///
+ /// \return
+ /// The globally unique ID for this target, or
+ /// LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID if the target is invalid.
+ lldb::user_id_t GetGloballyUniqueID() const;
+
SBError SetLabel(const char *label);
/// Architecture opcode byte size width accessor
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 14a09f29094d5..f4a09237ce897 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -600,6 +600,17 @@ class Target : public std::enable_shared_from_this<Target>,
bool IsDummyTarget() const { return m_is_dummy_target; }
+ /// Get the globally unique ID for this target.
+ ///
+ /// This ID is unique across all debugger instances and all targets,
+ /// within the same lldb process. The ID is assigned
+ /// during target construction and remains constant for the target's lifetime.
+ /// The first target created (typically the dummy target) gets ID 1.
+ ///
+ /// \return
+ /// The globally unique ID for this target.
+ lldb::user_id_t GetGloballyUniqueID() const { return m_target_unique_id; }
+
const std::string &GetLabel() const { return m_label; }
/// Set a label for a target.
@@ -1651,6 +1662,9 @@ class Target : public std::enable_shared_from_this<Target>,
bool m_suppress_stop_hooks; /// Used to not run stop hooks for expressions
bool m_is_dummy_target;
unsigned m_next_persistent_variable_index = 0;
+ lldb::user_id_t m_target_unique_id =
+ LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; /// The globally unique ID
+ /// assigned to this target
/// An optional \a lldb_private::Trace object containing processor trace
/// information of this target.
lldb::TraceSP m_trace_sp;
diff --git a/lldb/include/lldb/Target/TargetList.h b/lldb/include/lldb/Target/TargetList.h
index 080a6039c7ff8..88272512bcc0f 100644
--- a/lldb/include/lldb/Target/TargetList.h
+++ b/lldb/include/lldb/Target/TargetList.h
@@ -159,6 +159,17 @@ class TargetList : public Broadcaster {
lldb::TargetSP FindTargetWithProcess(lldb_private::Process *process) const;
+ /// Find the target that has a globally unique ID that matches ID \a id.
+ ///
+ /// \param[in] id
+ /// The globally unique target ID to search our target list for.
+ ///
+ /// \return
+ /// A shared pointer to a target object. The returned shared
+ /// pointer will contain nullptr if no target objects has a
+ /// matching target ID.
+ lldb::TargetSP FindTargetByGloballyUniqueID(lldb::user_id_t id) const;
+
lldb::TargetSP GetTargetSP(Target *target) const;
/// Send an async interrupt to one or all processes.
diff --git a/lldb/include/lldb/lldb-defines.h b/lldb/include/lldb/lldb-defines.h
index c7bd019c5c90e..c54ef884b01dc 100644
--- a/lldb/include/lldb/lldb-defines.h
+++ b/lldb/include/lldb/lldb-defines.h
@@ -96,6 +96,7 @@
#define LLDB_INVALID_QUEUE_ID 0
#define LLDB_INVALID_CPU_ID UINT32_MAX
#define LLDB_INVALID_WATCHPOINT_RESOURCE_ID UINT32_MAX
+#define LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID 0
/// CPU Type definitions
#define LLDB_ARCH_DEFAULT "systemArch"
diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index 603e306497841..5c4c653d95a81 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -983,6 +983,17 @@ uint32_t SBDebugger::GetIndexOfTarget(lldb::SBTarget target) {
return m_opaque_sp->GetTargetList().GetIndexOfTarget(target.GetSP());
}
+SBTarget SBDebugger::FindTargetByGloballyUniqueID(lldb::user_id_t id) {
+ LLDB_INSTRUMENT_VA(this, id);
+ SBTarget sb_target;
+ if (m_opaque_sp) {
+ // No need to lock, the target list is thread safe
+ sb_target.SetSP(
+ m_opaque_sp->GetTargetList().FindTargetByGloballyUniqueID(id));
+ }
+ return sb_target;
+}
+
SBTarget SBDebugger::FindTargetWithProcessID(lldb::pid_t pid) {
LLDB_INSTRUMENT_VA(this, pid);
diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp
index 0d03250753802..98d10aa07c53f 100644
--- a/lldb/source/API/SBTarget.cpp
+++ b/lldb/source/API/SBTarget.cpp
@@ -1633,6 +1633,14 @@ const char *SBTarget::GetLabel() const {
return nullptr;
}
+lldb::user_id_t SBTarget::GetGloballyUniqueID() const {
+ LLDB_INSTRUMENT_VA(this);
+
+ if (TargetSP target_sp = GetSP())
+ return target_sp->GetGloballyUniqueID();
+ return LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID;
+}
+
SBError SBTarget::SetLabel(const char *label) {
LLDB_INSTRUMENT_VA(this, label);
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index fa98c24606492..e0286c4576ae5 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -139,6 +139,8 @@ struct MainExecutableInstaller {
};
} // namespace
+static std::atomic<lldb::user_id_t> g_target_unique_id{1};
+
template <typename Installer>
static Status installExecutable(const Installer &installer) {
if (!installer.m_local_file || !installer.m_remote_file)
@@ -183,6 +185,7 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch,
m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0),
m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false),
m_is_dummy_target(is_dummy_target),
+ m_target_unique_id(g_target_unique_id++),
m_frame_recognizer_manager_up(
std::make_unique<StackFrameRecognizerManager>()) {
SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed");
diff --git a/lldb/source/Target/TargetList.cpp b/lldb/source/Target/TargetList.cpp
index 7037dc2bea3cc..188c2508a71ed 100644
--- a/lldb/source/Target/TargetList.cpp
+++ b/lldb/source/Target/TargetList.cpp
@@ -428,6 +428,18 @@ TargetSP TargetList::FindTargetWithProcess(Process *process) const {
return target_sp;
}
+TargetSP TargetList::FindTargetByGloballyUniqueID(lldb::user_id_t id) const {
+ std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex);
+ auto it = llvm::find_if(m_target_list, [id](const TargetSP &item) {
+ return item->GetGloballyUniqueID() == id;
+ });
+
+ if (it != m_target_list.end())
+ return *it;
+
+ return TargetSP();
+}
+
TargetSP TargetList::GetTargetSP(Target *target) const {
TargetSP target_sp;
if (!target)
diff --git a/lldb/test/API/python_api/debugger/TestDebuggerAPI.py b/lldb/test/API/python_api/debugger/TestDebuggerAPI.py
index 43f45f330ee2a..44b1183288017 100644
--- a/lldb/test/API/python_api/debugger/TestDebuggerAPI.py
+++ b/lldb/test/API/python_api/debugger/TestDebuggerAPI.py
@@ -294,3 +294,150 @@ def test_version(self):
self.assertEqual(instance_str, class_str)
self.assertEqual(class_str, property_str)
+
+ def test_find_target_with_unique_id(self):
+ """Test SBDebugger.FindTargetByGloballyUniqueID() functionality."""
+
+ # Test with invalid ID - should return invalid target
+ invalid_target = self.dbg.FindTargetByGloballyUniqueID(999999)
+ self.assertFalse(invalid_target.IsValid())
+
+ # Test with ID 0 - should return invalid target
+ zero_target = self.dbg.FindTargetByGloballyUniqueID(0)
+ self.assertFalse(zero_target.IsValid())
+
+ # Build a real executable and create target with it
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target.IsValid())
+
+ # Find the target using its unique ID
+ unique_id = target.GetGloballyUniqueID()
+ self.assertNotEqual(unique_id, lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID)
+ found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id)
+ self.assertTrue(found_target.IsValid())
+ self.assertEqual(
+ self.dbg.GetIndexOfTarget(target), self.dbg.GetIndexOfTarget(found_target)
+ )
+ self.assertEqual(found_target.GetGloballyUniqueID(), unique_id)
+
+ def test_target_unique_id_uniqueness(self):
+ """Test that Target.GetGloballyUniqueID() returns unique values across multiple targets."""
+
+ # Create multiple targets and verify they all have unique IDs
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ targets = []
+ unique_ids = set()
+
+ for i in range(10):
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target.IsValid())
+
+ unique_id = target.GetGloballyUniqueID()
+ self.assertNotEqual(unique_id, 0)
+
+ # Verify this ID hasn't been used before
+ self.assertNotIn(
+ unique_id, unique_ids, f"Duplicate unique ID found: {unique_id}"
+ )
+
+ unique_ids.add(unique_id)
+ targets.append(target)
+
+ # Verify all targets can still be found by their IDs
+ for target in targets:
+ unique_id = target.GetGloballyUniqueID()
+ found = self.dbg.FindTargetByGloballyUniqueID(unique_id)
+ self.assertTrue(found.IsValid())
+ self.assertEqual(found.GetGloballyUniqueID(), unique_id)
+
+ def test_target_unique_id_uniqueness_after_deletion(self):
+ """Test finding targets have unique ID after target deletion."""
+ # Create two targets
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+ target1 = self.dbg.CreateTarget(exe)
+ target2 = self.dbg.CreateTarget(exe)
+ self.assertTrue(target1.IsValid())
+ self.assertTrue(target2.IsValid())
+
+ unique_id1 = target1.GetGloballyUniqueID()
+ unique_id2 = target2.GetGloballyUniqueID()
+ self.assertNotEqual(unique_id1, 0)
+ self.assertNotEqual(unique_id2, 0)
+ self.assertNotEqual(unique_id1, unique_id2)
+
+ # Verify we can find them initially
+ found_target1 = self.dbg.FindTargetByGloballyUniqueID(unique_id1)
+ found_target2 = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
+ self.assertTrue(found_target1.IsValid())
+ self.assertTrue(found_target2.IsValid())
+ target2_index = self.dbg.GetIndexOfTarget(target2)
+
+ # Delete target 2
+ deleted = self.dbg.DeleteTarget(target2)
+ self.assertTrue(deleted)
+
+ # Try to find the deleted target - should not be found
+ not_found_target = self.dbg.FindTargetByGloballyUniqueID(unique_id2)
+ self.assertFalse(not_found_target.IsValid())
+
+ # Create a new target
+ target3 = self.dbg.CreateTarget(exe)
+ self.assertTrue(target3.IsValid())
+ # Target list index of target3 should be the same as target2's
+ # since it was deleted, but it should have a distinct unique ID
+ target3_index = self.dbg.GetIndexOfTarget(target3)
+ unique_id3 = target3.GetGloballyUniqueID()
+ self.assertEqual(target3_index, target2_index)
+ self.assertNotEqual(unique_id3, unique_id2)
+ self.assertNotEqual(unique_id3, unique_id1)
+ # Make sure we can find the new target
+ found_target3 = self.dbg.FindTargetByGloballyUniqueID(
+ target3.GetGloballyUniqueID()
+ )
+ self.assertTrue(found_target3.IsValid())
+
+ def test_target_globally_unique_id_across_debuggers(self):
+ """Test that target IDs are globally unique across multiple debuggers."""
+ self.build()
+ exe = self.getBuildArtifact("a.out")
+
+ # Create two debuggers with targets each
+ debugger1 = lldb.SBDebugger.Create()
+ debugger2 = lldb.SBDebugger.Create()
+ self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger1))
+ self.addTearDownHook(lambda: lldb.SBDebugger.Destroy(debugger2))
+
+ # Create 2 targets per debugger
+ targets_d1 = [debugger1.CreateTarget(exe), debugger1.CreateTarget(exe)]
+ targets_d2 = [debugger2.CreateTarget(exe), debugger2.CreateTarget(exe)]
+ targets = targets_d1 + targets_d2
+
+ # Get all IDs and verify they're unique
+ ids = [target.GetGloballyUniqueID() for target in targets]
+ self.assertEqual(
+ len(set(ids)), len(ids), f"IDs should be globally unique: {ids}"
+ )
+ self.assertTrue(
+ all(uid != lldb.LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID for uid in ids),
+ "All IDs should be valid",
+ )
+
+ # Verify targets can be found by their IDs in respective debuggers
+ for debugger, target_pair in [
+ (debugger1, targets[:2]),
+ (debugger2, targets[2:]),
+ ]:
+ for target in target_pair:
+ found = debugger.FindTargetByGloballyUniqueID(
+ target.GetGloballyUniqueID()
+ )
+ self.assertTrue(
+ found.IsValid(), "Target should be found by its unique ID"
+ )
+ self.assertEqual(
+ found.GetGloballyUniqueID(), target.GetGloballyUniqueID()
+ )
More information about the lldb-commits
mailing list