From ec456ba9ca0a097da63daafbb031cb2024f5513a Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Mon, 23 Oct 2023 15:42:59 -0700 Subject: [PATCH] [lldb] Add OperatingSystem base class to the lldb python module This patch introduces an `OperatingSystem` base implementation in the `lldb` python module to make it easier for lldb users to write their own implementation. The `OperatingSystem` base implementation is derived itself from the `ScriptedThread` base implementation since they share some common grounds. To achieve that, this patch makes changes to the `ScriptedThread` initializer since it gets called by the `OperatingSystem` initializer. I also took the opportunity to document the `OperatingSystem` base class and methods. Differential Revision: https://reviews.llvm.org/D159315 Signed-off-by: Med Ismail Bennani --- lldb/bindings/python/CMakeLists.txt | 3 +- .../python/templates/operating_system.py | 103 ++++++++++++++++++ .../python/templates/scripted_process.py | 26 +++-- .../python_os_plugin/operating_system.py | 40 +------ 4 files changed, 124 insertions(+), 48 deletions(-) create mode 100644 lldb/examples/python/templates/operating_system.py diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index c4806bda27049..c941f764dfc92 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -104,7 +104,8 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "plugins" FILES "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" - "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py") + "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" + "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py") if(APPLE) create_python_package( diff --git a/lldb/examples/python/templates/operating_system.py b/lldb/examples/python/templates/operating_system.py new file mode 100644 index 0000000000000..a8053bcaa21af --- /dev/null +++ b/lldb/examples/python/templates/operating_system.py @@ -0,0 +1,103 @@ +from abc import abstractmethod + +import lldb +import struct + +from lldb.plugins.scripted_process import ScriptedThread + + +class OperatingSystem(ScriptedThread): + """ + Class that provides data for an instance of a LLDB 'OperatingSystemPython' plug-in class. + + ``` + thread_info = { + "tid": tid, + "name": "four", + "queue": "queue4", + "state": "stopped", + "stop_reason": "none", + "core" : 2 + } + ``` + + - tid : thread ID (mandatory) + - name : thread name (optional key/value pair) + - queue : thread dispatch queue name (optional key/value pair) + - state : thread state (mandatory, set to 'stopped' for now) + - core : the index of the core (lldb) thread that this OS Thread should shadow + - stop_reason : thread stop reason. (mandatory, usually set to 'none') + Possible values include: + - 'breakpoint': thread is stopped at a breakpoint + - 'none': thread is stopped because the process is stopped + - 'trace': thread is stopped after single stepping + The usual value for this while threads are in memory is 'none' + - register_data_addr : the address of the register data in memory (optional key/value pair) + Specifying this key/value pair for a thread will avoid a call to get_register_data() + and can be used when your registers are in a thread context structure that is contiguous + in memory. Don't specify this if your register layout in memory doesn't match the layout + described by the dictionary returned from a call to the get_register_info() method. + """ + + def __init__(self, process): + """Initialization needs a valid lldb.SBProcess object. This plug-in + will get created after a live process is valid and has stopped for the + first time. + + Args: + process (lldb.SBProcess): The process owning this thread. + """ + self.registers = None + super().__init__(process, None) + self.registers = self.register_info + self.threads = [] + + def create_thread(self, tid, context): + """Lazily create an operating system thread using a thread information + dictionary and an optional operating system thread context address. + This method is called manually, using the SBAPI + `lldb.SBProcess.CreateOSPluginThread` affordance. + + Args: + tid (int): Thread ID to get `thread_info` dictionary for. + context (int): Address of the operating system thread struct. + + Returns: + Dict: The `thread_info` dictionary containing the various information + for lldb to create a Thread object and add it to the process thread list. + """ + return None + + @abstractmethod + def get_thread_info(self): + """Get the list of operating system threads. This method gets called + automatically every time the process stops and it needs to update its + thread list. + + Returns: + List[thread_info]: A list of `os_thread` dictionaries + containing at least for each entry, the thread id, it's name, + queue, state, stop reason. It can also contain a + `register_data_addr`. The list can be empty. + """ + pass + + @abstractmethod + def get_register_data(self, tid): + """Get the operating system thread register context for given a thread + id. This method is called when unwinding the stack of one of the + operating system threads. + + Args: + tid (int): Thread ID to get register context for. + + Returns: + str: A byte representing all register's value. + """ + pass + + def get_register_context(self): + pass + + def get_stop_reason(self): + pass diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py index d74ef02dec859..3ddcebd128eaa 100644 --- a/lldb/examples/python/templates/scripted_process.py +++ b/lldb/examples/python/templates/scripted_process.py @@ -244,16 +244,16 @@ class ScriptedThread(metaclass=ABCMeta): """ @abstractmethod - def __init__(self, scripted_process, args): + def __init__(self, process, args): """Construct a scripted thread. Args: - process (ScriptedProcess): The scripted process owning this thread. + process (ScriptedProcess/lldb.SBProcess): The process owning this thread. args (lldb.SBStructuredData): A Dictionary holding arbitrary key/value pairs used by the scripted thread. """ self.target = None - self.scripted_process = None + self.originating_process = None self.process = None self.args = None self.idx = 0 @@ -268,9 +268,13 @@ def __init__(self, scripted_process, args): self.frames = [] self.extended_info = [] - if isinstance(scripted_process, ScriptedProcess): - self.target = scripted_process.target - self.scripted_process = scripted_process + if ( + isinstance(process, ScriptedProcess) + or isinstance(process, lldb.SBProcess) + and process.IsValid() + ): + self.target = process.target + self.originating_process = process self.process = self.target.GetProcess() self.get_register_info() @@ -354,14 +358,14 @@ def get_stackframes(self): def get_register_info(self): if self.register_info is None: self.register_info = dict() - if self.scripted_process.arch == "x86_64": + if self.originating_process.arch == "x86_64": self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = INTEL64_GPR - elif "arm64" in self.scripted_process.arch: + elif "arm64" in self.originating_process.arch: self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = ARM64_GPR else: - raise ValueError("Unknown architecture", self.scripted_process.arch) + raise ValueError("Unknown architecture", self.originating_process.arch) return self.register_info @abstractmethod @@ -505,12 +509,12 @@ def get_stop_reason(self): # TODO: Passthrough stop reason from driving process if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: - if "arm64" in self.scripted_process.arch: + if "arm64" in self.originating_process.arch: stop_reason["type"] = lldb.eStopReasonException stop_reason["data"][ "desc" ] = self.driving_thread.GetStopDescription(100) - elif self.scripted_process.arch == "x86_64": + elif self.originating_process.arch == "x86_64": stop_reason["type"] = lldb.eStopReasonSignal stop_reason["data"]["signal"] = signal.SIGTRAP else: diff --git a/lldb/test/API/functionalities/plugins/python_os_plugin/operating_system.py b/lldb/test/API/functionalities/plugins/python_os_plugin/operating_system.py index 52c678fac2efe..f4404d78492f9 100644 --- a/lldb/test/API/functionalities/plugins/python_os_plugin/operating_system.py +++ b/lldb/test/API/functionalities/plugins/python_os_plugin/operating_system.py @@ -1,29 +1,14 @@ -#!/usr/bin/env python - import lldb import struct +from lldb.plugins.operating_system import OperatingSystem + -class OperatingSystemPlugIn(object): +class OperatingSystemPlugIn(OperatingSystem): """Class that provides data for an instance of a LLDB 'OperatingSystemPython' plug-in class""" def __init__(self, process): - """Initialization needs a valid.SBProcess object. - - This plug-in will get created after a live process is valid and has stopped for the - first time.""" - self.process = None - self.registers = None - self.threads = None - if isinstance(process, lldb.SBProcess) and process.IsValid(): - self.process = process - self.threads = None # Will be an dictionary containing info for each thread - - def get_target(self): - # NOTE: Don't use "lldb.target" when trying to get your target as the "lldb.target" - # tracks the current target in the LLDB command interpreter which isn't the - # correct thing to use for this plug-in. - return self.process.target + super().__init__(process) def create_thread(self, tid, context): if tid == 0x444444444: @@ -40,23 +25,6 @@ def create_thread(self, tid, context): def get_thread_info(self): if not self.threads: - # The sample dictionary below shows the values that can be returned for a thread - # tid => thread ID (mandatory) - # name => thread name (optional key/value pair) - # queue => thread dispatch queue name (optional key/value pair) - # state => thred state (mandatory, set to 'stopped' for now) - # stop_reason => thread stop reason. (mandatory, usually set to 'none') - # Possible values include: - # 'breakpoint' if the thread is stopped at a breakpoint - # 'none' thread is just stopped because the process is stopped - # 'trace' the thread just single stepped - # The usual value for this while threads are in memory is 'none' - # register_data_addr => the address of the register data in memory (optional key/value pair) - # Specifying this key/value pair for a thread will avoid a call to get_register_data() - # and can be used when your registers are in a thread context structure that is contiguous - # in memory. Don't specify this if your register layout in memory doesn't match the layout - # described by the dictionary returned from a call to the - # get_register_info() method. self.threads = [ { "tid": 0x111111111,