Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lldb/bindings/interface/SBFrameListDocstrings.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
%feature("docstring",
"Represents a list of :py:class:`SBFrame` objects."
) lldb::SBFrameList;

%feature("autodoc", "GetSize(SBFrameList self) -> uint32_t") lldb::SBFrameList::GetSize;
%feature("docstring", "
Returns the number of frames in the list."
) lldb::SBFrameList::GetSize;

%feature("autodoc", "GetFrameAtIndex(SBFrameList self, uint32_t idx) -> SBFrame") lldb::SBFrameList::GetFrameAtIndex;
%feature("docstring", "
Returns the frame at the given index."
) lldb::SBFrameList::GetFrameAtIndex;
46 changes: 46 additions & 0 deletions lldb/bindings/interface/SBFrameListExtensions.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
%extend lldb::SBFrameList {

#ifdef SWIGPYTHON
%nothreadallow;
#endif
std::string lldb::SBFrameList::__str__ (){
lldb::SBStream description;
const size_t n = $self->GetSize();
if (n)
{
for (size_t i=0; i<n; ++i)
$self->GetFrameAtIndex(i).GetDescription(description);
}
else
{
description.Printf("<empty> lldb.SBFrameList()");
}
const char *desc = description.GetData();
size_t desc_len = description.GetSize();
if (desc_len > 0 && (desc[desc_len-1] == '\n' || desc[desc_len-1] == '\r'))
--desc_len;
return std::string(desc, desc_len);
}
#ifdef SWIGPYTHON
%clearnothreadallow;
#endif

#ifdef SWIGPYTHON
%pythoncode %{
def __iter__(self):
'''Iterate over all frames in a lldb.SBFrameList object.'''
return lldb_iter(self, 'GetSize', 'GetFrameAtIndex')

def __len__(self):
return int(self.GetSize())

def __getitem__(self, key):
count = len(self)
if type(key) is int:
if -count <= key < count:
key %= count
return self.GetFrameAtIndex(key)
return None
%}
#endif
}
3 changes: 2 additions & 1 deletion lldb/bindings/interface/SBThreadExtensions.i
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ STRING_EXTENSION_OUTSIDE(SBThread)
def get_thread_frames(self):
'''An accessor function that returns a list() that contains all frames in a lldb.SBThread object.'''
frames = []
for frame in self:
frame_list = self.GetFrames()
for frame in frame_list:
frames.append(frame)
return frames

Expand Down
3 changes: 3 additions & 0 deletions lldb/bindings/interfaces.swig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
%include "./interface/SBFileSpecListDocstrings.i"
%include "./interface/SBFormatDocstrings.i"
%include "./interface/SBFrameDocstrings.i"
%include "./interface/SBFrameListDocstrings.i"
%include "./interface/SBFunctionDocstrings.i"
%include "./interface/SBHostOSDocstrings.i"
%include "./interface/SBInstructionDocstrings.i"
Expand Down Expand Up @@ -118,6 +119,7 @@
%include "lldb/API/SBFileSpecList.h"
%include "lldb/API/SBFormat.h"
%include "lldb/API/SBFrame.h"
%include "lldb/API/SBFrameList.h"
%include "lldb/API/SBFunction.h"
%include "lldb/API/SBHostOS.h"
%include "lldb/API/SBInstruction.h"
Expand Down Expand Up @@ -192,6 +194,7 @@
%include "./interface/SBFileSpecExtensions.i"
%include "./interface/SBFileSpecListExtensions.i"
%include "./interface/SBFrameExtensions.i"
%include "./interface/SBFrameListExtensions.i"
%include "./interface/SBFunctionExtensions.i"
%include "./interface/SBInstructionExtensions.i"
%include "./interface/SBInstructionListExtensions.i"
Expand Down
1 change: 1 addition & 0 deletions lldb/bindings/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
"plugins"
FILES
"${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py"
Expand Down
5 changes: 5 additions & 0 deletions lldb/bindings/python/python-swigsafecast.swig
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ PythonObject SWIGBridge::ToSWIGWrapper(lldb::ThreadPlanSP thread_plan_sp) {
SWIGTYPE_p_lldb__SBThreadPlan);
}

PythonObject SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP frames_sp) {
return ToSWIGHelper(new lldb::SBFrameList(std::move(frames_sp)),
SWIGTYPE_p_lldb__SBFrameList);
}

PythonObject SWIGBridge::ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp) {
return ToSWIGHelper(new lldb::SBBreakpoint(std::move(breakpoint_sp)),
SWIGTYPE_p_lldb__SBBreakpoint);
Expand Down
12 changes: 12 additions & 0 deletions lldb/bindings/python/python-wrapper.swig
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyOb
return sb_ptr;
}

void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) {
lldb::SBFrameList *sb_ptr = NULL;

int valid_cast = SWIG_ConvertPtr(data, (void **)&sb_ptr,
SWIGTYPE_p_lldb__SBFrameList, 0);

if (valid_cast == -1)
return NULL;

return sb_ptr;
}

bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand(
const char *python_function_name, const char *session_dictionary_name,
lldb::DebuggerSP debugger, const char *args,
Expand Down
154 changes: 154 additions & 0 deletions lldb/examples/python/templates/scripted_frame_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
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()
)
"""

@abstractmethod
def __init__(self, thread, args):
"""Construct a scripted frame provider.

Args:
thread (lldb.SBThread): The thread for which to provide frames.
args (lldb.SBStructuredData): A Dictionary holding arbitrary
key/value pairs used by the scripted frame provider.
"""
self.thread = None
self.args = None
self.target = None
self.process = None

if isinstance(thread, lldb.SBThread) and thread.IsValid():
self.thread = thread
self.process = 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

def get_merge_strategy(self):
"""Get the merge strategy for how scripted frames should be integrated.

The merge strategy determines how the scripted frames are combined with the
real unwound frames from the thread's normal unwinder.

Returns:
int: One of the following lldb.ScriptedFrameProviderMergeStrategy values:

- lldb.eScriptedFrameProviderMergeStrategyReplace: Replace the entire stack
with scripted frames. The thread will only show frames provided
by this provider.

- lldb.eScriptedFrameProviderMergeStrategyPrepend: Prepend scripted frames
before the real unwound frames. Useful for adding synthetic frames
at the top of the stack while preserving the actual callstack below.

- lldb.eScriptedFrameProviderMergeStrategyAppend: Append scripted frames
after the real unwound frames. Useful for showing additional context
after the actual callstack ends.

- lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex: Replace specific
frames at given indices with scripted frames, keeping other real frames
intact. The idx field in each frame dictionary determines which real
frame to replace (e.g., idx=0 replaces frame 0, idx=2 replaces frame 2).

The default implementation returns Replace strategy.

Example:

.. code-block:: python

def get_merge_strategy(self):
# Only show our custom frames
return lldb.eScriptedFrameProviderMergeStrategyReplace

def get_merge_strategy(self):
# Add diagnostic frames on top of real stack
return lldb.eScriptedFrameProviderMergeStrategyPrepend

def get_merge_strategy(self):
# Replace frame 0 and frame 2 with custom frames, keep others
return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex
"""
return lldb.eScriptedFrameProviderMergeStrategyReplace

@abstractmethod
def get_stackframes(self, real_frames):
"""Get the list of stack frames to provide.

This method is called when the thread's backtrace is requested
(e.g., via the 'bt' command). The returned frames will be integrated
with the real frames according to the mode returned by get_mode().

Args:
real_frames (lldb.SBFrameList): The actual unwound frames from the
thread's normal unwinder. This allows you to iterate, filter,
and selectively replace frames. The frames are materialized
lazily as you access them.

Returns:
List[Dict]: A list of frame dictionaries, where each dictionary
describes a single stack frame. Each dictionary should contain:

Required fields:
- idx (int): The frame index (0 for innermost/top frame)
- pc (int): The program counter address for this frame

Alternatively, you can return a list of ScriptedFrame objects
for more control over frame behavior.

Example:

.. code-block:: python

def get_stackframes(self, real_frames):
frames = []

# Iterate over real frames and filter/augment them
for i, frame in enumerate(real_frames):
if self.should_include_frame(frame):
frames.append({
"idx": i,
"pc": frame.GetPC(),
})

# Or create custom frames
frames.append({
"idx": 0,
"pc": 0x100001234,
})

return frames

Note:
The frames are indexed from 0 (innermost/newest) to N (outermost/oldest).
"""
pass
41 changes: 29 additions & 12 deletions lldb/examples/python/templates/scripted_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ def __init__(self, process, args):
key/value pairs used by the scripted thread.
"""
self.target = None
self.arch = None
self.originating_process = None
self.process = None
self.args = None
Expand All @@ -266,6 +267,9 @@ def __init__(self, process, args):
and process.IsValid()
):
self.target = process.target
triple = self.target.triple
if triple:
self.arch = triple.split("-")[0]
self.originating_process = process
self.process = self.target.GetProcess()
self.get_register_info()
Expand Down Expand Up @@ -352,17 +356,14 @@ def get_stackframes(self):
def get_register_info(self):
if self.register_info is None:
self.register_info = dict()
if "x86_64" in self.originating_process.arch:
if "x86_64" in self.arch:
self.register_info["sets"] = ["General Purpose Registers"]
self.register_info["registers"] = INTEL64_GPR
elif (
"arm64" in self.originating_process.arch
or self.originating_process.arch == "aarch64"
):
elif "arm64" in self.arch or self.arch == "aarch64":
self.register_info["sets"] = ["General Purpose Registers"]
self.register_info["registers"] = ARM64_GPR
else:
raise ValueError("Unknown architecture", self.originating_process.arch)
raise ValueError("Unknown architecture", self.arch)
return self.register_info

@abstractmethod
Expand Down Expand Up @@ -405,11 +406,12 @@ def __init__(self, thread, args):
"""Construct a scripted frame.

Args:
thread (ScriptedThread): The thread owning this frame.
thread (ScriptedThread/lldb.SBThread): The thread owning this frame.
args (lldb.SBStructuredData): A Dictionary holding arbitrary
key/value pairs used by the scripted frame.
"""
self.target = None
self.arch = None
self.originating_thread = None
self.thread = None
self.args = None
Expand All @@ -424,10 +426,14 @@ def __init__(self, thread, args):
or isinstance(thread, lldb.SBThread)
and thread.IsValid()
):
self.target = thread.target
self.process = thread.process
self.target = self.process.target
triple = self.target.triple
if triple:
self.arch = triple.split("-")[0]
tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id
self.originating_thread = thread
self.thread = self.process.GetThreadByIndexID(thread.tid)
self.thread = self.process.GetThreadByIndexID(tid)
self.get_register_info()

@abstractmethod
Expand Down Expand Up @@ -508,7 +514,18 @@ def get_variables(self, filters):

def get_register_info(self):
if self.register_info is None:
self.register_info = self.originating_thread.get_register_info()
if isinstance(self.originating_thread, ScriptedThread):
self.register_info = self.originating_thread.get_register_info()
elif isinstance(self.originating_thread, lldb.SBThread):
self.register_info = dict()
if "x86_64" in self.arch:
self.register_info["sets"] = ["General Purpose Registers"]
self.register_info["registers"] = INTEL64_GPR
elif "arm64" in self.arch or self.arch == "aarch64":
self.register_info["sets"] = ["General Purpose Registers"]
self.register_info["registers"] = ARM64_GPR
else:
raise ValueError("Unknown architecture", self.arch)
return self.register_info

@abstractmethod
Expand Down Expand Up @@ -642,12 +659,12 @@ def get_stop_reason(self):

# TODO: Passthrough stop reason from driving process
if self.driving_thread.GetStopReason() != lldb.eStopReasonNone:
if "arm64" in self.originating_process.arch:
if "arm64" in self.arch:
stop_reason["type"] = lldb.eStopReasonException
stop_reason["data"]["desc"] = (
self.driving_thread.GetStopDescription(100)
)
elif self.originating_process.arch == "x86_64":
elif self.arch == "x86_64":
stop_reason["type"] = lldb.eStopReasonSignal
stop_reason["data"]["signal"] = signal.SIGTRAP
else:
Expand Down
Loading
Loading