Skip to content
Merged
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
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
113 changes: 113 additions & 0 deletions lldb/examples/python/templates/scripted_frame_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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, 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
14 changes: 14 additions & 0 deletions lldb/include/lldb/API/SBFrameList.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@

#include "lldb/API/SBDefines.h"

namespace lldb_private {
class ScriptInterpreter;
namespace python {
class SWIGBridge;
}
namespace lua {
class SWIGBridge;
}
} // namespace lldb_private

namespace lldb {

/// Represents a list of SBFrame objects.
Expand Down Expand Up @@ -66,6 +76,10 @@ class LLDB_API SBFrameList {
protected:
friend class SBThread;

friend class lldb_private::python::SWIGBridge;
friend class lldb_private::lua::SWIGBridge;
friend class lldb_private::ScriptInterpreter;

private:
SBFrameList(const lldb::StackFrameListSP &frame_list_sp);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H

#include "lldb/lldb-private.h"

#include "ScriptedInterface.h"

namespace lldb_private {
class ScriptedFrameProviderInterface : public ScriptedInterface {
public:
virtual llvm::Expected<StructuredData::GenericSP>
CreatePluginObject(llvm::StringRef class_name,
lldb::StackFrameListSP input_frames,
StructuredData::DictionarySP args_sp) = 0;

virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) {
return {};
}
};
} // namespace lldb_private

#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
10 changes: 10 additions & 0 deletions lldb/include/lldb/Interpreter/ScriptInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "lldb/API/SBError.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBExecutionContext.h"
#include "lldb/API/SBFrameList.h"
#include "lldb/API/SBLaunchInfo.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
Expand All @@ -28,6 +29,7 @@
#include "lldb/Host/StreamFile.h"
#include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
Expand Down Expand Up @@ -537,6 +539,11 @@ class ScriptInterpreter : public PluginInterface {
return {};
}

virtual lldb::ScriptedFrameProviderInterfaceSP
CreateScriptedFrameProviderInterface() {
return {};
}

virtual lldb::ScriptedThreadPlanInterfaceSP
CreateScriptedThreadPlanInterface() {
return {};
Expand Down Expand Up @@ -596,6 +603,9 @@ class ScriptInterpreter : public PluginInterface {
lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext(
const lldb::SBExecutionContext &exe_ctx) const;

lldb::StackFrameListSP
GetOpaqueTypeFromSBFrameList(const lldb::SBFrameList &exe_ctx) const;

protected:
Debugger &m_debugger;
lldb::ScriptLanguage m_script_lang;
Expand Down
3 changes: 3 additions & 0 deletions lldb/include/lldb/lldb-forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ class Scalar;
class ScriptInterpreter;
class ScriptInterpreterLocker;
class ScriptedFrameInterface;
class ScriptedFrameProviderInterface;
class ScriptedMetadata;
class ScriptedBreakpointInterface;
class ScriptedPlatformInterface;
Expand Down Expand Up @@ -412,6 +413,8 @@ typedef std::shared_ptr<lldb_private::ScriptSummaryFormat>
typedef std::shared_ptr<lldb_private::ScriptInterpreter> ScriptInterpreterSP;
typedef std::shared_ptr<lldb_private::ScriptedFrameInterface>
ScriptedFrameInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedFrameProviderInterface>
ScriptedFrameProviderInterfaceSP;
typedef std::shared_ptr<lldb_private::SyntheticFrameProvider>
SyntheticFrameProviderSP;
typedef std::shared_ptr<lldb_private::ScriptedMetadata> ScriptedMetadataSP;
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Interpreter/ScriptInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext(
return exe_ctx.m_exe_ctx_sp;
}

lldb::StackFrameListSP ScriptInterpreter::GetOpaqueTypeFromSBFrameList(
const lldb::SBFrameList &frame_list) const {
return frame_list.m_opaque_sp;
}

lldb::ScriptLanguage
ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
if (language.equals_insensitive(LanguageToString(eScriptLanguageNone)))
Expand Down
1 change: 0 additions & 1 deletion lldb/source/Plugins/Process/scripted/ScriptedFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
#define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H

#include "Plugins/Process/Utility/RegisterContextMemory.h"
#include "ScriptedThread.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Target/DynamicRegisterInfo.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN
OperatingSystemPythonInterface.cpp
ScriptInterpreterPythonInterfaces.cpp
ScriptedFramePythonInterface.cpp
ScriptedFrameProviderPythonInterface.cpp
ScriptedPlatformPythonInterface.cpp
ScriptedProcessPythonInterface.cpp
ScriptedPythonInterface.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "OperatingSystemPythonInterface.h"
#include "ScriptedBreakpointPythonInterface.h"
#include "ScriptedFrameProviderPythonInterface.h"
#include "ScriptedFramePythonInterface.h"
#include "ScriptedPlatformPythonInterface.h"
#include "ScriptedProcessPythonInterface.h"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Host/Config.h"
#include "lldb/Target/Thread.h"
#include "lldb/Utility/Log.h"
#include "lldb/lldb-enumerations.h"

#if LLDB_ENABLE_PYTHON

// LLDB Python header must be included first
#include "../lldb-python.h"

#include "../SWIGPythonBridge.h"
#include "../ScriptInterpreterPythonImpl.h"
#include "ScriptedFrameProviderPythonInterface.h"
#include <optional>

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::python;
using Locker = ScriptInterpreterPythonImpl::Locker;

ScriptedFrameProviderPythonInterface::ScriptedFrameProviderPythonInterface(
ScriptInterpreterPythonImpl &interpreter)
: ScriptedFrameProviderInterface(), ScriptedPythonInterface(interpreter) {}

llvm::Expected<StructuredData::GenericSP>
ScriptedFrameProviderPythonInterface::CreatePluginObject(
const llvm::StringRef class_name, lldb::StackFrameListSP input_frames,
StructuredData::DictionarySP args_sp) {
if (!input_frames)
return llvm::createStringError("Invalid frame list");

StructuredDataImpl sd_impl(args_sp);
return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr,
input_frames, sd_impl);
}

StructuredData::ObjectSP
ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) {
Status error;
StructuredData::ObjectSP obj = Dispatch("get_frame_at_index", error, index);

if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
error))
return {};

return obj;
}

#endif
Loading
Loading