| """ | |
| Test Debugger APIs. | |
| """ | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| class DebuggerAPITestCase(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| SHARED_BUILD_TESTCASE = False | |
| def test_debugger_api_boundary_condition(self): | |
| """Exercise SBDebugger APIs with boundary conditions.""" | |
| self.dbg.HandleCommand(None) | |
| self.dbg.SetDefaultArchitecture(None) | |
| self.dbg.GetScriptingLanguage(None) | |
| self.dbg.CreateTarget(None) | |
| self.dbg.CreateTarget(None, None, None, True, lldb.SBError()) | |
| self.dbg.CreateTargetWithFileAndTargetTriple(None, None) | |
| self.dbg.CreateTargetWithFileAndArch(None, None) | |
| self.dbg.FindTargetWithFileAndArch(None, None) | |
| self.dbg.SetInternalVariable(None, None, None) | |
| self.dbg.GetInternalVariableValue(None, None) | |
| # FIXME (filcab): We must first allow for the swig bindings to know if | |
| # a Python callback is set. (Check python-typemaps.swig) | |
| # self.dbg.SetLoggingCallback(None) | |
| self.dbg.SetPrompt(None) | |
| self.dbg.SetCurrentPlatform(None) | |
| self.dbg.SetCurrentPlatformSDKRoot(None) | |
| fresh_dbg = lldb.SBDebugger() | |
| self.assertEqual(len(fresh_dbg), 0) | |
| def test_debugger_delete_invalid_target(self): | |
| """SBDebugger.DeleteTarget() should not crash LLDB given and invalid target.""" | |
| target = lldb.SBTarget() | |
| self.assertFalse(target.IsValid()) | |
| self.dbg.DeleteTarget(target) | |
| def test_debugger_internal_variables(self): | |
| """Ensure that SBDebugger reachs the same instance of properties | |
| regardless CommandInterpreter's context initialization""" | |
| self.build() | |
| exe = self.getBuildArtifact("a.out") | |
| # Create a target by the debugger. | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| property_name = "target.process.memory-cache-line-size" | |
| def get_cache_line_size(): | |
| value_list = lldb.SBStringList() | |
| value_list = self.dbg.GetInternalVariableValue( | |
| property_name, self.dbg.GetInstanceName() | |
| ) | |
| self.assertEqual(value_list.GetSize(), 1) | |
| self.assertEqual(value_list[0], value_list.GetStringAtIndex(0)) | |
| try: | |
| return int(value_list.GetStringAtIndex(0)) | |
| except ValueError as error: | |
| self.fail("Value is not a number: " + error) | |
| # Get global property value while there are no processes. | |
| global_cache_line_size = get_cache_line_size() | |
| # Run a process via SB interface. CommandInterpreter's execution context | |
| # remains empty. | |
| error = lldb.SBError() | |
| launch_info = lldb.SBLaunchInfo(None) | |
| launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry) | |
| process = target.Launch(launch_info, error) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| # This should change the value of a process's local property. | |
| new_cache_line_size = global_cache_line_size + 512 | |
| error = self.dbg.SetInternalVariable( | |
| property_name, str(new_cache_line_size), self.dbg.GetInstanceName() | |
| ) | |
| self.assertSuccess(error, property_name + " value was changed successfully") | |
| # Check that it was set actually. | |
| self.assertEqual(get_cache_line_size(), new_cache_line_size) | |
| # Run any command to initialize CommandInterpreter's execution context. | |
| self.runCmd("target list") | |
| # Test the local property again, is it set to new_cache_line_size? | |
| self.assertEqual(get_cache_line_size(), new_cache_line_size) | |
| @expectedFailureAll( | |
| remote=True, | |
| bugnumber="github.com/llvm/llvm-project/issues/92419", | |
| ) | |
| def test_CreateTarget_platform(self): | |
| exe = self.getBuildArtifact("a.out") | |
| self.yaml2obj("elf.yaml", exe) | |
| error = lldb.SBError() | |
| target1 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) | |
| self.assertSuccess(error) | |
| platform1 = target1.GetPlatform() | |
| platform1.SetWorkingDirectory("/foo/bar") | |
| # Reuse a platform if it matches the currently selected one... | |
| target2 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) | |
| self.assertSuccess(error) | |
| platform2 = target2.GetPlatform() | |
| self.assertTrue( | |
| platform2.GetWorkingDirectory().endswith("bar"), | |
| platform2.GetWorkingDirectory(), | |
| ) | |
| # ... but create a new one if it doesn't. | |
| self.dbg.SetSelectedPlatform(lldb.SBPlatform("remote-windows")) | |
| target3 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) | |
| self.assertSuccess(error) | |
| platform3 = target3.GetPlatform() | |
| self.assertIsNone(platform3.GetWorkingDirectory()) | |
| def test_CreateTarget_arch(self): | |
| exe = self.getBuildArtifact("a.out") | |
| if lldbplatformutil.getHostPlatform() == "linux": | |
| self.yaml2obj("macho.yaml", exe) | |
| arch = "x86_64-apple-macosx" | |
| expected_platform = "remote-macosx" | |
| else: | |
| self.yaml2obj("elf.yaml", exe) | |
| arch = "x86_64-pc-linux" | |
| expected_platform = "remote-linux" | |
| fbsd = lldb.SBPlatform("remote-freebsd") | |
| self.dbg.SetSelectedPlatform(fbsd) | |
| error = lldb.SBError() | |
| target1 = self.dbg.CreateTarget(exe, arch, None, False, error) | |
| self.assertSuccess(error) | |
| platform1 = target1.GetPlatform() | |
| self.assertEqual(platform1.GetName(), expected_platform) | |
| platform1.SetWorkingDirectory("/foo/bar") | |
| # Reuse a platform even if it is not currently selected. | |
| self.dbg.SetSelectedPlatform(fbsd) | |
| target2 = self.dbg.CreateTarget(exe, arch, None, False, error) | |
| self.assertSuccess(error) | |
| platform2 = target2.GetPlatform() | |
| self.assertEqual(platform2.GetName(), expected_platform) | |
| self.assertTrue( | |
| platform2.GetWorkingDirectory().endswith("bar"), | |
| platform2.GetWorkingDirectory(), | |
| ) | |
| def test_SetDestroyCallback(self): | |
| destroy_dbg_id = None | |
| def foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal destroy_dbg_id | |
| destroy_dbg_id = dbg_id | |
| self.dbg.SetDestroyCallback(foo) | |
| original_dbg_id = self.dbg.GetID() | |
| self.dbg.Destroy(self.dbg) | |
| self.assertEqual(destroy_dbg_id, original_dbg_id) | |
| def test_AddDestroyCallback(self): | |
| original_dbg_id = self.dbg.GetID() | |
| called = [] | |
| def foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal called | |
| called += [('foo', dbg_id)] | |
| def bar(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal called | |
| called += [('bar', dbg_id)] | |
| token_foo = self.dbg.AddDestroyCallback(foo) | |
| token_bar = self.dbg.AddDestroyCallback(bar) | |
| self.dbg.Destroy(self.dbg) | |
| # Should call both `foo()` and `bar()`. | |
| self.assertEqual(called, [ | |
| ('foo', original_dbg_id), | |
| ('bar', original_dbg_id), | |
| ]) | |
| def test_RemoveDestroyCallback(self): | |
| original_dbg_id = self.dbg.GetID() | |
| called = [] | |
| def foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal called | |
| called += [('foo', dbg_id)] | |
| def bar(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal called | |
| called += [('bar', dbg_id)] | |
| token_foo = self.dbg.AddDestroyCallback(foo) | |
| token_bar = self.dbg.AddDestroyCallback(bar) | |
| ret = self.dbg.RemoveDestroyCallback(token_foo) | |
| self.dbg.Destroy(self.dbg) | |
| # `Remove` should be successful | |
| self.assertTrue(ret) | |
| # Should only call `bar()` | |
| self.assertEqual(called, [('bar', original_dbg_id)]) | |
| def test_RemoveDestroyCallback_invalid_token(self): | |
| original_dbg_id = self.dbg.GetID() | |
| magic_token_that_should_not_exist = 32413 | |
| called = [] | |
| def foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal called | |
| called += [('foo', dbg_id)] | |
| token_foo = self.dbg.AddDestroyCallback(foo) | |
| ret = self.dbg.RemoveDestroyCallback(magic_token_that_should_not_exist) | |
| self.dbg.Destroy(self.dbg) | |
| # `Remove` should be unsuccessful | |
| self.assertFalse(ret) | |
| # Should call `foo()` | |
| self.assertEqual(called, [('foo', original_dbg_id)]) | |
| def test_HandleDestroyCallback(self): | |
| """ | |
| Validates: | |
| 1. AddDestroyCallback and RemoveDestroyCallback work during debugger destroy. | |
| 2. HandleDestroyCallback invokes all callbacks in FIFO order. | |
| """ | |
| original_dbg_id = self.dbg.GetID() | |
| events = [] | |
| bar_token = None | |
| def foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal events | |
| events.append(('foo called', dbg_id)) | |
| def bar(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal events | |
| events.append(('bar called', dbg_id)) | |
| def add_foo(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal events | |
| events.append(('add_foo called', dbg_id)) | |
| events.append(('foo token', self.dbg.AddDestroyCallback(foo))) | |
| def remove_bar(dbg_id): | |
| # Need nonlocal to modify closure variable. | |
| nonlocal events | |
| events.append(('remove_bar called', dbg_id)) | |
| events.append(('remove bar ret', self.dbg.RemoveDestroyCallback(bar_token))) | |
| # Setup | |
| events.append(('add_foo token', self.dbg.AddDestroyCallback(add_foo))) | |
| bar_token = self.dbg.AddDestroyCallback(bar) | |
| events.append(('bar token', bar_token)) | |
| events.append(('remove_bar token', self.dbg.AddDestroyCallback(remove_bar))) | |
| # Destroy | |
| self.dbg.Destroy(self.dbg) | |
| self.assertEqual(events, [ | |
| # Setup | |
| ('add_foo token', 0), # add_foo should be added | |
| ('bar token', 1), # bar should be added | |
| ('remove_bar token', 2), # remove_bar should be added | |
| # Destroy | |
| ('add_foo called', original_dbg_id), # add_foo should be called | |
| ('foo token', 3), # foo should be added | |
| ('bar called', original_dbg_id), # bar should be called | |
| ('remove_bar called', original_dbg_id), # remove_bar should be called | |
| ('remove bar ret', False), # remove_bar should fail, because it's already invoked and removed | |
| ('foo called', original_dbg_id), # foo should be called | |
| ]) | |
| def test_version(self): | |
| instance_str = self.dbg.GetVersionString() | |
| class_str = lldb.SBDebugger.GetVersionString() | |
| property_str = lldb.SBDebugger.version | |
| 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() | |
| ) |