This patch adds __getitem__ method to the SBAPI list classes that were missing subscript support, enabling Pythonic index access (e.g., list[0], list[-1]) in Python bindings. The implementation adds __getitem__ to the following classes: - SBStringList - SBFileSpecList - SBProcessInfoList - SBMemoryRegionInfoList - SBThreadCollection - SBBreakpointList - SBModuleSpecList - SBTypeList Each implementation follows the same pattern: - Type validation (raises TypeError for non-integer indices) - Range validation with negative index support (raises IndexError for out-of-range) - Delegates to the appropriate Get*AtIndex() method The changes are in SWIG interface extension files (.i), implementing the __getitem__ method in Python bindings while maintaining compatibility with existing Get*AtIndex() API. Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
445 lines
17 KiB
Python
445 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)
|
|
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()
|
|
)
|