Skip to content

Conversation

@medismailben
Copy link
Member

@medismailben medismailben commented Dec 2, 2025

This patch re-lands #161870 with fixes to the previous test failures.

rdar://161834688

Signed-off-by: Med Ismail Bennani ismail@bennani.ma

@llvmbot
Copy link
Member

llvmbot commented Dec 2, 2025

@llvm/pr-subscribers-lldb

Author: Med Ismail Bennani (medismailben)

Changes

This patch extends ScriptedFrame to work with real (non-scripted)
threads,
enabling frame providers to synthesize frames for native processes.

Previously, ScriptedFrame only worked within
ScriptedProcess/ScriptedThread
contexts. This patch decouples ScriptedFrame from ScriptedThread,
allowing
users to augment or replace stack frames in real debugging sessions for
use
cases like custom calling conventions, reconstructing corrupted frames
from
core files, or adding diagnostic frames.

Key changes:

  • ScriptedFrame::Create() now accepts ThreadSP instead of requiring
    ScriptedThread, extracting architecture from the target triple rather
    than ScriptedProcess.arch

  • Added SBTarget::RegisterScriptedFrameProvider() and
    ClearScriptedFrameProvider() APIs, with Target storing a
    SyntheticFrameProviderDescriptor template for new threads

  • Added "target frame-provider register/clear" commands for CLI access

  • Thread class gains LoadScriptedFrameProvider(),
    ClearScriptedFrameProvider(),
    and GetFrameProvider() methods for per-thread frame provider management

  • New SyntheticStackFrameList overrides FetchFramesUpTo() to lazily
    provide
    frames from either the frame provider or the real stack

This enables practical use of the SyntheticFrameProvider infrastructure
in
real debugging workflows.

rdar://161834688

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>


Patch is 158.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/170236.diff

53 Files Affected:

  • (modified) lldb/bindings/python/python-wrapper.swig (+12)
  • (modified) lldb/examples/python/templates/scripted_frame_provider.py (+47)
  • (modified) lldb/examples/python/templates/scripted_process.py (+31-16)
  • (modified) lldb/include/lldb/API/SBTarget.h (+30)
  • (modified) lldb/include/lldb/API/SBThread.h (+1)
  • (modified) lldb/include/lldb/API/SBThreadCollection.h (+1)
  • (modified) lldb/include/lldb/Core/FormatEntity.h (+1)
  • (modified) lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h (+18)
  • (modified) lldb/include/lldb/Interpreter/ScriptInterpreter.h (+3)
  • (added) lldb/include/lldb/Target/BorrowedStackFrame.h (+146)
  • (modified) lldb/include/lldb/Target/StackFrame.h (+48-36)
  • (modified) lldb/include/lldb/Target/StackFrameList.h (+30-4)
  • (modified) lldb/include/lldb/Target/SyntheticFrameProvider.h (+24-6)
  • (modified) lldb/include/lldb/Target/Target.h (+38)
  • (modified) lldb/include/lldb/Target/Thread.h (+12)
  • (modified) lldb/include/lldb/Target/ThreadSpec.h (+2)
  • (modified) lldb/include/lldb/Utility/ScriptedMetadata.h (+27)
  • (modified) lldb/include/lldb/lldb-private-interfaces.h (+2-2)
  • (modified) lldb/source/API/SBTarget.cpp (+82)
  • (modified) lldb/source/Commands/CommandObjectTarget.cpp (+200)
  • (modified) lldb/source/Core/FormatEntity.cpp (+20)
  • (modified) lldb/source/Interpreter/ScriptInterpreter.cpp (+7)
  • (modified) lldb/source/Plugins/CMakeLists.txt (+1)
  • (modified) lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp (+60-27)
  • (modified) lldb/source/Plugins/Process/scripted/ScriptedFrame.h (+33-7)
  • (modified) lldb/source/Plugins/Process/scripted/ScriptedThread.cpp (+3-3)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp (+2)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp (+57-1)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h (+21-2)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp (+13)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h (+116-5)
  • (modified) lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h (+1)
  • (added) lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt (+1)
  • (added) lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt (+12)
  • (added) lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp (+221)
  • (added) lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h (+53)
  • (added) lldb/source/Target/BorrowedStackFrame.cpp (+183)
  • (modified) lldb/source/Target/CMakeLists.txt (+1)
  • (modified) lldb/source/Target/StackFrame.cpp (+3)
  • (modified) lldb/source/Target/StackFrameList.cpp (+39)
  • (modified) lldb/source/Target/SyntheticFrameProvider.cpp (+23-2)
  • (modified) lldb/source/Target/Target.cpp (+55)
  • (modified) lldb/source/Target/Thread.cpp (+71-1)
  • (modified) lldb/source/Target/ThreadSpec.cpp (+4)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/Makefile (+3)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py (+419)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile (+3)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestCircularDependency.py (+148)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py (+103)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c (+21)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/main.cpp (+55)
  • (added) lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py (+224)
  • (modified) lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp (+5)
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index ef501fbafc947..0ba152166522b 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -425,6 +425,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject * return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject * data) { + lldb::SBThread *sb_ptr = nullptr; + + int valid_cast = + SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBThread, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject * data) { lldb::SBFrame *sb_ptr = nullptr; diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py index 20f4d76d188c2..7a72f1a24c9da 100644 --- a/lldb/examples/python/templates/scripted_frame_provider.py +++ b/lldb/examples/python/templates/scripted_frame_provider.py @@ -31,7 +31,54 @@ class ScriptedFrameProvider(metaclass=ABCMeta): ) """ + @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. diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py index b4232f632a30a..24aa9818bb989 100644 --- a/lldb/examples/python/templates/scripted_process.py +++ b/lldb/examples/python/templates/scripted_process.py @@ -243,6 +243,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 @@ -264,6 +265,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() @@ -350,17 +354,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 @@ -403,11 +404,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 @@ -417,15 +419,17 @@ def __init__(self, thread, args): self.register_ctx = {} self.variables = [] - if ( - isinstance(thread, ScriptedThread) - or isinstance(thread, lldb.SBThread) - and thread.IsValid() + if isinstance(thread, ScriptedThread) 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 @@ -506,7 +510,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 @@ -640,12 +655,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: diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h index ce81ae46a0905..0318492f1054c 100644 --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -19,6 +19,7 @@ #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBStatisticsOptions.h" #include "lldb/API/SBSymbolContextList.h" +#include "lldb/API/SBThreadCollection.h" #include "lldb/API/SBType.h" #include "lldb/API/SBValue.h" #include "lldb/API/SBWatchpoint.h" @@ -1003,6 +1004,35 @@ class LLDB_API SBTarget { lldb::SBMutex GetAPIMutex() const; + /// Register a scripted frame provider for this target. + /// If a scripted frame provider with the same name and same argument + /// dictionary is already registered on this target, it will be overwritten. + /// + /// \param[in] class_name + /// The name of the Python class that implements the frame provider. + /// + /// \param[in] args_dict + /// A dictionary of arguments to pass to the frame provider class. + /// + /// \param[out] error + /// An error object indicating success or failure. + /// + /// \return + /// A unique identifier for the frame provider descriptor that was + /// registered. 0 if the registration failed. + uint32_t RegisterScriptedFrameProvider(const char *class_name, + lldb::SBStructuredData args_dict, + lldb::SBError &error); + + /// Remove a scripted frame provider from this target by name. + /// + /// \param[in] provider_id + /// The id of the frame provider class to remove. + /// + /// \return + /// An error object indicating success or failure. + lldb::SBError RemoveScriptedFrameProvider(uint32_t provider_id); + protected: friend class SBAddress; friend class SBAddressRange; diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h index f6a6d19935b83..639e7a0a1a5c0 100644 --- a/lldb/include/lldb/API/SBThread.h +++ b/lldb/include/lldb/API/SBThread.h @@ -256,6 +256,7 @@ class LLDB_API SBThread { friend class SBThreadPlan; friend class SBTrace; + friend class lldb_private::ScriptInterpreter; friend class lldb_private::python::SWIGBridge; SBThread(const lldb::ThreadSP &lldb_object_sp); diff --git a/lldb/include/lldb/API/SBThreadCollection.h b/lldb/include/lldb/API/SBThreadCollection.h index 5a052e6246026..d13dea0f11cd2 100644 --- a/lldb/include/lldb/API/SBThreadCollection.h +++ b/lldb/include/lldb/API/SBThreadCollection.h @@ -46,6 +46,7 @@ class LLDB_API SBThreadCollection { void SetOpaque(const lldb::ThreadCollectionSP &threads); private: + friend class SBTarget; friend class SBProcess; friend class SBThread; friend class SBSaveCoreOptions; diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index 40916dc48a70b..107c30a000979 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -81,6 +81,7 @@ struct Entry { FrameRegisterByName, FrameIsArtificial, FrameKind, + FrameBorrowedInfo, ScriptFrame, FunctionID, FunctionDidChange, diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h index 2d9f713676f90..49b60131399d5 100644 --- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h @@ -16,11 +16,29 @@ namespace lldb_private { class ScriptedFrameProviderInterface : public ScriptedInterface { public: + virtual bool AppliesToThread(llvm::StringRef class_name, + lldb::ThreadSP thread_sp) { + return true; + } + virtual llvm::Expected<StructuredData::GenericSP> CreatePluginObject(llvm::StringRef class_name, lldb::StackFrameListSP input_frames, StructuredData::DictionarySP args_sp) = 0; + /// Get a description string for the frame provider. + /// + /// This is called by the descriptor to fetch a description from the + /// scripted implementation. Implementations should call a static method + /// on the scripting class to retrieve the description. + /// + /// \param class_name The name of the scripting class implementing the + /// provider. + /// + /// \return A string describing what this frame provider does, or an + /// empty string if no description is available. + virtual std::string GetDescription(llvm::StringRef class_name) { return {}; } + virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) { return {}; } diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 7fed4940b85bf..0b91d6756552d 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -21,6 +21,7 @@ #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBSymbolContext.h" +#include "lldb/API/SBThread.h" #include "lldb/Breakpoint/BreakpointOptions.h" #include "lldb/Core/PluginInterface.h" #include "lldb/Core/SearchFilter.h" @@ -580,6 +581,8 @@ class ScriptInterpreter : public PluginInterface { lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const; + lldb::ThreadSP GetOpaqueTypeFromSBThread(const lldb::SBThread &exe_ctx) const; + lldb::StackFrameSP GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const; SymbolContext diff --git a/lldb/include/lldb/Target/BorrowedStackFrame.h b/lldb/include/lldb/Target/BorrowedStackFrame.h new file mode 100644 index 0000000000000..72e7777961da7 --- /dev/null +++ b/lldb/include/lldb/Target/BorrowedStackFrame.h @@ -0,0 +1,146 @@ +//===----------------------------------------------------------------------===// +// +// 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_TARGET_BORROWEDSTACKFRAME_H +#define LLDB_TARGET_BORROWEDSTACKFRAME_H + +#include "lldb/Target/StackFrame.h" + +namespace lldb_private { + +/// \class BorrowedStackFrame BorrowedStackFrame.h +/// "lldb/Target/BorrowedStackFrame.h" +/// +/// A wrapper around an existing StackFrame that supersedes its frame indices. +/// +/// This class is useful when you need to present an existing stack frame +/// with a different index, such as when creating synthetic frame views or +/// renumbering frames without copying all the underlying data. +/// +/// All methods delegate to the borrowed frame except for GetFrameIndex() +/// & GetConcreteFrameIndex() which uses the overridden indices. +class BorrowedStackFrame : public StackFrame { +public: + /// Construct a BorrowedStackFrame that wraps an existing frame. + /// + /// \param [in] borrowed_frame_sp + /// The existing StackFrame to borrow from. This frame's data will be + /// used for all operations except frame index queries. + /// + /// \param [in] new_frame_index + /// The frame index to report instead of the borrowed frame's index. + /// + /// \param [in] new_concrete_frame_index + /// Optional concrete frame index. If not provided, defaults to + /// new_frame_index. + BorrowedStackFrame( + lldb::StackFrameSP borrowed_frame_sp, uint32_t new_frame_index, + std::optional<uint32_t> new_concrete_frame_index = std::nullopt); + + ~BorrowedStackFrame() override = default; + + uint32_t GetFrameIndex() const override; + void SetFrameIndex(uint32_t index); + + /// Get the concrete frame index for this borrowed frame. + /// + /// Returns the overridden concrete frame index provided at construction, + /// or LLDB_INVALID_FRAME_ID if the borrowed frame represents an inlined + /// function, since this would require some computation if we chain inlined + /// borrowed stack frames. + /// + /// \return + /// The concrete frame index, or LLDB_INVALID_FRAME_ID for inline frames. + uint32_t GetConcreteFrameIndex() override; + + StackID &GetStackID() override; + + const Address &GetFrameCodeAddress() override; + + Address GetFrameCodeAddressForSymbolication() override; + + bool ChangePC(lldb::addr_t pc) override; + + const SymbolContext & + GetSymbolContext(lldb::SymbolContextItem resolve_scope) override; + + llvm::Error GetFrameBaseValue(Scalar &value) override; + + DWARFExpressionList *GetFrameBaseExpression(Status *error_ptr) override; + + Block *GetFrameBlock() override; + + lldb::RegisterContextSP GetRegisterContext() override; + + VariableList *GetVariableList(bool get_file_globals, + Status *error_ptr) override; + + lldb::VariableListSP + GetInScopeVariableList(bool get_file_globals, + bool must_have_valid_location = false) override; + + lldb::ValueObjectSP GetValueForVariableExpressionPath( + llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic, + uint32_t options, lldb::VariableSP &var_sp, Status &error) override; + + bool HasDebugInformation() override; + + const char *Disassemble() override; + + lldb::ValueObjectSP + GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp, + lldb::DynamicValueType use_dynamic) override; + + bool IsInlined() override; + + bool IsSynthetic() const override; + + bool IsHistorical() const override; + + bool IsArtificial() const override; + + bool IsHidden() override; + + const char *GetFunctionName() override; + + const char *GetDisplayFunctionName() override; + + lldb::ValueObjectSP FindVariable(ConstString name) override; + + SourceLanguage GetLanguage() override; + + SourceLanguage GuessLanguage() override; + + lldb::ValueObjectSP GuessValueForAddress(lldb::addr_t addr) override; + + lldb::ValueObjectSP GuessValueForRegisterAndOffset(ConstString reg, + int64_t offset) override; + + StructuredData::ObjectSP GetLanguageSpecificData() override; + + lldb::RecognizedStackFrameSP GetRecognizedFrame() override; + + /// Get the underlying borrowed frame. + lldb::StackFrameSP GetBorrowedFrame() const; + + bool isA(const void *ClassID) const override; + static bool classof(const StackFrame *obj); + +private: + lldb::StackFrameSP m_borrowed_frame_sp; + uint32_t m_new_frame_index; + uint32_t m_new_concrete_frame_index; + static char ID; + + BorrowedStackFrame(const BorrowedStackFrame &) = delete; + const BorrowedStackFrame &operator=(const BorrowedStackFrame &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_BORROWEDSTACKFRAME_H diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index 135bd81e4e8d4..46922448d6e59 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -43,6 +43,13 @@ namespace lldb_private { class StackFrame : public ExecutionContextScope, public std::enable_shared_from_this<StackFrame> { public: + /// LLVM RTTI support. + /// \{ + static char ID; + virtual bool isA(const void *ClassID) const { return ClassID == &ID; } + static bool classof(const StackFrame *obj) { return obj->isA(&ID); } + /// \} + enum ExpressionPathOption { eExpressionPathOptionCheckPtrVsMember = (1u << 0), eExpressionPathOptionsNoFragileObjcIvar = (1u << 1), @@ -127,7 +134,7 @@ class StackFrame : public ExecutionContextScope, lldb::ThreadSP GetThread() const { return m_thread_wp.lock(); } - StackID &GetStackID(); + virtual StackID &GetStackID(); /// Get an Address for the current pc value in this StackFrame. /// @@ -135,7 +142,7 @@ class StackFrame : public ExecutionContextScope, /// /// \return /// The Address object set to the current PC value. - const Address &GetFrameCodeAddress(); + virtual const Address &GetFrameCodeAddress(); /// Get the current code Address suitable for symbolication, /// may not be the same as GetFrameCodeAddress(). @@ -153,7 +160,7 @@ class StackFrame : public ExecutionContextScope, /// /// \return /// The Address object set to the current PC value. - Address GetFrameCodeAddressForSymbolication... [truncated] 
@github-actions
Copy link

github-actions bot commented Dec 2, 2025

⚠️ Python code formatter, darker found issues in your code. ⚠️

You can test this locally with the following command:
darker --check --diff -r origin/main...HEAD lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py lldb/examples/python/templates/scripted_frame_provider.py lldb/examples/python/templates/scripted_process.py

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from darker here.
--- test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py	2025-12-02 01:54:35.000000 +0000 +++ test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py	2025-12-02 01:56:09.792664 +0000 @@ -113,7 +113,5 @@ # These calls should not trigger circular dependency. pc = frame.GetPC() self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC") func_name = frame.GetFunctionName() self.assertIsNotNone(func_name, f"Frame {i} should have function name") - - 
@medismailben medismailben force-pushed the scripted-frame-provider branch from 5ffc853 to 49bc29f Compare December 2, 2025 01:34
@medismailben
Copy link
Member Author

This depends on #170191. After that lands, it should only contain one commit.

@medismailben medismailben force-pushed the scripted-frame-provider branch 2 times, most recently from a195b0f to 761c40c Compare December 2, 2025 01:54
This change makes StackFrame methods virtual to enable subclass overrides and introduces BorrowedStackFrame, a wrapper that presents an existing StackFrame with a different frame index. This enables creating synthetic frame views or renumbering frames without copying the underlying frame data, which is useful for frame manipulation scenarios. This also adds a new borrowed-info format entity to show what was the original frame index of the borrowed frame. Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
…#161870)" This patch extends ScriptedFrame to work with real (non-scripted) threads, enabling frame providers to synthesize frames for native processes. Previously, ScriptedFrame only worked within ScriptedProcess/ScriptedThread contexts. This patch decouples ScriptedFrame from ScriptedThread, allowing users to augment or replace stack frames in real debugging sessions for use cases like custom calling conventions, reconstructing corrupted frames from core files, or adding diagnostic frames. Key changes: - ScriptedFrame::Create() now accepts ThreadSP instead of requiring ScriptedThread, extracting architecture from the target triple rather than ScriptedProcess.arch - Added SBTarget::RegisterScriptedFrameProvider() and ClearScriptedFrameProvider() APIs, with Target storing a SyntheticFrameProviderDescriptor template for new threads - Added "target frame-provider register/clear" commands for CLI access - Thread class gains LoadScriptedFrameProvider(), ClearScriptedFrameProvider(), and GetFrameProvider() methods for per-thread frame provider management - New SyntheticStackFrameList overrides FetchFramesUpTo() to lazily provide frames from either the frame provider or the real stack This enables practical use of the SyntheticFrameProvider infrastructure in real debugging workflows. rdar://161834688 Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
@medismailben medismailben force-pushed the scripted-frame-provider branch from 761c40c to 5b760e8 Compare December 2, 2025 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 participants