diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -104,6 +104,13 @@ FILES "${LLDB_SOURCE_DIR}/examples/python/in_call_stack.py" "${LLDB_SOURCE_DIR}/examples/python/symbolication.py") + create_python_package( + ${swig_target} + ${lldb_python_target_dir} + "plugins" + FILES + "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_process.py") + if(APPLE) create_python_package( ${swig_target} diff --git a/lldb/examples/python/scripted_process/my_scripted_process.py b/lldb/examples/python/scripted_process/my_scripted_process.py new file mode 100644 --- /dev/null +++ b/lldb/examples/python/scripted_process/my_scripted_process.py @@ -0,0 +1,42 @@ +import os + +import lldb +from lldb.plugins.scripted_process import ScriptedProcess + +class MyScriptedProcess(ScriptedProcess): + def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData): + super().__init__(target, args) + + def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo: + return self.memory_regions[0] + + def get_thread_with_id(self, tid: int): + return {} + + def get_registers_for_thread(self, tid: int): + return {} + + def read_memory_at_address(self, addr: int, size: int) -> lldb.SBData: + data = lldb.SBData().CreateDataFromCString( + self.target.GetByteOrder(), + self.target.GetCodeByteSize(), + "Hello, world!") + return data + + def get_loaded_images(self): + return self.loaded_images + + def get_process_id(self) -> int: + return 42 + + def is_alive(self) -> bool: + return True + +def __lldb_init_module(debugger, dict): + if not 'SKIP_SCRIPTED_PROCESS_LAUNCH' in os.environ: + debugger.HandleCommand( + "process launch -C %s.%s" % (__name__, + MyScriptedProcess.__name__)) + else: + print("Name of the class that will manage the scripted process: '%s.%s'" + % (__name__, MyScriptedProcess.__name__)) \ No newline at end of file diff --git a/lldb/examples/python/scripted_process/scripted_process.py b/lldb/examples/python/scripted_process/scripted_process.py new file mode 100644 --- /dev/null +++ b/lldb/examples/python/scripted_process/scripted_process.py @@ -0,0 +1,147 @@ +from abc import ABCMeta, abstractmethod +import six + +import lldb + +@six.add_metaclass(ABCMeta) +class ScriptedProcess: + + """ + The base class for a scripted process. + + Most of the base class methods are `@abstractmethod` that need to be + overwritten by the inheriting class. + + DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE. + THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE. + """ + + @abstractmethod + def __init__(self, target, args): + """ Construct a scripted process. + + Args: + target (lldb.SBTarget): The target launching the scripted process. + args (lldb.SBStructuredData): A Dictionary holding arbitrary + key/value pairs used by the scripted process. + """ + self.target = None + self.args = None + if isinstance(target, lldb.SBTarget) and target.IsValid(): + self.target = target + if isinstance(args, lldb.SBStructuredData) and args.IsValid(): + self.args = args + + @abstractmethod + def get_memory_region_containing_address(addr): + """ Get the memory region for the scripted process, containing a + specific address. + + Args: + addr (int): Address to look for in the scripted process memory + regions. + + Returns: + lldb.SBMemoryRegionInfo: The memory region containing the address. + None if out of bounds. + """ + pass + + @abstractmethod + def get_thread_with_id(tid): + """ Get the scripted process thread with a specific ID. + + Args: + tid (int): Thread ID to look for in the scripted process. + + Returns: + Dict: The thread represented as a dictionary, withr the + tid thread ID. None if tid doesn't match any of the scripted + process threads. + """ + pass + + @abstractmethod + def get_registers_for_thread(tid): + """ Get the register context dictionary for a certain thread of + the scripted process. + + Args: + tid (int): Thread ID for the thread's register context. + + Returns: + Dict: The register context represented as a dictionary, for the + tid thread. None if tid doesn't match any of the scripted + process threads. + """ + pass + + @abstractmethod + def read_memory_at_address(addr, size): + """ Get a memory buffer from the scripted process at a certain address, + of a certain size. + + Args: + addr (int): Address from which we should start reading. + size (int): Size of the memory to read. + + Returns: + lldb.SBData: An `lldb.SBData` buffer with the target byte size and + byte order storing the memory read. + """ + pass + + @abstractmethod + def get_loaded_images(self): + """ Get the list of loaded images for the scripted process. + + ``` + class ScriptedProcessImage: + def __init__(name, file_spec, uuid, load_address): + self.name = name + self.file_spec = file_spec + self.uuid = uuid + self.load_address = load_address + ``` + + Returns: + List[ScriptedProcessImage]: A list of `ScriptedProcessImage` + containing for each entry, the name of the library, a UUID, + an `lldb.SBFileSpec` and a load address. + None if the list is empty. + """ + pass + + def get_process_id(self): + """ Get the scripted process identifier. + + Returns: + int: The scripted process identifier. + """ + return 0 + + + def launch(self): + """ Simulate the scripted process launch. + + Returns: + lldb.SBError: An `lldb.SBError` with error code 0. + """ + return lldb.SBError() + + def resume(self): + """ Simulate the scripted process resume. + + Returns: + lldb.SBError: An `lldb.SBError` with error code 0. + """ + return lldb.SBError() + + @abstractmethod + def is_alive(self): + """ Check if the scripted process is alive. + + Returns: + bool: True if scripted process is alive. False otherwise. + """ + pass diff --git a/lldb/test/API/functionalities/scripted_process/Makefile b/lldb/test/API/functionalities/scripted_process/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_process/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c + +include Makefile.rules + diff --git a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py @@ -0,0 +1,45 @@ +""" +Test python scripted process in lldb +""" + +import os + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test import lldbtest + + +class PlatformProcessCrashInfoTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + TestBase.setUp(self) + self.source = "main.c" + + def tearDown(self): + TestBase.tearDown(self) + + def test_python_plugin_package(self): + """Test that the lldb python module has a `plugins.scripted_process` + package.""" + self.expect('script import lldb.plugins', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins)', + substrs=["scripted_process"]) + + self.expect('script import lldb.plugins.scripted_process', + substrs=["ModuleNotFoundError"], matching=False) + + self.expect('script dir(lldb.plugins.scripted_process)', + substrs=["ScriptedProcess"]) + + self.expect('script from lldb.plugins.scripted_process import ScriptedProcess', + substrs=["ImportError"], matching=False) + + self.expect('script dir(ScriptedProcess)', + substrs=["launch"]) + diff --git a/lldb/test/API/functionalities/scripted_process/main.c b/lldb/test/API/functionalities/scripted_process/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_process/main.c @@ -0,0 +1,5 @@ +#include + +int main() { + return 0; // break here +}