diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -367,6 +367,38 @@ 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(implementor)); + auto pfunc = self.ResolveName(method_name); + + if (!pfunc.IsAllocated()) + return false; + + auto *sb_stream = new lldb::SBStream(); + PythonObject sb_stream_arg = + ToSWIGWrapper(std::unique_ptr(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 --- 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_step(self): return True + def stop_description(self, stream): + stream.Print("Simple step completed") class StepWithPlan: @@ -146,6 +152,9 @@ def should_step(self): return False + def stop_description(self, stream): + self.step_thread_plan.GetDescription(s, 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_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 @@ 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(s, lldb.eDescriptionLevelBrief) diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -314,6 +314,14 @@ 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 --- a/lldb/include/lldb/Target/ThreadPlanPython.h +++ b/lldb/include/lldb/Target/ThreadPlanPython.h @@ -51,6 +51,9 @@ 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 @@ 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 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -148,6 +148,11 @@ 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 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1743,7 +1743,7 @@ 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 @@ 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 @@ 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 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -97,6 +97,11 @@ 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 --- a/lldb/source/Target/ThreadPlanPython.cpp +++ b/lldb/source/Target/ThreadPlanPython.cpp @@ -136,8 +136,11 @@ // 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 @@ 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 --- a/lldb/test/API/functionalities/step_scripted/Steps.py +++ b/lldb/test/API/functionalities/step_scripted/Steps.py @@ -19,6 +19,11 @@ 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 @@ # 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 @@ # 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 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 --- a/lldb/test/API/functionalities/step_scripted/TestStepScripted.py +++ b/lldb/test/API/functionalities/step_scripted/TestStepScripted.py @@ -41,7 +41,8 @@ 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 @@ # 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 @@ 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 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -107,6 +107,14 @@ 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,