Author: jimingham
Date: 2026-08-31T09:54:47-07:00
New Revision: e2e8caf51f5029df79968db391e630533fd13c19

URL: 
https://github.com/llvm/llvm-project/commit/e2e8caf51f5029df79968db391e630533fd13c19
DIFF: 
https://github.com/llvm/llvm-project/commit/e2e8caf51f5029df79968db391e630533fd13c19.diff

LOG: Add the ability to provide a scripted "step through" plan. (#218812)

This is a scripted equivalent of the "trampoline handler" that lldb uses
to run to the target of a dynamic dispatch stub, or to the target of
std::function, or an objc message send.

Added: 
    lldb/test/API/commands/frame/recognizer/step-through/Makefile
    
lldb/test/API/commands/frame/recognizer/step-through/TestFrameRecognizerStepThrough.py
    lldb/test/API/commands/frame/recognizer/step-through/main.c
    lldb/test/API/commands/frame/recognizer/step-through/recognizer.py

Modified: 
    lldb/bindings/python/python-wrapper.swig
    lldb/docs/use/tutorials/custom-frame-recognizers.md
    lldb/examples/python/templates/scripted_stackframe_recognizer.py
    lldb/include/lldb/API/SBThreadPlan.h
    
lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
    lldb/include/lldb/Target/StackFrameRecognizer.h
    lldb/source/API/ScriptInterpreterBridge.cpp
    lldb/source/API/ScriptInterpreterBridge.h
    
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
    
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
    
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
    
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
    lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
    lldb/source/Target/StackFrameRecognizer.cpp
    lldb/source/Target/ThreadPlanStepThrough.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/bindings/python/python-wrapper.swig 
b/lldb/bindings/python/python-wrapper.swig
index ebd0245febf54..c96d2f8390f64 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -237,6 +237,18 @@ void 
*lldb_private::python::LLDBSWIGPython_CastPyObjectToSBData(PyObject * data)
   return sb_ptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThreadPlan(PyObject 
* data) {
+  lldb::SBThreadPlan *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBThreadPlan, 
0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject 
* data) {
   lldb::SBBreakpoint *sb_ptr = nullptr;
 

diff  --git a/lldb/docs/use/tutorials/custom-frame-recognizers.md 
b/lldb/docs/use/tutorials/custom-frame-recognizers.md
index e1f859bad3b8d..a357ffbd7e076 100644
--- a/lldb/docs/use/tutorials/custom-frame-recognizers.md
+++ b/lldb/docs/use/tutorials/custom-frame-recognizers.md
@@ -5,6 +5,32 @@ on ABI, arguments or other special properties of that frame, 
even without
 source code or debug info. Currently, one use case is to extract function
 arguments that would otherwise be inaccessible, or augment existing arguments.
 
+Frame recognizers also allow you to implement "trampoline handlers".  lldb
+has built-in trampoline handlers, for instance, to go from a shared library
+stub to its target, from the dispatch of an objc message send to its target,
+etc, or from std::function to its target.  They work by recognizing the
+start point of the stub, and providing a "step through" Thread Plan that drives
+the thread to the trampoline target.
+
+Frame recognizers already do the "identify the target" part.  To provide the
+step through, have your frame recogizer implement the `get_step_through_plan`
+method:
+
+```
+def get_step_through_plan(self, thread : lldb.SBThread):
+```
+
+It should return a Python dictionary with two keys:
+
+```
+class_name : the name of the class implementing the step through thread plan
+extra_args : a dictionary with the keys that will be passed to the __init__
+             of your scripted thread plan.
+```
+
+and lldb will push a ScriptedThreadPlan using the class and extra_args
+provided.
+
 Adding a custom frame recognizer is done by implementing a Python class and
 using the `frame recognizer add` command. The Python class should implement the
 `get_recognized_arguments` method and it will receive an argument of type

diff  --git a/lldb/examples/python/templates/scripted_stackframe_recognizer.py 
b/lldb/examples/python/templates/scripted_stackframe_recognizer.py
index 0c8489f8d6d2d..c3a86d7d529a7 100644
--- a/lldb/examples/python/templates/scripted_stackframe_recognizer.py
+++ b/lldb/examples/python/templates/scripted_stackframe_recognizer.py
@@ -100,3 +100,20 @@ def get_stop_description(self, frame: lldb.SBFrame) -> str:
             default.
         """
         return ""
+
+    def get_step_through_plan(self, thread):
+        """Provide a 'step through' plan from the stopped frame.
+        Args:
+            thread (lldb.SBThread): passed in is the one to step.  You
+            will only be asked to step through from the zeroth frame of that
+            thread.
+
+        Returns:
+            Python dictionary with two keys:
+
+            class_name : name of a class implementing a ScriptedThreadPlan.
+            extra_args : a dictionary that will be passed to the
+            __init__ of your scripted thread plan.  The extra_args is optional.
+        """
+
+        return None

diff  --git a/lldb/include/lldb/API/SBThreadPlan.h 
b/lldb/include/lldb/API/SBThreadPlan.h
index 1f0164efcfb98..2a1c823d05edd 100644
--- a/lldb/include/lldb/API/SBThreadPlan.h
+++ b/lldb/include/lldb/API/SBThreadPlan.h
@@ -133,6 +133,7 @@ class LLDB_API SBThreadPlan {
   friend class SBValue;
   friend class lldb_private::QueueImpl;
   friend class SBQueueItem;
+  friend class lldb_private::ScriptInterpreterBridge;
 
   lldb::ThreadPlanSP GetSP() const { return m_opaque_wp.lock(); }
   lldb_private::ThreadPlan *get() const { return GetSP().get(); }

diff  --git 
a/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
index 333973b9f933b..e8ebf9f72aaef 100644
--- 
a/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
+++ 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h
@@ -37,6 +37,10 @@ class ScriptedStackFrameRecognizerInterface : virtual public 
ScriptedInterface {
   virtual std::string GetStopDescription(lldb::StackFrameSP frame_sp) {
     return "";
   }
+
+  virtual lldb::ThreadPlanSP GetStepThroughPlan(lldb::ThreadSP thread_sp) {
+    return {};
+  }
 };
 } // namespace lldb_private
 

diff  --git a/lldb/include/lldb/Target/StackFrameRecognizer.h 
b/lldb/include/lldb/Target/StackFrameRecognizer.h
index 95e8a03cac96c..c9ef6e6fb141b 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -47,6 +47,8 @@ class RecognizedStackFrame
   /// displaying backtraces, for example.
   virtual bool ShouldHide() { return false; }
 
+  virtual lldb::ThreadPlanSP GetStepThroughPlan() { return {}; }
+
 protected:
   lldb::ValueObjectListSP m_arguments;
   std::string m_stop_desc;

diff  --git a/lldb/source/API/ScriptInterpreterBridge.cpp 
b/lldb/source/API/ScriptInterpreterBridge.cpp
index e818a0c424d16..86aaeec793828 100644
--- a/lldb/source/API/ScriptInterpreterBridge.cpp
+++ b/lldb/source/API/ScriptInterpreterBridge.cpp
@@ -25,6 +25,7 @@
 #include "lldb/API/SBSymbolContext.h"
 #include "lldb/API/SBTarget.h"
 #include "lldb/API/SBThread.h"
+#include "lldb/API/SBThreadPlan.h"
 #include "lldb/API/SBValue.h"
 #include "lldb/Host/ProcessLaunchInfo.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
@@ -40,6 +41,11 @@ ScriptInterpreterBridge::GetDataExtractor(const lldb::SBData 
&data) {
   return data.m_opaque_sp;
 }
 
+lldb::ThreadPlanSP
+ScriptInterpreterBridge::GetThreadPlan(const lldb::SBThreadPlan &thread_plan) {
+  return thread_plan.GetSP();
+}
+
 lldb::BreakpointSP
 ScriptInterpreterBridge::GetBreakpoint(const lldb::SBBreakpoint &breakpoint) {
   return breakpoint.m_opaque_wp.lock();

diff  --git a/lldb/source/API/ScriptInterpreterBridge.h 
b/lldb/source/API/ScriptInterpreterBridge.h
index d0dbb049a6aff..4196e5da83e21 100644
--- a/lldb/source/API/ScriptInterpreterBridge.h
+++ b/lldb/source/API/ScriptInterpreterBridge.h
@@ -31,6 +31,9 @@ class ScriptInterpreterBridge {
 public:
   static lldb::DataExtractorSP GetDataExtractor(const lldb::SBData &data);
 
+  static lldb::ThreadPlanSP
+  GetThreadPlan(const lldb::SBThreadPlan &thread_plan);
+
   static Status GetStatus(const lldb::SBError &error);
 
   static Event *GetEvent(const lldb::SBEvent &event);

diff  --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index e08b4795b9297..b1a769af96e92 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -216,6 +216,22 @@ lldb::ProcessLaunchInfoSP 
ScriptedPythonInterface::ExtractValueFromPythonObject<
   return ScriptInterpreterBridge::GetProcessLaunchInfo(*sb_launch_info);
 }
 
+template <>
+lldb::ThreadPlanSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadPlanSP>(
+    python::PythonObject &p, Status &error) {
+  lldb::SBThreadPlan *sb_thread_plan = reinterpret_cast<lldb::SBThreadPlan *>(
+      python::LLDBSWIGPython_CastPyObjectToSBThreadPlan(p.get()));
+
+  if (!sb_thread_plan) {
+    error = Status::FromErrorStringWithFormat(
+        "Couldn't cast lldb::SBThreadPlan to lldb::ThreadPlanSP.");
+    return {};
+  }
+
+  return ScriptInterpreterBridge::GetThreadPlan(*sb_thread_plan);
+}
+
 template <>
 std::optional<MemoryRegionInfo>
 ScriptedPythonInterface::ExtractValueFromPythonObject<

diff  --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index 91663d1293108..aecb7ab68d017 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -936,6 +936,11 @@ lldb::DataExtractorSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
     python::PythonObject &p, Status &error);
 
+template <>
+lldb::ThreadPlanSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadPlanSP>(
+    python::PythonObject &p, Status &error);
+
 template <>
 std::optional<MemoryRegionInfo>
 ScriptedPythonInterface::ExtractValueFromPythonObject<

diff  --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
index 2e05928a2ad57..7cdfc99a6b4c8 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
@@ -9,7 +9,10 @@
 #include "../lldb-python.h"
 
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Target/ScriptedThreadPlan.h"
 #include "lldb/Target/StackFrame.h"
+#include "lldb/Target/ThreadPlan.h"
+#include "lldb/Utility/StructuredData.h"
 #include "lldb/lldb-enumerations.h"
 
 #include "../SWIGPythonBridge.h"
@@ -78,6 +81,46 @@ std::string 
ScriptedStackFrameRecognizerPythonInterface::GetStopDescription(
   return obj->GetStringValue().str();
 }
 
+lldb::ThreadPlanSP
+ScriptedStackFrameRecognizerPythonInterface::GetStepThroughPlan(
+    lldb::ThreadSP thread_sp) {
+  Status error;
+  StructuredData::DictionarySP dict_sp = 
Dispatch<StructuredData::DictionarySP>(
+      "get_step_through_plan", error, thread_sp);
+  if (error.Fail())
+    return {};
+
+  // The return value is an StructuredData::Dictionary with the class name and
+  // the extra args for the call:
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION,
+                                                    dict_sp, error))
+    return {};
+
+  StructuredData::ObjectSP obj = dict_sp->GetValueForKey("class_name");
+  if (!obj)
+    return {};
+
+  llvm::StringRef class_string = obj->GetStringValue();
+  if (class_string.empty())
+    return {};
+
+  // Look for extra args, this is optional:
+  StructuredData::Dictionary *extra_args_ptr = nullptr;
+  StructuredData::DictionarySP extra_args_sp;
+  if (dict_sp->GetValueForKeyAsDictionary("extra_args", extra_args_ptr))
+    extra_args_sp = std::static_pointer_cast<StructuredData::Dictionary>(
+        extra_args_ptr->shared_from_this());
+
+  // Now make a new thread plan for stepping using the provided class name and
+  // extra args.
+  ScriptedMetadata plan_metadata(class_string, extra_args_sp);
+  ThreadPlanSP step_through_plan_sp(
+      new ScriptedThreadPlan(*thread_sp.get(), plan_metadata));
+  step_through_plan_sp->SetStopOthers(true);
+
+  return step_through_plan_sp;
+}
+
 void ScriptedStackFrameRecognizerPythonInterface::Initialize() {
   const std::vector<llvm::StringRef> ci_usages = {
       "frame recognizer add -l <script-name> [-s <shlib> ...] "

diff  --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
index eaf4116ab54c0..0a54cd9c2186c 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedStackFrameRecognizerPythonInterface.h
@@ -42,6 +42,8 @@ class ScriptedStackFrameRecognizerPythonInterface
 
   std::string GetStopDescription(lldb::StackFrameSP frame_sp) override;
 
+  lldb::ThreadPlanSP GetStepThroughPlan(lldb::ThreadSP thread_sp) override;
+
   static void Initialize();
 
   static void Terminate();

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h 
b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 2530342f74bd3..f09284b3aa697 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -207,6 +207,7 @@ void *LLDBSWIGPython_CastPyObjectToSBDebugger(PyObject 
*data);
 void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBThreadPlan(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);

diff  --git a/lldb/source/Target/StackFrameRecognizer.cpp 
b/lldb/source/Target/StackFrameRecognizer.cpp
index 99ef837ec580b..62edc010d7954 100644
--- a/lldb/source/Target/StackFrameRecognizer.cpp
+++ b/lldb/source/Target/StackFrameRecognizer.cpp
@@ -23,17 +23,15 @@ using namespace lldb;
 using namespace lldb_private;
 
 class ScriptedRecognizedStackFrame : public RecognizedStackFrame {
-  bool m_hidden;
-  lldb::StackFrameSP m_most_relevant_frame;
-  lldb::ValueObjectSP m_exception;
-
 public:
   ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden,
                                lldb::StackFrameSP most_relevant_frame,
                                lldb::ValueObjectSP exception,
-                               std::string stop_desc)
+                               std::string stop_desc,
+                               lldb::ThreadPlanSP step_through_plan_sp)
       : m_hidden(hidden), 
m_most_relevant_frame(std::move(most_relevant_frame)),
-        m_exception(std::move(exception)) {
+        m_exception(std::move(exception)),
+        m_thread_plan_sp(std::move(step_through_plan_sp)) {
     m_arguments = std::move(args);
     m_stop_desc = std::move(stop_desc);
   }
@@ -42,6 +40,14 @@ class ScriptedRecognizedStackFrame : public 
RecognizedStackFrame {
     return m_most_relevant_frame;
   }
   lldb::ValueObjectSP GetExceptionObject() override { return m_exception; }
+
+  lldb::ThreadPlanSP GetStepThroughPlan() override { return m_thread_plan_sp; }
+
+protected:
+  bool m_hidden;
+  lldb::StackFrameSP m_most_relevant_frame;
+  lldb::ValueObjectSP m_exception;
+  lldb::ThreadPlanSP m_thread_plan_sp;
 };
 
 ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer(
@@ -87,10 +93,14 @@ 
ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
       m_interface_sp->SelectMostRelevantFrame(frame);
   lldb::ValueObjectSP exception = m_interface_sp->GetException(frame);
   std::string stop_desc = m_interface_sp->GetStopDescription(frame);
+  // We only do step through if we're at the zeroth frame:
+  lldb::ThreadPlanSP step_through_sp;
+  if (frame->GetConcreteFrameIndex() == 0)
+    step_through_sp = m_interface_sp->GetStepThroughPlan(frame->GetThread());
 
   return RecognizedStackFrameSP(new ScriptedRecognizedStackFrame(
       args_synthesized, hidden, std::move(most_relevant), std::move(exception),
-      std::move(stop_desc)));
+      std::move(stop_desc), std::move(step_through_sp)));
 }
 
 void StackFrameRecognizerManager::BumpGeneration() {

diff  --git a/lldb/source/Target/ThreadPlanStepThrough.cpp 
b/lldb/source/Target/ThreadPlanStepThrough.cpp
index 382335e234868..ac40fe80699a9 100644
--- a/lldb/source/Target/ThreadPlanStepThrough.cpp
+++ b/lldb/source/Target/ThreadPlanStepThrough.cpp
@@ -12,6 +12,7 @@
 #include "lldb/Target/LanguageRuntime.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/StackFrameRecognizer.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
@@ -77,6 +78,16 @@ void ThreadPlanStepThrough::DidPush() {
 
 void ThreadPlanStepThrough::LookForPlanToStepThroughFromCurrentPC() {
   Thread &thread = GetThread();
+
+  // Give the frame recognizers a chance to provide a step through:
+  StackFrameSP frame_zero_sp = thread.GetStackFrameAtIndex(0);
+  if (RecognizedStackFrameSP frame_recognizer_sp =
+          frame_zero_sp->GetRecognizedFrame()) {
+    m_sub_plan_sp = frame_recognizer_sp->GetStepThroughPlan();
+    if (m_sub_plan_sp)
+      return;
+  }
+
   DynamicLoader *loader = thread.GetProcess()->GetDynamicLoader();
   if (loader)
     m_sub_plan_sp = loader->GetStepThroughTrampolinePlan(thread, 
m_stop_others);

diff  --git a/lldb/test/API/commands/frame/recognizer/step-through/Makefile 
b/lldb/test/API/commands/frame/recognizer/step-through/Makefile
new file mode 100644
index 0000000000000..4774d1d781b3c
--- /dev/null
+++ b/lldb/test/API/commands/frame/recognizer/step-through/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+MAKE_DSYM := NO
+
+include Makefile.rules

diff  --git 
a/lldb/test/API/commands/frame/recognizer/step-through/TestFrameRecognizerStepThrough.py
 
b/lldb/test/API/commands/frame/recognizer/step-through/TestFrameRecognizerStepThrough.py
new file mode 100644
index 0000000000000..9fcafed175865
--- /dev/null
+++ 
b/lldb/test/API/commands/frame/recognizer/step-through/TestFrameRecognizerStepThrough.py
@@ -0,0 +1,56 @@
+# encoding: utf-8
+"""
+Test lldb's frame recognizers.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+import recognizer
+
+
+class TestFrameRecognizerStepThrough(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIfWindows
+    def test_frame_recognizer_step_through(self):
+        """Test that the step through recognizer works"""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        # Clear internal & plugins recognizers that get initialized at launch
+        self.runCmd("frame recognizer clear")
+
+        # Create a target.
+        target, process, thread, _ = lldbutil.run_to_source_breakpoint(
+            self, "Stop here to step through", lldb.SBFileSpec("main.c")
+        )
+
+        self.runCmd(
+            "command script import "
+            + os.path.join(self.getSourceDir(), "recognizer.py")
+        )
+
+        # Check that this doesn't contain our own FrameRecognizer somehow.
+        self.expect(
+            "frame recognizer list", matching=False, 
substrs=["NestedFrameRecognizer"]
+        )
+
+        # Add a frame recognizer in that target.
+        self.runCmd(
+            "frame recognizer add -f 1 -l recognizer.NestedFrameRecognizer -s 
a.out -n baz"
+        )
+
+        self.expect(
+            "frame recognizer list",
+            substrs=[
+                "recognizer.NestedFrameRecognizer, module a.out, demangled 
symbol baz"
+            ],
+        )
+
+        # Now do a step in, the step through should kick in and take us to bar.
+        thread.StepInto()
+        self.assertEqual(thread.frames[0].name, "bar", "Did stop at bar")
+        self.assertIn("step in", thread.stop_description, "Reason was 
correct.")

diff  --git a/lldb/test/API/commands/frame/recognizer/step-through/main.c 
b/lldb/test/API/commands/frame/recognizer/step-through/main.c
new file mode 100644
index 0000000000000..6be9d745c9937
--- /dev/null
+++ b/lldb/test/API/commands/frame/recognizer/step-through/main.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+void foo(int a, int b) { printf("%d %d\n", a, b); }
+
+void bar(int *ptr) { printf("%d\n", *ptr); }
+
+void nested(int *ptr) { bar(ptr); }
+
+void baz(int *ptr) { nested(ptr); }
+
+int main(int argc, const char *argv[]) {
+  foo(42, 56);
+  int i = 78;
+  bar(&i);
+  baz(&i); // Stop here to step through
+  return 0;
+}

diff  --git 
a/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py 
b/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
new file mode 100644
index 0000000000000..e74cde6233be2
--- /dev/null
+++ b/lldb/test/API/commands/frame/recognizer/step-through/recognizer.py
@@ -0,0 +1,90 @@
+# encoding: utf-8
+
+import lldb
+from lldb.plugins.scripted_thread_plan import ScriptedThreadPlan
+from lldb.plugins.scripted_stackframe_recognizer import 
ScriptedStackFrameRecognizer
+
+
+class NestedFrameRecognizer(ScriptedStackFrameRecognizer):
+    """Exercises the step-through feature of frame recognizers."""
+
+    def get_step_through_plan(self, thread):
+        """Step through from baz to bar"""
+        frame = thread.frames[0]
+        print(f"Asked to step through at {frame.name}")
+        if frame.name.startswith("baz"):
+            target = thread.process.target
+            bar_funcs = target.FindFunctions("bar", lldb.eFunctionNameTypeFull)
+            if bar_funcs.GetSize() == 0:
+                print("Found no functions matching bar")
+                return None
+            if bar_funcs.GetSize() != 1:
+                print("Found more than one function matching bar")
+                return None
+            bar_func = bar_funcs.functions[0]
+            address = bar_func.addr
+            if not address.IsValid():
+                print("Didn't get a valid address for bar")
+                return None
+
+            load_addr = address.GetLoadAddress(target)
+
+            dict = {
+                "class_name": "recognizer.StepThrough",
+                "extra_args": {"address": str(load_addr)},
+            }
+
+            return dict
+
+
+class StepThrough(ScriptedThreadPlan):
+    def __init__(
+        self, thread_plan: lldb.SBThreadPlan, extra_args: lldb.SBStructuredData
+    ):
+        super().__init__(thread_plan)
+
+        target = thread_plan.GetThread().process.target
+
+        addr_val = extra_args.GetValueForKey("address")
+        if not addr_val.IsValid():
+            print("Missing addr_val key")
+            thread_plan.SetPlanComplete(False)
+            return
+
+        strm = lldb.SBStream()
+        addr_val.GetDescription(strm)
+        addr_str = addr_val.GetStringValue(32)
+        addr_int = int(addr_str)
+        if addr_int == 0:
+            print("Got zero value for addr_int")
+            thread_plan.SetPlanComplete(False)
+            return
+
+        address = lldb.SBAddress(addr_int, target)
+        if not address.IsValid():
+            print("Got invalid value for address")
+            thread_plan.SetPlanComplete(False)
+            return
+
+        error = lldb.SBError()
+        self.addr_plan = thread_plan.QueueThreadPlanForRunToAddress(address, 
error)
+        if error.Fail():
+            print(f"Couldn't queue run to address plan: {error.description}")
+            thread_plan.SetPlanComplete(False)
+            return
+
+    def explains_stop(self, event: lldb.SBEvent):
+        if self.addr_plan.IsPlanComplete():
+            self.thread_plan.SetPlanComplete(True)
+            return True
+        else:
+            return False
+
+    def should_stop(self):
+        return self.thread_plan.IsPlanComplete()
+
+    def should_step(self):
+        return False
+
+    def stop_description(self, stream: lldb.SBStream):
+        stream.Print("stepped through from baz to bar")


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to