Skip to content

Commit 1b1d981

Browse files
committed
Revert "Revert "Add the ability to write target stop-hooks using the ScriptInterpreter.""
This reverts commit f775fe5. I fixed a return type error in the original patch that was causing a test failure. Also added a REQUIRES: python to the shell test so we'll skip this for people who build lldb w/o Python. Also added another test for the error printing.
1 parent 962a247 commit 1b1d981

File tree

19 files changed

+943
-132
lines changed

19 files changed

+943
-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: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,127 @@ 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+
size_t num_args = (*args_info).max_positional_args;
522+
if (num_args != 2) {
523+
error.SetErrorStringWithFormat("Wrong number of args for "
524+
"handle_stop callback, should be 2 (excluding self), got: %d",
525+
num_args);
526+
Py_RETURN_NONE;
527+
} else
528+
return result.release();
529+
} else {
530+
error.SetErrorString("Couldn't get num arguments for handle_stop "
531+
"callback.");
532+
Py_RETURN_NONE;
533+
}
534+
return result.release();
535+
}
536+
else {
537+
error.SetErrorStringWithFormat("Class \"%s\" is missing the required "
538+
"handle_stop callback.",
539+
python_class_name);
540+
result.release();
541+
}
542+
}
543+
Py_RETURN_NONE;
544+
}
545+
546+
SWIGEXPORT bool
547+
LLDBSwigPythonStopHookCallHandleStop
548+
(
549+
void *implementor,
550+
lldb::ExecutionContextRefSP exc_ctx_sp,
551+
lldb::StreamSP stream
552+
)
553+
{
554+
// handle_stop will return a bool with the meaning "should_stop"...
555+
// If you return nothing we'll assume we are going to stop.
556+
// Also any errors should return true, since we should stop on error.
557+
558+
PyErr_Cleaner py_err_cleaner(false);
559+
PythonObject self(PyRefType::Borrowed, static_cast<PyObject*>(implementor));
560+
auto pfunc = self.ResolveName<PythonCallable>("handle_stop");
561+
562+
if (!pfunc.IsAllocated())
563+
return true;
564+
565+
PythonObject result;
566+
lldb::SBExecutionContext sb_exc_ctx(exc_ctx_sp);
567+
PythonObject exc_ctx_arg(PyRefType::Owned, SBTypeToSWIGWrapper(sb_exc_ctx));
568+
lldb::SBStream sb_stream;
569+
PythonObject sb_stream_arg(PyRefType::Owned,
570+
SBTypeToSWIGWrapper(sb_stream));
571+
result = pfunc(exc_ctx_arg, sb_stream_arg);
572+
573+
if (PyErr_Occurred())
574+
{
575+
stream->PutCString("Python error occurred handling stop-hook.");
576+
PyErr_Print();
577+
PyErr_Clear();
578+
return true;
579+
}
580+
581+
// Now add the result to the output stream. SBStream only
582+
// makes an internally help StreamString which I can't interpose, so I
583+
// have to copy it over here.
584+
stream->PutCString(sb_stream.GetData());
585+
586+
if (result.get() == Py_False)
587+
return false;
588+
else
589+
return true;
590+
}
591+
471592
// wrapper that calls an optional instance member of an object taking no arguments
472593
static PyObject*
473594
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)