Skip to content

Commit

Permalink
[lldb] Add OperatingSystem base class to the lldb python module
Browse files Browse the repository at this point in the history
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 <ismail@bennani.ma>
  • Loading branch information
medismailben committed Oct 26, 2023
1 parent 7a1e878 commit ec456ba
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 48 deletions.
3 changes: 2 additions & 1 deletion lldb/bindings/python/CMakeLists.txt
Expand Up @@ -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(
Expand Down
103 changes: 103 additions & 0 deletions 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
26 changes: 15 additions & 11 deletions lldb/examples/python/templates/scripted_process.py
Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
@@ -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:
Expand All @@ -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,
Expand Down

0 comments on commit ec456ba

Please sign in to comment.