mirror of
https://github.com/intel/llvm.git
synced 2026-01-13 19:08:21 +08:00
### 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 ```
444 lines
17 KiB
Python
444 lines
17 KiB
Python
"""
|
|
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
|
|
|
|
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)
|
|
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()
|
|
)
|