Skip to content

Scripted stop hooks #1906

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lldb/bindings/python/python-swigsafecast.swig
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,10 @@ SBTypeToSWIGWrapper (lldb::SBSymbolContext* sym_ctx_sb)
{
return SWIG_NewPointerObj((void *) sym_ctx_sb, SWIGTYPE_p_lldb__SBSymbolContext, 0);
}

template <>
PyObject*
SBTypeToSWIGWrapper (lldb::SBStream* stream_sb)
{
return SWIG_NewPointerObj((void *) stream_sb, SWIGTYPE_p_lldb__SBStream, 0);
}
121 changes: 121 additions & 0 deletions lldb/bindings/python/python-wrapper.swig
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,127 @@ LLDBSwigPythonCallBreakpointResolver
return ret_val;
}

SWIGEXPORT void *
LLDBSwigPythonCreateScriptedStopHook
(
lldb::TargetSP target_sp,
const char *python_class_name,
const char *session_dictionary_name,
lldb_private::StructuredDataImpl *args_impl,
Status &error
)
{
if (python_class_name == NULL || python_class_name[0] == '\0') {
error.SetErrorString("Empty class name.");
Py_RETURN_NONE;
}
if (!session_dictionary_name) {
error.SetErrorString("No session dictionary");
Py_RETURN_NONE;
}

PyErr_Cleaner py_err_cleaner(true);

auto dict =
PythonModule::MainModule().ResolveName<PythonDictionary>(
session_dictionary_name);
auto pfunc =
PythonObject::ResolveNameWithDictionary<PythonCallable>(
python_class_name, dict);

if (!pfunc.IsAllocated()) {
error.SetErrorStringWithFormat("Could not find class: %s.",
python_class_name);
return nullptr;
}

lldb::SBTarget *target_val
= new lldb::SBTarget(target_sp);

PythonObject target_arg(PyRefType::Owned, SBTypeToSWIGWrapper(target_val));

lldb::SBStructuredData *args_value = new lldb::SBStructuredData(args_impl);
PythonObject args_arg(PyRefType::Owned, SBTypeToSWIGWrapper(args_value));

PythonObject result = pfunc(target_arg, args_arg, dict);

if (result.IsAllocated())
{
// Check that the handle_stop callback is defined:
auto callback_func = result.ResolveName<PythonCallable>("handle_stop");
if (callback_func.IsAllocated()) {
if (auto args_info = callback_func.GetArgInfo()) {
size_t num_args = (*args_info).max_positional_args;
if (num_args != 2) {
error.SetErrorStringWithFormat("Wrong number of args for "
"handle_stop callback, should be 2 (excluding self), got: %d",
num_args);
Py_RETURN_NONE;
} else
return result.release();
} else {
error.SetErrorString("Couldn't get num arguments for handle_stop "
"callback.");
Py_RETURN_NONE;
}
return result.release();
}
else {
error.SetErrorStringWithFormat("Class \"%s\" is missing the required "
"handle_stop callback.",
python_class_name);
result.release();
}
}
Py_RETURN_NONE;
}

SWIGEXPORT bool
LLDBSwigPythonStopHookCallHandleStop
(
void *implementor,
lldb::ExecutionContextRefSP exc_ctx_sp,
lldb::StreamSP stream
)
{
// handle_stop will return a bool with the meaning "should_stop"...
// If you return nothing we'll assume we are going to stop.
// Also any errors should return true, since we should stop on error.

PyErr_Cleaner py_err_cleaner(false);
PythonObject self(PyRefType::Borrowed, static_cast<PyObject*>(implementor));
auto pfunc = self.ResolveName<PythonCallable>("handle_stop");

if (!pfunc.IsAllocated())
return true;

PythonObject result;
lldb::SBExecutionContext sb_exc_ctx(exc_ctx_sp);
PythonObject exc_ctx_arg(PyRefType::Owned, SBTypeToSWIGWrapper(sb_exc_ctx));
lldb::SBStream sb_stream;
PythonObject sb_stream_arg(PyRefType::Owned,
SBTypeToSWIGWrapper(sb_stream));
result = pfunc(exc_ctx_arg, sb_stream_arg);

if (PyErr_Occurred())
{
stream->PutCString("Python error occurred handling stop-hook.");
PyErr_Print();
PyErr_Clear();
return true;
}

// Now add the result to the output stream. SBStream only
// makes an internally help StreamString which I can't interpose, so I
// have to copy it over here.
stream->PutCString(sb_stream.GetData());

if (result.get() == Py_False)
return false;
else
return true;
}

// wrapper that calls an optional instance member of an object taking no arguments
static PyObject*
LLDBSwigPython_CallOptionalMember
Expand Down
46 changes: 46 additions & 0 deletions lldb/docs/use/python-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -819,3 +819,49 @@ When the program is stopped at the beginning of the 'read' function in libc, we
frame #0: 0x00007fff06013ca0 libsystem_kernel.dylib`read
(lldb) frame variable
(int) fd = 3

Writing Target Stop-Hooks in Python:
------------------------------------

Stop hooks fire whenever the process stops just before control is returned to the
user. Stop hooks can either be a set of lldb command-line commands, or can
be implemented by a suitably defined Python class. The Python based stop-hooks
can also be passed as set of -key -value pairs when they are added, and those
will get packaged up into a SBStructuredData Dictionary and passed to the
constructor of the Python object managing the stop hook. This allows for
parametrization of the stop hooks.

To add a Python-based stop hook, first define a class with the following methods:

+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
| Name | Arguments | Description |
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
| **__init__** | **target: lldb.SBTarget** | This is the constructor for the new stop-hook. |
| | **extra_args: lldb.SBStructuredData** | |
| | | |
| | | **target** is the SBTarget to which the stop hook is added. |
| | | |
| | | **extra_args** is an SBStructuredData object that the user can pass in when creating instances of this |
| | | breakpoint. It is not required, but allows for reuse of stop-hook classes. |
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+
| **handle_stop** | **exe_ctx: lldb.SBExecutionContext** | This is the called when the target stops. |
| | **stream: lldb.SBStream** | |
| | | **exe_ctx** argument will be filled with the current stop point for which the stop hook is |
| | | being evaluated. |
| | | |
| | | **stream** an lldb.SBStream, anything written to this stream will be written to the debugger console. |
| | | |
| | | The return value is a "Should Stop" vote from this thread. If the method returns either True or no return |
| | | this thread votes to stop. If it returns False, then the thread votes to continue after all the stop-hooks |
| | | are evaluated. |
| | | Note, the --auto-continue flag to 'target stop-hook add' overrides a True return value from the method. |
+--------------------+---------------------------------------+------------------------------------------------------------------------------------------------------------------+

To use this class in lldb, run the command:

::

(lldb) command script import MyModule.py
(lldb) target stop-hook add -P MyModule.MyStopHook -k first -v 1 -k second -v 2

where MyModule.py is the file containing the class definition MyStopHook.
17 changes: 17 additions & 0 deletions lldb/include/lldb/Interpreter/ScriptInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,23 @@ class ScriptInterpreter : public PluginInterface {
return lldb::eSearchDepthModule;
}

virtual StructuredData::GenericSP
CreateScriptedStopHook(lldb::TargetSP target_sp, const char *class_name,
StructuredDataImpl *args_data, Status &error) {
error.SetErrorString("Creating scripted stop-hooks with the current "
"script interpreter is not supported.");
return StructuredData::GenericSP();
}

// This dispatches to the handle_stop method of the stop-hook class. It
// returns a "should_stop" bool.
virtual bool
ScriptedStopHookHandleStop(StructuredData::GenericSP implementor_sp,
ExecutionContext &exc_ctx,
lldb::StreamSP stream_sp) {
return true;
}

virtual StructuredData::ObjectSP
LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
return StructuredData::ObjectSP();
Expand Down
2 changes: 1 addition & 1 deletion lldb/include/lldb/Symbol/SymbolContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class SymbolContextSpecifier {

void Clear();

bool SymbolContextMatches(SymbolContext &sc);
bool SymbolContextMatches(const SymbolContext &sc);

bool AddressMatches(lldb::addr_t addr);

Expand Down
96 changes: 82 additions & 14 deletions lldb/include/lldb/Target/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "lldb/Target/ExecutionContextScope.h"
#include "lldb/Target/PathMappingList.h"
#include "lldb/Target/SectionLoadHistory.h"
#include "lldb/Target/ThreadSpec.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/LLDBAssert.h"
Expand Down Expand Up @@ -508,6 +509,8 @@ class Target : public std::enable_shared_from_this<Target>,

static void SetDefaultArchitecture(const ArchSpec &arch);

bool IsDummyTarget() const { return m_is_dummy_target; }

/// Find a binary on the system and return its Module,
/// or return an existing Module that is already in the Target.
///
Expand Down Expand Up @@ -1139,23 +1142,32 @@ class Target : public std::enable_shared_from_this<Target>,
class StopHook : public UserID {
public:
StopHook(const StopHook &rhs);
virtual ~StopHook() = default;

~StopHook();

StringList *GetCommandPointer() { return &m_commands; }

const StringList &GetCommands() { return m_commands; }
enum class StopHookKind : uint32_t { CommandBased = 0, ScriptBased };
enum class StopHookResult : uint32_t {
KeepStopped = 0,
RequestContinue,
AlreadyContinued
};

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

void SetCommands(StringList &in_commands) { m_commands = in_commands; }

// Set the specifier. The stop hook will own the specifier, and is
// responsible for deleting it when we're done.
void SetSpecifier(SymbolContextSpecifier *specifier);

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

bool ExecutionContextPasses(const ExecutionContext &exe_ctx);

// Called on stop, this gets passed the ExecutionContext for each "stop
// with a reason" thread. It should add to the stream whatever text it
// wants to show the user, and return False to indicate it wants the target
// not to stop.
virtual StopHookResult HandleStop(ExecutionContext &exe_ctx,
lldb::StreamSP output) = 0;

// Set the Thread Specifier. The stop hook will own the thread specifier,
// and is responsible for deleting it when we're done.
void SetThreadSpecifier(ThreadSpec *specifier);
Expand All @@ -1173,28 +1185,84 @@ class Target : public std::enable_shared_from_this<Target>,
bool GetAutoContinue() const { return m_auto_continue; }

void GetDescription(Stream *s, lldb::DescriptionLevel level) const;
virtual void GetSubclassDescription(Stream *s,
lldb::DescriptionLevel level) const = 0;

private:
protected:
lldb::TargetSP m_target_sp;
StringList m_commands;
lldb::SymbolContextSpecifierSP m_specifier_sp;
std::unique_ptr<ThreadSpec> m_thread_spec_up;
bool m_active = true;
bool m_auto_continue = false;

StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
};

class StopHookCommandLine : public StopHook {
public:
virtual ~StopHookCommandLine() = default;

StringList &GetCommands() { return m_commands; }
void SetActionFromString(const std::string &strings);
void SetActionFromStrings(const std::vector<std::string> &strings);

StopHookResult HandleStop(ExecutionContext &exc_ctx,
lldb::StreamSP output_sp) override;
void GetSubclassDescription(Stream *s,
lldb::DescriptionLevel level) const override;

private:
StringList m_commands;
// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
// and fill it with commands, and SetSpecifier to set the specifier shared
// pointer (can be null, that will match anything.)
StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid);
StopHookCommandLine(lldb::TargetSP target_sp, lldb::user_id_t uid)
: StopHook(target_sp, uid) {}
friend class Target;
};

class StopHookScripted : public StopHook {
public:
virtual ~StopHookScripted() = default;
StopHookResult HandleStop(ExecutionContext &exc_ctx,
lldb::StreamSP output) override;

Status SetScriptCallback(std::string class_name,
StructuredData::ObjectSP extra_args_sp);

void GetSubclassDescription(Stream *s,
lldb::DescriptionLevel level) const override;

private:
std::string m_class_name;
/// This holds the dictionary of keys & values that can be used to
/// parametrize any given callback's behavior.
StructuredDataImpl *m_extra_args; // We own this structured data,
// but the SD itself manages the UP.
/// This holds the python callback object.
StructuredData::GenericSP m_implementation_sp;

/// Use CreateStopHook to make a new empty stop hook. The GetCommandPointer
/// and fill it with commands, and SetSpecifier to set the specifier shared
/// pointer (can be null, that will match anything.)
StopHookScripted(lldb::TargetSP target_sp, lldb::user_id_t uid)
: StopHook(target_sp, uid) {}
friend class Target;
};

typedef std::shared_ptr<StopHook> StopHookSP;

// Add an empty stop hook to the Target's stop hook list, and returns a
// shared pointer to it in new_hook. Returns the id of the new hook.
StopHookSP CreateStopHook();
/// Add an empty stop hook to the Target's stop hook list, and returns a
/// shared pointer to it in new_hook. Returns the id of the new hook.
StopHookSP CreateStopHook(StopHook::StopHookKind kind);

/// If you tried to create a stop hook, and that failed, call this to
/// remove the stop hook, as it will also reset the stop hook counter.
void UndoCreateStopHook(lldb::user_id_t uid);

void RunStopHooks();
// Runs the stop hooks that have been registered for this target.
// Returns true if the stop hooks cause the target to resume.
bool RunStopHooks();

size_t GetStopHookSize();

Expand Down
Loading