Skip to content

Commit b65966c

Browse files
committed
Add the ability to write target stop-hooks using the ScriptInterpreter.
Differential Revision: https://reviews.llvm.org/D88123
1 parent 58cdbf5 commit b65966c

File tree

19 files changed

+908
-132
lines changed

19 files changed

+908
-132
lines changed

lldb/bindings/python/python-swigsafecast.swig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,10 @@ SBTypeToSWIGWrapper (lldb::SBSymbolContext* sym_ctx_sb)
152152
{
153153
return SWIG_NewPointerObj((void *) sym_ctx_sb, SWIGTYPE_p_lldb__SBSymbolContext, 0);
154154
}
155+
156+
template <>
157+
PyObject*
158+
SBTypeToSWIGWrapper (lldb::SBStream* stream_sb)
159+
{
160+
return SWIG_NewPointerObj((void *) stream_sb, SWIGTYPE_p_lldb__SBStream, 0);
161+
}

lldb/bindings/python/python-wrapper.swig

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,123 @@ LLDBSwigPythonCallBreakpointResolver
468468
return ret_val;
469469
}
470470

471+
SWIGEXPORT void *
472+
LLDBSwigPythonCreateScriptedStopHook
473+
(
474+
lldb::TargetSP target_sp,
475+
const char *python_class_name,
476+
const char *session_dictionary_name,
477+
lldb_private::StructuredDataImpl *args_impl,
478+
Status &error
479+
)
480+
{
481+
if (python_class_name == NULL || python_class_name[0] == '\0') {
482+
error.SetErrorString("Empty class name.");
483+
Py_RETURN_NONE;
484+
}
485+
if (!session_dictionary_name) {
486+
error.SetErrorString("No session dictionary");
487+
Py_RETURN_NONE;
488+
}
489+
490+
PyErr_Cleaner py_err_cleaner(true);
491+
492+
auto dict =
493+
PythonModule::MainModule().ResolveName<PythonDictionary>(
494+
session_dictionary_name);
495+
auto pfunc =
496+
PythonObject::ResolveNameWithDictionary<PythonCallable>(
497+
python_class_name, dict);
498+
499+
if (!pfunc.IsAllocated()) {
500+
error.SetErrorStringWithFormat("Could not find class: %s.",
501+
python_class_name);
502+
return nullptr;
503+
}
504+
505+
lldb::SBTarget *target_val
506+
= new lldb::SBTarget(target_sp);
507+
508+
PythonObject target_arg(PyRefType::Owned, SBTypeToSWIGWrapper(target_val));
509+
510+
lldb::SBStructuredData *args_value = new lldb::SBStructuredData(args_impl);
511+
PythonObject args_arg(PyRefType::Owned, SBTypeToSWIGWrapper(args_value));
512+
513+
PythonObject result = pfunc(target_arg, args_arg, dict);
514+
515+
if (result.IsAllocated())
516+
{
517+
// Check that the handle_stop callback is defined:
518+
auto callback_func = result.ResolveName<PythonCallable>("handle_stop");
519+
if (callback_func.IsAllocated()) {
520+
if (auto args_info = callback_func.GetArgInfo()) {
521+
if ((*args_info).max_positional_args < 2) {
522+
error.SetErrorStringWithFormat("Wrong number of args for "
523+
"handle_stop callback, should be 2 (excluding self), got: %d",
524+
(*args_info).max_positional_args);
525+
} else
526+
return result.release();
527+
} else {
528+
error.SetErrorString("Couldn't get num arguments for handle_stop "
529+
"callback.");
530+
}
531+
return result.release();
532+
}
533+
else {
534+
error.SetErrorStringWithFormat("Class \"%s\" is missing the required "
535+
"handle_stop callback.");
536+
result.release();
537+
}
538+
}
539+
Py_RETURN_NONE;
540+
}
541+
542+
SWIGEXPORT bool
543+
LLDBSwigPythonStopHookCallHandleStop
544+
(
545+
void *implementor,
546+
lldb::ExecutionContextRefSP exc_ctx_sp,
547+
lldb::StreamSP stream
548+
)
549+
{
550+
// handle_stop will return a bool with the meaning "should_stop"...
551+
// If you return nothing we'll assume we are going to stop.
552+
// Also any errors should return true, since we should stop on error.
553+
554+
PyErr_Cleaner py_err_cleaner(false);
555+
PythonObject self(PyRefType::Borrowed, static_cast<PyObject*>(implementor));
556+
auto pfunc = self.ResolveName<PythonCallable>("handle_stop");
557+
558+
if (!pfunc.IsAllocated())
559+
return true;
560+
561+
PythonObject result;
562+
lldb::SBExecutionContext sb_exc_ctx(exc_ctx_sp);
563+
PythonObject exc_ctx_arg(PyRefType::Owned, SBTypeToSWIGWrapper(sb_exc_ctx));
564+
lldb::SBStream sb_stream;
565+
PythonObject sb_stream_arg(PyRefType::Owned,
566+
SBTypeToSWIGWrapper(sb_stream));
567+
result = pfunc(exc_ctx_arg, sb_stream_arg);
568+
569+
if (PyErr_Occurred())
570+
{
571+
stream->PutCString("Python error occurred handling stop-hook.");
572+
PyErr_Print();
573+
PyErr_Clear();
574+
return true;
575+
}
576+
577+
// Now add the result to the output stream. SBStream only
578+
// makes an internally help StreamString which I can't interpose, so I
579+
// have to copy it over here.
580+
stream->PutCString(sb_stream.GetData());
581+
582+
if (result.get() == Py_False)
583+
return false;
584+
else
585+
return true;
586+
}
587+
471588
// wrapper that calls an optional instance member of an object taking no arguments
472589
static PyObject*
473590
LLDBSwigPython_CallOptionalMember

lldb/docs/use/python-reference.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,3 +819,49 @@ When the program is stopped at the beginning of the 'read' function in libc, we
819819
frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read
820820
(lldb) frame variable
821821
(int) fd = 3
822+
823+
Writing Target Stop-Hooks in Python:
824+
------------------------------------
825+
826+
Stop hooks fire whenever the process stops just before control is returned to the
827+
user. Stop hooks can either be a set of lldb command-line commands, or can
828+
be implemented by a suitably defined Python class. The Python based stop-hooks
829+
can also be passed as set of -key -value pairs when they are added, and those
830+
will get packaged up into a SBStructuredData Dictionary and passed to the
831+
constructor of the Python object managing the stop hook. This allows for
832+
parametrization of the stop hooks.
833+
834+
To add a Python-based stop hook, first define a class with the following methods:
835+
836+
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
837+
| Name | Arguments | Description |
838+
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
839+
| **__init__** | **target: lldb.SBTarget** | This is the constructor for the new stop-hook. |
840+
| | **extra_args: lldb.SBStructuredData** | |
841+
| | | |
842+
| | | **target** is the SBTarget to which the stop hook is added. |
843+
| | | |
844+
| | | **extra_args** is an SBStructuredData object that the user can pass in when creating instances of this |
845+
| | | breakpoint. It is not required, but allows for reuse of stop-hook classes. |
846+
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
847+
| **handle_stop** | **exe_ctx: lldb.SBExecutionContext** | This is the called when the target stops. |
848+
| | **stream: lldb.SBStream** | |
849+
| | | **exe_ctx** argument will be filled with the current stop point for which the stop hook is |
850+
| | | being evaluated. |
851+
| | | |
852+
| | | **stream** an lldb.SBStream, anything written to this stream will be written to the debugger console. |
853+
| | | |
854+
| | | The return value is a "Should Stop" vote from this thread. If the method returns either True or no return |
855+
| | | this thread votes to stop. If it returns False, then the thread votes to continue after all the stop-hooks |
856+
| | | are evaluated. |
857+
| | | Note, the --auto-continue flag to 'target stop-hook add' overrides a True return value from the method. |
858+
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
859+
860+
To use this class in lldb, run the command:
861+
862+
::
863+
864+
(lldb) command script import MyModule.py
865+
(lldb) target stop-hook add -P MyModule.MyStopHook -k first -v 1 -k second -v 2
866+
867+
where MyModule.py is the file containing the class definition MyStopHook.

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,23 @@ class ScriptInterpreter : public PluginInterface {
298298
return lldb::eSearchDepthModule;
299299
}
300300

301+
virtual StructuredData::GenericSP
302+
CreateScriptedStopHook(lldb::TargetSP target_sp, const char *class_name,
303+
StructuredDataImpl *args_data, Status &error) {
304+
error.SetErrorString("Creating scripted stop-hooks with the current "
305+
"script interpreter is not supported.");
306+
return StructuredData::GenericSP();
307+
}
308+
309+
// This dispatches to the handle_stop method of the stop-hook class. It
310+
// returns a "should_stop" bool.
311+
virtual bool
312+
ScriptedStopHookHandleStop(StructuredData::GenericSP implementor_sp,
313+
ExecutionContext &exc_ctx,
314+
lldb::StreamSP stream_sp) {
315+
return true;
316+
}
317+
301318
virtual StructuredData::ObjectSP
302319
LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
303320
return StructuredData::ObjectSP();

lldb/include/lldb/Symbol/SymbolContext.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ class SymbolContextSpecifier {
340340

341341
void Clear();
342342

343-
bool SymbolContextMatches(SymbolContext &sc);
343+
bool SymbolContextMatches(const SymbolContext &sc);
344344

345345
bool AddressMatches(lldb::addr_t addr);
346346

lldb/include/lldb/Target/Target.h

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "lldb/Target/ExecutionContextScope.h"
2929
#include "lldb/Target/PathMappingList.h"
3030
#include "lldb/Target/SectionLoadHistory.h"
31+
#include "lldb/Target/ThreadSpec.h"
3132
#include "lldb/Utility/ArchSpec.h"
3233
#include "lldb/Utility/Broadcaster.h"
3334
#include "lldb/Utility/LLDBAssert.h"
@@ -508,6 +509,8 @@ class Target : public std::enable_shared_from_this<Target>,
508509

509510
static void SetDefaultArchitecture(const ArchSpec &arch);
510511

512+
bool IsDummyTarget() const { return m_is_dummy_target; }
513+
511514
/// Find a binary on the system and return its Module,
512515
/// or return an existing Module that is already in the Target.
513516
///
@@ -1139,23 +1142,27 @@ class Target : public std::enable_shared_from_this<Target>,
11391142
class StopHook : public UserID {
11401143
public:
11411144
StopHook(const StopHook &rhs);
1145+
virtual ~StopHook() = default;
11421146

1143-
~StopHook();
1144-
1145-
StringList *GetCommandPointer() { return &m_commands; }
1146-
1147-
const StringList &GetCommands() { return m_commands; }
1147+
enum class StopHookKind : uint32_t { CommandBased = 0, ScriptBased };
11481148

11491149
lldb::TargetSP &GetTarget() { return m_target_sp; }
11501150

1151-
void SetCommands(StringList &in_commands) { m_commands = in_commands; }
1152-
11531151
// Set the specifier. The stop hook will own the specifier, and is
11541152
// responsible for deleting it when we're done.
11551153
void SetSpecifier(SymbolContextSpecifier *specifier);
11561154

11571155
SymbolContextSpecifier *GetSpecifier() { return m_specifier_sp.get(); }
11581156

1157+
bool ExecutionContextPasses(const ExecutionContext &exe_ctx);
1158+
1159+
// Called on stop, this gets passed the ExecutionContext for each "stop
1160+
// with a reason" thread. It should add to the stream whatever text it
1161+
// wants to show the user, and return False to indicate it wants the target
1162+
// not to stop.
1163+
virtual bool HandleStop(ExecutionContext &exe_ctx,
1164+
lldb::StreamSP output) = 0;
1165+
11591166
// Set the Thread Specifier. The stop hook will own the thread specifier,
11601167
// and is responsible for deleting it when we're done.
11611168
void SetThreadSpecifier(ThreadSpec *specifier);
@@ -1173,26 +1180,79 @@ class Target : public std::enable_shared_from_this<Target>,
11731180
bool GetAutoContinue() const { return m_auto_continue; }
11741181

11751182
void GetDescription(Stream *s, lldb::DescriptionLevel level) const;
1183+
virtual void GetSubclassDescription(Stream *s,
1184+
lldb::DescriptionLevel level) const = 0;
11761185

1177-
private:
1186+
protected:
11781187
lldb::TargetSP m_target_sp;
1179-
StringList m_commands;
11801188
lldb::SymbolContextSpecifierSP m_specifier_sp;
11811189
std::unique_ptr<ThreadSpec> m_thread_spec_up;
11821190
bool m_active = true;
11831191
bool m_auto_continue = false;
11841192

1193+
StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
1194+
};
1195+
1196+
class StopHookCommandLine : public StopHook {
1197+
public:
1198+
virtual ~StopHookCommandLine() = default;
1199+
1200+
StringList &GetCommands() { return m_commands; }
1201+
void SetActionFromString(const std::string &strings);
1202+
void SetActionFromStrings(const std::vector<std::string> &strings);
1203+
1204+
bool HandleStop(ExecutionContext &exc_ctx,
1205+
lldb::StreamSP output_sp) override;
1206+
void GetSubclassDescription(Stream *s,
1207+
lldb::DescriptionLevel level) const override;
1208+
1209+
private:
1210+
StringList m_commands;
11851211
// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
11861212
// and fill it with commands, and SetSpecifier to set the specifier shared
11871213
// pointer (can be null, that will match anything.)
1188-
StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
1214+
StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
1215+
: StopHook(target_sp, uid) {}
1216+
friend class Target;
1217+
};
1218+
1219+
class StopHookScripted : public StopHook {
1220+
public:
1221+
virtual ~StopHookScripted() = default;
1222+
bool HandleStop(ExecutionContext &exc_ctx, lldb::StreamSP output) override;
1223+
1224+
Status SetScriptCallback(std::string class_name,
1225+
StructuredData::ObjectSP extra_args_sp);
1226+
1227+
void GetSubclassDescription(Stream *s,
1228+
lldb::DescriptionLevel level) const override;
1229+
1230+
private:
1231+
std::string m_class_name;
1232+
/// This holds the dictionary of keys & values that can be used to
1233+
/// parametrize any given callback's behavior.
1234+
StructuredDataImpl *m_extra_args; // We own this structured data,
1235+
// but the SD itself manages the UP.
1236+
/// This holds the python callback object.
1237+
StructuredData::GenericSP m_implementation_sp;
1238+
1239+
/// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
1240+
/// and fill it with commands, and SetSpecifier to set the specifier shared
1241+
/// pointer (can be null, that will match anything.)
1242+
StopHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
1243+
: StopHook(target_sp, uid) {}
11891244
friend class Target;
11901245
};
1246+
11911247
typedef std::shared_ptr<StopHook> StopHookSP;
11921248

1193-
// Add an empty stop hook to the Target's stop hook list, and returns a
1194-
// shared pointer to it in new_hook. Returns the id of the new hook.
1195-
StopHookSP CreateStopHook();
1249+
/// Add an empty stop hook to the Target's stop hook list, and returns a
1250+
/// shared pointer to it in new_hook. Returns the id of the new hook.
1251+
StopHookSP CreateStopHook(StopHook::StopHookKind kind);
1252+
1253+
/// If you tried to create a stop hook, and that failed, call this to
1254+
/// remove the stop hook, as it will also reset the stop hook counter.
1255+
void UndoCreateStopHook(lldb::user_id_t uid);
11961256

11971257
void RunStopHooks();
11981258

0 commit comments

Comments
 (0)