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, 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, 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 eb56337de3c44..f949ba224b8dc 100644 --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -1632,6 +1632,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 g_target_unique_id{1}; + template 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()) { 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 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() + )