Skip to content

Commit cee7200

Browse files
Merge pull request #6873 from jimingham/thread-plan-stop-description
Allow scripted thread plans to modify the thread stop description when
2 parents 3c71bcc + 84cb1c6 commit cee7200

File tree

12 files changed

+186
-15
lines changed

12 files changed

+186
-15
lines changed

lldb/bindings/python/python-wrapper.swig

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,38 @@ bool lldb_private::LLDBSWIGPythonCallThreadPlan(
341341
return false;
342342
}
343343

344+
bool lldb_private::LLDBSWIGPythonCallThreadPlan(
345+
void *implementor, const char *method_name, lldb_private::Stream *stream,
346+
bool &got_error) {
347+
got_error = false;
348+
349+
PyErr_Cleaner py_err_cleaner(false);
350+
PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementor));
351+
auto pfunc = self.ResolveName<PythonCallable>(method_name);
352+
353+
if (!pfunc.IsAllocated())
354+
return false;
355+
356+
auto *sb_stream = new lldb::SBStream();
357+
PythonObject sb_stream_arg =
358+
ToSWIGWrapper(std::unique_ptr<lldb::SBStream>(sb_stream));
359+
360+
PythonObject result;
361+
result = pfunc(sb_stream_arg);
362+
363+
if (PyErr_Occurred()) {
364+
printf("Error occured for call to %s.\n",
365+
method_name);
366+
PyErr_Print();
367+
got_error = true;
368+
return false;
369+
}
370+
if (stream)
371+
stream->PutCString(sb_stream->GetData());
372+
return true;
373+
374+
}
375+
344376
PythonObject lldb_private::LLDBSwigPythonCreateScriptedBreakpointResolver(
345377
const char *python_class_name, const char *session_dictionary_name,
346378
const StructuredDataImpl &args_impl,

lldb/bindings/python/static-binding/LLDBWrapPython.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4970,6 +4970,38 @@ bool lldb_private::LLDBSWIGPythonCallThreadPlan(
49704970
return false;
49714971
}
49724972

4973+
bool lldb_private::LLDBSWIGPythonCallThreadPlan(
4974+
void *implementor, const char *method_name, lldb_private::Stream *stream,
4975+
bool &got_error) {
4976+
got_error = false;
4977+
4978+
PyErr_Cleaner py_err_cleaner(false);
4979+
PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementor));
4980+
auto pfunc = self.ResolveName<PythonCallable>(method_name);
4981+
4982+
if (!pfunc.IsAllocated())
4983+
return false;
4984+
4985+
auto *sb_stream = new lldb::SBStream();
4986+
PythonObject sb_stream_arg =
4987+
ToSWIGWrapper(std::unique_ptr<lldb::SBStream>(sb_stream));
4988+
4989+
PythonObject result;
4990+
result = pfunc(sb_stream_arg);
4991+
4992+
if (PyErr_Occurred()) {
4993+
printf("Error occured for call to %s.\n",
4994+
method_name);
4995+
PyErr_Print();
4996+
got_error = true;
4997+
return false;
4998+
}
4999+
if (stream)
5000+
stream->PutCString(sb_stream->GetData());
5001+
return true;
5002+
5003+
}
5004+
49735005
PythonObject lldb_private::LLDBSwigPythonCreateScriptedBreakpointResolver(
49745006
const char *python_class_name, const char *session_dictionary_name,
49755007
const StructuredDataImpl &args_impl,

lldb/examples/python/scripted_step.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@
7777
# is stale. In this case, if the step_out plan that the FinishPrintAndContinue
7878
# plan is driving is stale, so is ours, and it is time to do our printing.
7979
#
80+
# 4) If you implement the "stop_description(SBStream stream)" method in your
81+
# python class, then that will show up as the "plan completed" reason when
82+
# your thread plan is complete.
83+
#
8084
# Both examples show stepping through an address range for 20 bytes from the
8185
# current PC. The first one does it by single stepping and checking a condition.
8286
# It doesn't, however handle the case where you step into another frame while
@@ -122,6 +126,8 @@ def should_stop(self, event):
122126
def should_step(self):
123127
return True
124128

129+
def stop_description(self, stream):
130+
stream.Print("Simple step completed")
125131

126132
class StepWithPlan:
127133

@@ -146,6 +152,9 @@ def should_stop(self, event):
146152
def should_step(self):
147153
return False
148154

155+
def stop_description(self, stream):
156+
self.step_thread_plan.GetDescription(stream, lldb.eDescriptionLevelBrief)
157+
149158
# Here's another example which does "step over" through the current function,
150159
# and when it stops at each line, it checks some condition (in this example the
151160
# value of a variable) and stops if that condition is true.
@@ -205,6 +214,9 @@ def should_stop(self, event):
205214
def should_step(self):
206215
return True
207216

217+
def stop_description(self, stream):
218+
stream.Print(f"Stepped until a == 20")
219+
208220
# Here's an example that steps out of the current frame, gathers some information
209221
# and then continues. The information in this case is rax. Currently the thread
210222
# plans are not a safe place to call lldb command-line commands, so the information
@@ -242,3 +254,6 @@ def do_print(self):
242254
print("RAX on exit: ", rax_value.GetValue())
243255
else:
244256
print("Couldn't get rax value:", rax_value.GetError().GetCString())
257+
258+
def stop_description(self, stream):
259+
self.step_out_thread_plan.GetDescription(stream, lldb.eDescriptionLevelBrief)

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ class ScriptInterpreter : public PluginInterface {
309309
return lldb::eStateStepping;
310310
}
311311

312+
virtual bool
313+
ScriptedThreadPlanGetStopDescription(StructuredData::ObjectSP implementor_sp,
314+
lldb_private::Stream *stream,
315+
bool &script_error) {
316+
script_error = true;
317+
return false;
318+
}
319+
312320
virtual StructuredData::GenericSP
313321
CreateScriptedBreakpointResolver(const char *class_name,
314322
const StructuredDataImpl &args_data,

lldb/include/lldb/Target/ThreadPlanPython.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class ThreadPlanPython : public ThreadPlan {
5151
void DidPush() override;
5252

5353
bool IsPlanStale() override;
54+
55+
bool DoWillResume(lldb::StateType resume_state, bool current_plan) override;
56+
5457

5558
protected:
5659
bool DoPlanExplainsStop(Event *event_ptr) override;
@@ -64,6 +67,7 @@ class ThreadPlanPython : public ThreadPlan {
6467
StructuredDataImpl m_args_data;
6568
std::string m_error_str;
6669
StructuredData::ObjectSP m_implementation_sp;
70+
StreamString m_stop_description; // Cache the stop description here
6771
bool m_did_push;
6872
bool m_stop_others;
6973

lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ python::PythonObject LLDBSwigPythonCreateScriptedThreadPlan(
143143
bool LLDBSWIGPythonCallThreadPlan(void *implementor, const char *method_name,
144144
lldb_private::Event *event_sp,
145145
bool &got_error);
146+
147+
bool LLDBSWIGPythonCallThreadPlan(void *implementor,
148+
const char *method_name,
149+
lldb_private::Stream *stream,
150+
bool &got_error);
146151

147152
python::PythonObject LLDBSwigPythonCreateScriptedBreakpointResolver(
148153
const char *python_class_name, const char *session_dictionary_name,

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,7 +1739,7 @@ bool ScriptInterpreterPythonImpl::ScriptedThreadPlanIsStale(
17391739
Locker py_lock(this,
17401740
Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
17411741
is_stale = LLDBSWIGPythonCallThreadPlan(generic->GetValue(), "is_stale",
1742-
nullptr, script_error);
1742+
(Event *) nullptr, script_error);
17431743
if (script_error)
17441744
return true;
17451745
}
@@ -1756,7 +1756,7 @@ lldb::StateType ScriptInterpreterPythonImpl::ScriptedThreadPlanGetRunState(
17561756
Locker py_lock(this,
17571757
Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
17581758
should_step = LLDBSWIGPythonCallThreadPlan(
1759-
generic->GetValue(), "should_step", nullptr, script_error);
1759+
generic->GetValue(), "should_step", (Event *) nullptr, script_error);
17601760
if (script_error)
17611761
should_step = true;
17621762
}
@@ -1765,6 +1765,24 @@ lldb::StateType ScriptInterpreterPythonImpl::ScriptedThreadPlanGetRunState(
17651765
return lldb::eStateRunning;
17661766
}
17671767

1768+
bool
1769+
ScriptInterpreterPythonImpl::ScriptedThreadPlanGetStopDescription(
1770+
StructuredData::ObjectSP implementor_sp, lldb_private::Stream *stream,
1771+
bool &script_error) {
1772+
StructuredData::Generic *generic = nullptr;
1773+
if (implementor_sp)
1774+
generic = implementor_sp->GetAsGeneric();
1775+
if (!generic) {
1776+
script_error = true;
1777+
return false;
1778+
}
1779+
Locker py_lock(this,
1780+
Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
1781+
return LLDBSWIGPythonCallThreadPlan(generic->GetValue(), "stop_description",
1782+
stream, script_error);
1783+
}
1784+
1785+
17681786
StructuredData::GenericSP
17691787
ScriptInterpreterPythonImpl::CreateScriptedBreakpointResolver(
17701788
const char *class_name, const StructuredDataImpl &args_data,

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
9797
ScriptedThreadPlanGetRunState(StructuredData::ObjectSP implementor_sp,
9898
bool &script_error) override;
9999

100+
bool
101+
ScriptedThreadPlanGetStopDescription(StructuredData::ObjectSP implementor_sp,
102+
lldb_private::Stream *s,
103+
bool &script_error) override;
104+
100105
StructuredData::GenericSP
101106
CreateScriptedBreakpointResolver(const char *class_name,
102107
const StructuredDataImpl &args_data,

lldb/source/Target/ThreadPlanPython.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,11 @@ bool ThreadPlanPython::MischiefManaged() {
136136
// I don't really need mischief_managed, since it's simpler to just call
137137
// SetPlanComplete in should_stop.
138138
mischief_managed = IsPlanComplete();
139-
if (mischief_managed)
139+
if (mischief_managed) {
140+
// We need to cache the stop reason here we'll need it in GetDescription.
141+
GetDescription(&m_stop_description, eDescriptionLevelBrief);
140142
m_implementation_sp.reset();
143+
}
141144
}
142145
return mischief_managed;
143146
}
@@ -158,15 +161,40 @@ lldb::StateType ThreadPlanPython::GetPlanRunState() {
158161
return run_state;
159162
}
160163

161-
// The ones below are not currently exported to Python.
162164
void ThreadPlanPython::GetDescription(Stream *s, lldb::DescriptionLevel level) {
163-
s->Printf("Python thread plan implemented by class %s.",
165+
Log *log = GetLog(LLDBLog::Thread);
166+
LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION,
167+
m_class_name.c_str());
168+
if (m_implementation_sp) {
169+
ScriptInterpreter *script_interp = GetScriptInterpreter();
170+
if (script_interp) {
171+
bool script_error;
172+
bool added_desc = script_interp->ScriptedThreadPlanGetStopDescription(
173+
m_implementation_sp, s, script_error);
174+
if (script_error || !added_desc)
175+
s->Printf("Python thread plan implemented by class %s.",
164176
m_class_name.c_str());
177+
}
178+
return;
179+
}
180+
// It's an error not to have a description, so if we get here, we should
181+
// add something.
182+
if (m_stop_description.Empty())
183+
s->Printf("Python thread plan implemented by class %s.",
184+
m_class_name.c_str());
185+
s->PutCString(m_stop_description.GetData());
165186
}
166187

188+
// The ones below are not currently exported to Python.
167189
bool ThreadPlanPython::WillStop() {
168190
Log *log = GetLog(LLDBLog::Thread);
169191
LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION,
170192
m_class_name.c_str());
171193
return true;
172194
}
195+
196+
bool ThreadPlanPython::DoWillResume(lldb::StateType resume_state,
197+
bool current_plan) {
198+
m_stop_description.Clear();
199+
return true;
200+
}

lldb/test/API/functionalities/step_scripted/Steps.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ def should_stop(self, event):
1919
def should_step(self):
2020
return False
2121

22+
def stop_description(self, stream):
23+
if self.child_thread_plan.IsPlanComplete():
24+
return self.child_thread_plan.GetDescription(stream)
25+
return True
26+
2227
def queue_child_thread_plan(self):
2328
return None
2429

@@ -39,19 +44,18 @@ def queue_child_thread_plan(self):
3944
# This plan does a step-over until a variable changes value.
4045
class StepUntil(StepWithChild):
4146
def __init__(self, thread_plan, args_data, dict):
47+
self.thread_plan = thread_plan
4248
self.frame = thread_plan.GetThread().frames[0]
4349
self.target = thread_plan.GetThread().GetProcess().GetTarget()
44-
func_entry = args_data.GetValueForKey("variable_name")
50+
var_entry = args_data.GetValueForKey("variable_name")
4551

46-
if not func_entry.IsValid():
52+
if not var_entry.IsValid():
4753
print("Did not get a valid entry for variable_name")
48-
func_name = func_entry.GetStringValue(100)
54+
self.var_name = var_entry.GetStringValue(100)
4955

50-
self.value = self.frame.FindVariable(func_name)
56+
self.value = self.frame.FindVariable(self.var_name)
5157
if self.value.GetError().Fail():
5258
print("Failed to get foo value: %s"%(self.value.GetError().GetCString()))
53-
else:
54-
print("'foo' value: %d"%(self.value.GetValueAsUnsigned()))
5559

5660
StepWithChild.__init__(self, thread_plan)
5761

@@ -70,17 +74,23 @@ def should_stop(self, event):
7074

7175
# If we've stepped out of this frame, stop.
7276
if not self.frame.IsValid():
77+
self.thread_plan.SetPlanComplete(True)
7378
return True
7479

7580
if not self.value.IsValid():
81+
self.thread_plan.SetPlanComplete(True)
7682
return True
7783

7884
if not self.value.GetValueDidChange():
7985
self.child_thread_plan = self.queue_child_thread_plan()
8086
return False
8187
else:
88+
self.thread_plan.SetPlanComplete(True)
8289
return True
8390

91+
def stop_description(self, stream):
92+
stream.Print(f"Stepped until {self.var_name} changed.")
93+
8494
# This plan does nothing, but sets stop_mode to the
8595
# value of GetStopOthers for this plan.
8696
class StepReportsStopOthers():
@@ -92,7 +102,6 @@ def __init__(self, thread_plan, args_data, dict):
92102

93103
def should_stop(self, event):
94104
self.thread_plan.SetPlanComplete(True)
95-
print("Called in should_stop")
96105
StepReportsStopOthers.stop_mode_dict[self.key] = self.thread_plan.GetStopOthers()
97106
return True
98107

0 commit comments

Comments
 (0)