[Lldb-commits] [lldb] c2be702 - Allow scripted thread plans to modify the thread stop description when

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Wed May 3 10:52:21 PDT 2023


Author: Jim Ingham
Date: 2023-05-03T10:52:12-07:00
New Revision: c2be702104284cb3facf31124494b9a400296179

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

LOG: Allow scripted thread plans to modify the thread stop description when
they are completed.

Added: 
    

Modified: 
    lldb/bindings/python/python-wrapper.swig
    lldb/examples/python/scripted_step.py
    lldb/include/lldb/Interpreter/ScriptInterpreter.h
    lldb/include/lldb/Target/ThreadPlanPython.h
    lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
    lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
    lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
    lldb/source/Target/ThreadPlanPython.cpp
    lldb/test/API/functionalities/step_scripted/Steps.py
    lldb/test/API/functionalities/step_scripted/TestStepScripted.py
    lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index d339f592cec4c..bb452658a8380 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -367,6 +367,38 @@ bool lldb_private::LLDBSWIGPythonCallThreadPlan(
   return false;
 }
 
+bool lldb_private::LLDBSWIGPythonCallThreadPlan(
+    void *implementor, const char *method_name, lldb_private::Stream *stream,
+    bool &got_error) {
+  got_error = false;
+
+  PyErr_Cleaner py_err_cleaner(false);
+  PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementor));
+  auto pfunc = self.ResolveName<PythonCallable>(method_name);
+
+  if (!pfunc.IsAllocated()) 
+    return false;
+
+  auto *sb_stream = new lldb::SBStream();
+  PythonObject sb_stream_arg =
+      ToSWIGWrapper(std::unique_ptr<lldb::SBStream>(sb_stream));
+
+  PythonObject result;
+  result = pfunc(sb_stream_arg);
+
+  if (PyErr_Occurred()) {
+    printf("Error occured for call to %s.\n",
+           method_name);
+    PyErr_Print();
+    got_error = true;
+    return false;
+  }
+  if (stream)
+    stream->PutCString(sb_stream->GetData());
+  return true;
+
+}
+
 PythonObject lldb_private::LLDBSwigPythonCreateScriptedBreakpointResolver(
     const char *python_class_name, const char *session_dictionary_name,
     const StructuredDataImpl &args_impl,

diff  --git a/lldb/examples/python/scripted_step.py b/lldb/examples/python/scripted_step.py
index 011e24d70cb26..6e53072e9735c 100644
--- a/lldb/examples/python/scripted_step.py
+++ b/lldb/examples/python/scripted_step.py
@@ -77,6 +77,10 @@
 #    is stale.  In this case, if the step_out plan that the FinishPrintAndContinue
 #    plan is driving is stale, so is ours, and it is time to do our printing.
 #
+# 4) If you implement the "stop_description(SBStream stream)" method in your
+#    python class, then that will show up as the "plan completed" reason when
+#    your thread plan is complete.
+#
 # Both examples show stepping through an address range for 20 bytes from the
 # current PC.  The first one does it by single stepping and checking a condition.
 # It doesn't, however handle the case where you step into another frame while
@@ -122,6 +126,8 @@ def should_stop(self, event):
     def should_step(self):
         return True
 
+    def stop_description(self, stream):
+        stream.Print("Simple step completed")
 
 class StepWithPlan:
 
@@ -146,6 +152,9 @@ def should_stop(self, event):
     def should_step(self):
         return False
 
+    def stop_description(self, stream):
+        self.step_thread_plan.GetDescription(stream, lldb.eDescriptionLevelBrief)
+
 # Here's another example which does "step over" through the current function,
 # and when it stops at each line, it checks some condition (in this example the
 # value of a variable) and stops if that condition is true.
@@ -205,6 +214,9 @@ def should_stop(self, event):
     def should_step(self):
         return True
 
+    def stop_description(self, stream):
+        stream.Print(f"Stepped until a == 20")
+
 # Here's an example that steps out of the current frame, gathers some information
 # and then continues.  The information in this case is rax.  Currently the thread
 # plans are not a safe place to call lldb command-line commands, so the information
@@ -242,3 +254,6 @@ def do_print(self):
             print("RAX on exit: ", rax_value.GetValue())
         else:
             print("Couldn't get rax value:", rax_value.GetError().GetCString())
+
+    def stop_description(self, stream):
+        self.step_out_thread_plan.GetDescription(stream, lldb.eDescriptionLevelBrief)

diff  --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index aeb822e148c77..0095905fd320c 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -314,6 +314,14 @@ class ScriptInterpreter : public PluginInterface {
     return lldb::eStateStepping;
   }
 
+  virtual bool
+  ScriptedThreadPlanGetStopDescription(StructuredData::ObjectSP implementor_sp,
+                                       lldb_private::Stream *stream,
+                                       bool &script_error) {
+    script_error = true;
+    return false;
+  }
+
   virtual StructuredData::GenericSP
   CreateScriptedBreakpointResolver(const char *class_name,
                                    const StructuredDataImpl &args_data,

diff  --git a/lldb/include/lldb/Target/ThreadPlanPython.h b/lldb/include/lldb/Target/ThreadPlanPython.h
index f148f88d4c46d..64854d66b8f25 100644
--- a/lldb/include/lldb/Target/ThreadPlanPython.h
+++ b/lldb/include/lldb/Target/ThreadPlanPython.h
@@ -51,6 +51,9 @@ class ThreadPlanPython : public ThreadPlan {
   void DidPush() override;
 
   bool IsPlanStale() override;
+  
+  bool DoWillResume(lldb::StateType resume_state, bool current_plan) override;
+
 
 protected:
   bool DoPlanExplainsStop(Event *event_ptr) override;
@@ -64,6 +67,7 @@ class ThreadPlanPython : public ThreadPlan {
   StructuredDataImpl m_args_data;
   std::string m_error_str;
   StructuredData::ObjectSP m_implementation_sp;
+  StreamString m_stop_description; // Cache the stop description here
   bool m_did_push;
   bool m_stop_others;
 

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 7e33c07213e0b..78b1375aedad9 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -148,6 +148,11 @@ python::PythonObject LLDBSwigPythonCreateScriptedThreadPlan(
 bool LLDBSWIGPythonCallThreadPlan(void *implementor, const char *method_name,
                                   lldb_private::Event *event_sp,
                                   bool &got_error);
+                                  
+bool LLDBSWIGPythonCallThreadPlan(void *implementor, 
+                                  const char *method_name,
+                                  lldb_private::Stream *stream,
+                                  bool &got_error);
 
 python::PythonObject LLDBSwigPythonCreateScriptedBreakpointResolver(
     const char *python_class_name, const char *session_dictionary_name,

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 3af3c87dab338..a98ac58e488bc 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1743,7 +1743,7 @@ bool ScriptInterpreterPythonImpl::ScriptedThreadPlanIsStale(
     Locker py_lock(this,
                    Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
     is_stale = LLDBSWIGPythonCallThreadPlan(generic->GetValue(), "is_stale",
-                                            nullptr, script_error);
+                                            (Event *) nullptr, script_error);
     if (script_error)
       return true;
   }
@@ -1760,7 +1760,7 @@ lldb::StateType ScriptInterpreterPythonImpl::ScriptedThreadPlanGetRunState(
     Locker py_lock(this,
                    Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
     should_step = LLDBSWIGPythonCallThreadPlan(
-        generic->GetValue(), "should_step", nullptr, script_error);
+        generic->GetValue(), "should_step", (Event *) nullptr, script_error);
     if (script_error)
       should_step = true;
   }
@@ -1769,6 +1769,24 @@ lldb::StateType ScriptInterpreterPythonImpl::ScriptedThreadPlanGetRunState(
   return lldb::eStateRunning;
 }
 
+bool
+ScriptInterpreterPythonImpl::ScriptedThreadPlanGetStopDescription(
+    StructuredData::ObjectSP implementor_sp, lldb_private::Stream *stream, 
+    bool &script_error) {
+  StructuredData::Generic *generic = nullptr;
+  if (implementor_sp)
+    generic = implementor_sp->GetAsGeneric();
+  if (!generic) {
+    script_error = true;
+    return false;
+  }
+  Locker py_lock(this,
+                   Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
+  return LLDBSWIGPythonCallThreadPlan(generic->GetValue(), "stop_description", 
+      stream, script_error);
+}
+
+
 StructuredData::GenericSP
 ScriptInterpreterPythonImpl::CreateScriptedBreakpointResolver(
     const char *class_name, const StructuredDataImpl &args_data,

diff  --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index 21fdf12505f86..b810bdbd9a0c7 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -97,6 +97,11 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
   ScriptedThreadPlanGetRunState(StructuredData::ObjectSP implementor_sp,
                                 bool &script_error) override;
 
+  bool
+  ScriptedThreadPlanGetStopDescription(StructuredData::ObjectSP implementor_sp,
+                                lldb_private::Stream *s,
+                                bool &script_error) override;
+
   StructuredData::GenericSP
   CreateScriptedBreakpointResolver(const char *class_name,
                                    const StructuredDataImpl &args_data,

diff  --git a/lldb/source/Target/ThreadPlanPython.cpp b/lldb/source/Target/ThreadPlanPython.cpp
index bc2a7a02e99c6..d6de6b3c3cf04 100644
--- a/lldb/source/Target/ThreadPlanPython.cpp
+++ b/lldb/source/Target/ThreadPlanPython.cpp
@@ -136,8 +136,11 @@ bool ThreadPlanPython::MischiefManaged() {
     // I don't really need mischief_managed, since it's simpler to just call
     // SetPlanComplete in should_stop.
     mischief_managed = IsPlanComplete();
-    if (mischief_managed)
+    if (mischief_managed) {
+      // We need to cache the stop reason here we'll need it in GetDescription.
+      GetDescription(&m_stop_description, eDescriptionLevelBrief);
       m_implementation_sp.reset();
+    }
   }
   return mischief_managed;
 }
@@ -158,15 +161,40 @@ lldb::StateType ThreadPlanPython::GetPlanRunState() {
   return run_state;
 }
 
-// The ones below are not currently exported to Python.
 void ThreadPlanPython::GetDescription(Stream *s, lldb::DescriptionLevel level) {
-  s->Printf("Python thread plan implemented by class %s.",
+  Log *log = GetLog(LLDBLog::Thread);
+  LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION,
+            m_class_name.c_str());
+  if (m_implementation_sp) {
+    ScriptInterpreter *script_interp = GetScriptInterpreter();
+    if (script_interp) {
+      bool script_error;
+      bool added_desc = script_interp->ScriptedThreadPlanGetStopDescription(
+          m_implementation_sp, s, script_error);
+      if (script_error || !added_desc)
+        s->Printf("Python thread plan implemented by class %s.",
             m_class_name.c_str());
+    }
+    return;
+  }
+  // It's an error not to have a description, so if we get here, we should
+  // add something.
+  if (m_stop_description.Empty())
+    s->Printf("Python thread plan implemented by class %s.",
+              m_class_name.c_str());
+  s->PutCString(m_stop_description.GetData());
 }
 
+// The ones below are not currently exported to Python.
 bool ThreadPlanPython::WillStop() {
   Log *log = GetLog(LLDBLog::Thread);
   LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION,
             m_class_name.c_str());
   return true;
 }
+
+bool ThreadPlanPython::DoWillResume(lldb::StateType resume_state, 
+                                  bool current_plan) {
+  m_stop_description.Clear();
+  return true;                                  
+}

diff  --git a/lldb/test/API/functionalities/step_scripted/Steps.py b/lldb/test/API/functionalities/step_scripted/Steps.py
index d41d01d43095e..6b49de772f8a0 100644
--- a/lldb/test/API/functionalities/step_scripted/Steps.py
+++ b/lldb/test/API/functionalities/step_scripted/Steps.py
@@ -19,6 +19,11 @@ def should_stop(self, event):
     def should_step(self):
         return False
 
+    def stop_description(self, stream):
+        if self.child_thread_plan.IsPlanComplete():
+            return self.child_thread_plan.GetDescription(stream)
+        return True
+
     def queue_child_thread_plan(self):
         return None
 
@@ -39,19 +44,18 @@ def queue_child_thread_plan(self):
 # This plan does a step-over until a variable changes value.
 class StepUntil(StepWithChild):
     def __init__(self, thread_plan, args_data, dict):
+        self.thread_plan = thread_plan
         self.frame = thread_plan.GetThread().frames[0]
         self.target = thread_plan.GetThread().GetProcess().GetTarget()
-        func_entry = args_data.GetValueForKey("variable_name")
+        var_entry = args_data.GetValueForKey("variable_name")
         
-        if not func_entry.IsValid():
+        if not var_entry.IsValid():
             print("Did not get a valid entry for variable_name")
-        func_name = func_entry.GetStringValue(100)
+        self.var_name = var_entry.GetStringValue(100)
 
-        self.value = self.frame.FindVariable(func_name)
+        self.value = self.frame.FindVariable(self.var_name)
         if self.value.GetError().Fail():
             print("Failed to get foo value: %s"%(self.value.GetError().GetCString()))
-        else:
-            print("'foo' value: %d"%(self.value.GetValueAsUnsigned()))
 
         StepWithChild.__init__(self, thread_plan)
 
@@ -70,17 +74,23 @@ def should_stop(self, event):
 
         # If we've stepped out of this frame, stop.
         if not self.frame.IsValid():
+            self.thread_plan.SetPlanComplete(True)
             return True
 
         if not self.value.IsValid():
+            self.thread_plan.SetPlanComplete(True)
             return True
 
         if not self.value.GetValueDidChange():
             self.child_thread_plan = self.queue_child_thread_plan()
             return False
         else:
+            self.thread_plan.SetPlanComplete(True)
             return True
 
+    def stop_description(self, stream):
+        stream.Print(f"Stepped until {self.var_name} changed.")
+        
 # This plan does nothing, but sets stop_mode to the
 # value of GetStopOthers for this plan.
 class StepReportsStopOthers():
@@ -92,7 +102,6 @@ def __init__(self, thread_plan, args_data, dict):
         
     def should_stop(self, event):
         self.thread_plan.SetPlanComplete(True)
-        print("Called in should_stop")
         StepReportsStopOthers.stop_mode_dict[self.key] = self.thread_plan.GetStopOthers()
         return True
 

diff  --git a/lldb/test/API/functionalities/step_scripted/TestStepScripted.py b/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
index b02c5ebfd90ca..50482782a3665 100644
--- a/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
+++ b/lldb/test/API/functionalities/step_scripted/TestStepScripted.py
@@ -41,7 +41,8 @@ def step_out_with_scripted_plan(self, name):
 
         frame = thread.GetFrameAtIndex(0)
         self.assertEqual("main", frame.GetFunctionName())
-
+        stop_desc = thread.GetStopDescription(1000)
+        self.assertIn("Stepping out from", stop_desc, "Got right description")
 
     def test_misspelled_plan_name(self):
         """Test that we get a useful error if we misspell the plan class name"""
@@ -106,6 +107,10 @@ def do_test_checking_variable(self, use_cli):
         # And foo should have changed:
         self.assertTrue(foo_val.GetValueDidChange(), "Foo changed")
 
+        # And we should have a reasonable stop description:
+        desc = thread.GetStopDescription(1000)
+        self.assertIn("Stepped until foo changed", desc, "Got right stop description")
+
     def test_stop_others_from_command(self):
         """Test that the stop-others flag is set correctly by the command line.
            Also test that the run-all-threads property overrides this."""
@@ -119,10 +124,12 @@ def run_step(self, stop_others_value, run_mode, token):
         cmd = "thread step-scripted -C Steps.StepReportsStopOthers -k token -v %s"%(token)
         if run_mode != None:
             cmd = cmd + " --run-mode %s"%(run_mode)
-        print(cmd)
+        if self.TraceOn():
+            print(cmd)
         interp.HandleCommand(cmd, result)
         self.assertTrue(result.Succeeded(), "Step scripted failed: %s."%(result.GetError()))
-        print(Steps.StepReportsStopOthers.stop_mode_dict)
+        if self.TraceOn():
+            print(Steps.StepReportsStopOthers.stop_mode_dict)
         value = Steps.StepReportsStopOthers.stop_mode_dict[token]
         self.assertEqual(value, stop_others_value, "Stop others has the correct value.")
 

diff  --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index 4b562192579ec..2883b39639e1a 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -107,6 +107,14 @@ bool lldb_private::LLDBSWIGPythonCallThreadPlan(void *implementor,
   return false;
 }
 
+bool 
+lldb_private::LLDBSWIGPythonCallThreadPlan(void *implementor,
+                                                const char *method_name,
+                                                Stream *event_sp,
+                                                bool &got_error) {
+  return false;
+}
+
 python::PythonObject
 lldb_private::LLDBSwigPythonCreateScriptedBreakpointResolver(
     const char *python_class_name, const char *session_dictionary_name,


        


More information about the lldb-commits mailing list