blob: 7a72f1a24c9da3fabb527452a9f76215a1d1bf09 [file]
from abc import ABCMeta, abstractmethod
import lldb
class ScriptedFrameProvider(metaclass=ABCMeta):
"""
The base class for a scripted frame provider.
A scripted frame provider allows you to provide custom stack frames for a
thread, which can be used to augment or replace the standard unwinding
mechanism. This is useful for:
- Providing frames for custom calling conventions or languages
- Reconstructing missing frames from crash dumps or core files
- Adding diagnostic or synthetic frames for debugging
- Visualizing state machines or async execution contexts
Most of the base class methods are `@abstractmethod` that need to be
overwritten by the inheriting class.
Example usage:
.. code-block:: python
# Attach a frame provider to a thread
thread = process.GetSelectedThread()
error = thread.SetScriptedFrameProvider(
"my_module.MyFrameProvider",
lldb.SBStructuredData()
)
"""
@staticmethod
def applies_to_thread(thread):
"""Determine if this frame provider should be used for a given thread.
This static method is called before creating an instance of the frame
provider to determine if it should be applied to a specific thread.
Override this method to provide custom filtering logic.
Args:
thread (lldb.SBThread): The thread to check.
Returns:
bool: True if this frame provider should be used for the thread,
False otherwise. The default implementation returns True for
all threads.
Example:
.. code-block:: python
@staticmethod
def applies_to_thread(thread):
# Only apply to thread 1
return thread.GetIndexID() == 1
"""
return True
@staticmethod
@abstractmethod
def get_description():
"""Get a description of this frame provider.
This method should return a human-readable string describing what
this frame provider does. The description is used for debugging
and display purposes.
Returns:
str: A description of the frame provider.
Example:
.. code-block:: python
def get_description(self):
return "Crash log frame provider for thread 1"
"""
pass
def __init__(self, input_frames, args):
"""Construct a scripted frame provider.
Args:
input_frames (lldb.SBFrameList): The frame list to use as input.
This allows you to access frames by index. The frames are
materialized lazily as you access them.
args (lldb.SBStructuredData): A Dictionary holding arbitrary
key/value pairs used by the scripted frame provider.
"""
self.input_frames = None
self.args = None
self.thread = None
self.target = None
self.process = None
if isinstance(input_frames, lldb.SBFrameList) and input_frames.IsValid():
self.input_frames = input_frames
self.thread = input_frames.GetThread()
if self.thread and self.thread.IsValid():
self.process = self.thread.GetProcess()
if self.process and self.process.IsValid():
self.target = self.process.GetTarget()
if isinstance(args, lldb.SBStructuredData) and args.IsValid():
self.args = args
@abstractmethod
def get_frame_at_index(self, index):
"""Get a single stack frame at the given index.
This method is called lazily when a specific frame is needed in the
thread's backtrace (e.g., via the 'bt' command). Each frame is
requested individually as needed.
Args:
index (int): The frame index to retrieve (0 for youngest/top frame).
Returns:
Dict or None: A frame dictionary describing the stack frame, or None
if no frame exists at this index. The dictionary should contain:
Required fields:
- idx (int): The synthetic frame index (0 for youngest/top frame)
- pc (int): The program counter address for the synthetic frame
Alternatively, you can return:
- A ScriptedFrame object for full control over frame behavior
- An integer representing an input frame index to reuse
- None to indicate no more frames exist
Example:
.. code-block:: python
def get_frame_at_index(self, index):
# Return None when there are no more frames
if index >= self.total_frames:
return None
# Re-use an input frame by returning its index
if self.should_use_input_frame(index):
return index # Returns input frame at this index
# Or create a custom frame dictionary
if index == 0:
return {
"idx": 0,
"pc": 0x100001234,
}
return None
Note:
The frames are indexed from 0 (youngest/top) to N (oldest/bottom).
This method will be called repeatedly with increasing indices until
None is returned.
"""
pass