[Lldb-commits] [lldb] 1b1d981 - Revert "Revert "Add the ability to write target stop-hooks using the ScriptInterpreter.""
Pavel Labath via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 30 05:50:28 PDT 2020
It looks like the new test (TestStopHookScripted.py) is flaky:
http://lab.llvm.org:8011/builders/lldb-x86_64-debian/builds/18360/steps/test/logs/stdio
http://lab.llvm.org:8011/builders/lldb-aarch64-ubuntu/builds/9484/steps/test/logs/stdio
http://lab.llvm.org:8011/builders/lldb-aarch64-ubuntu/builds/9504/steps/test/logs/stdio
http://lab.llvm.org:8011/builders/lldb-aarch64-ubuntu/builds/9505/steps/test/logs/stdio
On 29/09/2020 21:01, Jim Ingham via lldb-commits wrote:
>
> Author: Jim Ingham
> Date: 2020-09-29T12:01:14-07:00
> New Revision: 1b1d9815987a753f2f3524cfad050b85972dae5b
>
> URL: https://github.com/llvm/llvm-project/commit/1b1d9815987a753f2f3524cfad050b85972dae5b
> DIFF: https://github.com/llvm/llvm-project/commit/1b1d9815987a753f2f3524cfad050b85972dae5b.diff
>
> LOG: Revert "Revert "Add the ability to write target stop-hooks using the ScriptInterpreter.""
>
> This reverts commit f775fe59640a2e837ad059a8f40e26989d4f9831.
>
> 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.
>
> Added:
> lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
> lldb/test/API/commands/target/stop-hooks/stop_hook.py
> lldb/test/Shell/Commands/Inputs/stop_hook.py
> lldb/test/Shell/Commands/command-stop-hook-output.test
>
> Modified:
> lldb/bindings/python/python-swigsafecast.swig
> lldb/bindings/python/python-wrapper.swig
> lldb/docs/use/python-reference.rst
> lldb/include/lldb/Interpreter/ScriptInterpreter.h
> lldb/include/lldb/Symbol/SymbolContext.h
> lldb/include/lldb/Target/Target.h
> lldb/source/Commands/CommandObjectTarget.cpp
> lldb/source/Commands/Options.td
> lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
> lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
> lldb/source/Symbol/SymbolContext.cpp
> lldb/source/Target/Target.cpp
> lldb/test/API/commands/target/stop-hooks/TestStopHooks.py
> lldb/test/API/commands/target/stop-hooks/main.c
> lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
>
> Removed:
>
>
>
> ################################################################################
> diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig
> index d5cafbfa67cb..091fc29b1057 100644
> --- a/lldb/bindings/python/python-swigsafecast.swig
> +++ b/lldb/bindings/python/python-swigsafecast.swig
> @@ -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);
> +}
>
> diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
> index 516590ed5771..c00deba6073b 100644
> --- a/lldb/bindings/python/python-wrapper.swig
> +++ b/lldb/bindings/python/python-wrapper.swig
> @@ -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
>
> diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/python-reference.rst
> index 8c76ef1a0830..60474c94f185 100644
> --- a/lldb/docs/use/python-reference.rst
> +++ b/lldb/docs/use/python-reference.rst
> @@ -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.
>
> diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
> index 491923e6a6c4..c38786fd50d4 100644
> --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
> +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
> @@ -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();
>
> diff --git a/lldb/include/lldb/Symbol/SymbolContext.h b/lldb/include/lldb/Symbol/SymbolContext.h
> index cc49ce51c713..0f99364596c2 100644
> --- a/lldb/include/lldb/Symbol/SymbolContext.h
> +++ b/lldb/include/lldb/Symbol/SymbolContext.h
> @@ -340,7 +340,7 @@ class SymbolContextSpecifier {
>
> void Clear();
>
> - bool SymbolContextMatches(SymbolContext &sc);
> + bool SymbolContextMatches(const SymbolContext &sc);
>
> bool AddressMatches(lldb::addr_t addr);
>
>
> diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
> index 92904682ffb6..94c6ebeac10d 100644
> --- a/lldb/include/lldb/Target/Target.h
> +++ b/lldb/include/lldb/Target/Target.h
> @@ -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"
> @@ -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.
> ///
> @@ -1139,23 +1142,27 @@ 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 };
>
> 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 bool 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);
> @@ -1173,26 +1180,79 @@ 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);
> +
> + bool 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;
> + bool 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();
>
>
> diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
> index 431c2f3a19f0..98285289e3a9 100644
> --- a/lldb/source/Commands/CommandObjectTarget.cpp
> +++ b/lldb/source/Commands/CommandObjectTarget.cpp
> @@ -24,6 +24,7 @@
> #include "lldb/Interpreter/OptionGroupFile.h"
> #include "lldb/Interpreter/OptionGroupFormat.h"
> #include "lldb/Interpreter/OptionGroupPlatform.h"
> +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h"
> #include "lldb/Interpreter/OptionGroupString.h"
> #include "lldb/Interpreter/OptionGroupUInt64.h"
> #include "lldb/Interpreter/OptionGroupUUID.h"
> @@ -4442,10 +4443,10 @@ class CommandObjectTargetSymbols : public CommandObjectMultiword {
> class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
> public IOHandlerDelegateMultiline {
> public:
> - class CommandOptions : public Options {
> + class CommandOptions : public OptionGroup {
> public:
> CommandOptions()
> - : Options(), m_line_start(0), m_line_end(UINT_MAX),
> + : OptionGroup(), m_line_start(0), m_line_end(UINT_MAX),
> m_func_name_type_mask(eFunctionNameTypeAuto),
> m_sym_ctx_specified(false), m_thread_specified(false),
> m_use_one_liner(false), m_one_liner() {}
> @@ -4459,7 +4460,8 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
> Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
> ExecutionContext *execution_context) override {
> Status error;
> - const int short_option = m_getopt_table[option_idx].val;
> + const int short_option =
> + g_target_stop_hook_add_options[option_idx].short_option;
>
> switch (short_option) {
> case 'c':
> @@ -4589,20 +4591,75 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
> // Instance variables to hold the values for one_liner options.
> bool m_use_one_liner;
> std::vector<std::string> m_one_liner;
> +
> bool m_auto_continue;
> };
>
> CommandObjectTargetStopHookAdd(CommandInterpreter &interpreter)
> : CommandObjectParsed(interpreter, "target stop-hook add",
> - "Add a hook to be executed when the target stops.",
> + "Add a hook to be executed when the target stops."
> + "The hook can either be a list of commands or an "
> + "appropriately defined Python class. You can also "
> + "add filters so the hook only runs a certain stop "
> + "points.",
> "target stop-hook add"),
> IOHandlerDelegateMultiline("DONE",
> IOHandlerDelegate::Completion::LLDBCommand),
> - m_options() {}
> + m_options(), m_python_class_options("scripted stop-hook", true, 'P') {
> + SetHelpLong(
> + R"(
> +Command Based stop-hooks:
> +-------------------------
> + Stop hooks can run a list of lldb commands by providing one or more
> + --one-line-command options. The commands will get run in the order they are
> + added. Or you can provide no commands, in which case you will enter a
> + command editor where you can enter the commands to be run.
> +
> +Python Based Stop Hooks:
> +------------------------
> + Stop hooks can be implemented with a suitably defined Python class, whose name
> + is passed in the --python-class option.
> +
> + When the stop hook is added, the class is initialized by calling:
> +
> + def __init__(self, target, extra_args, dict):
> +
> + target: The target that the stop hook is being added to.
> + extra_args: An SBStructuredData Dictionary filled with the -key -value
> + option pairs passed to the command.
> + dict: An implementation detail provided by lldb.
> +
> + Then when the stop-hook triggers, lldb will run the 'handle_stop' method.
> + The method has the signature:
> +
> + def handle_stop(self, exe_ctx, stream):
> +
> + exe_ctx: An SBExecutionContext for the thread that has stopped.
> + stream: An SBStream, anything written to this stream will be printed in the
> + the stop message when the process stops.
> +
> + Return Value: The method returns "should_stop". If should_stop is false
> + from all the stop hook executions on threads that stopped
> + with a reason, then the process will continue. Note that this
> + will happen only after all the stop hooks are run.
> +
> +Filter Options:
> +---------------
> + Stop hooks can be set to always run, or to only run when the stopped thread
> + matches the filter options passed on the command line. The available filter
> + options include a shared library or a thread or queue specification,
> + a line range in a source file, a function name or a class name.
> + )");
> + m_all_options.Append(&m_python_class_options,
> + LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
> + LLDB_OPT_SET_FROM_TO(4, 6));
> + m_all_options.Append(&m_options);
> + m_all_options.Finalize();
> + }
>
> ~CommandObjectTargetStopHookAdd() override = default;
>
> - Options *GetOptions() override { return &m_options; }
> + Options *GetOptions() override { return &m_all_options; }
>
> protected:
> void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
> @@ -4626,10 +4683,15 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
> error_sp->Flush();
> }
> Target *target = GetDebugger().GetSelectedTarget().get();
> - if (target)
> - target->RemoveStopHookByID(m_stop_hook_sp->GetID());
> + if (target) {
> + target->UndoCreateStopHook(m_stop_hook_sp->GetID());
> + }
> } else {
> - m_stop_hook_sp->GetCommandPointer()->SplitIntoLines(line);
> + // The IOHandler editor is only for command lines stop hooks:
> + Target::StopHookCommandLine *hook_ptr =
> + static_cast<Target::StopHookCommandLine *>(m_stop_hook_sp.get());
> +
> + hook_ptr->SetActionFromString(line);
> StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
> if (output_sp) {
> output_sp->Printf("Stop hook #%" PRIu64 " added.\n",
> @@ -4646,7 +4708,10 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
> m_stop_hook_sp.reset();
>
> Target &target = GetSelectedOrDummyTarget();
> - Target::StopHookSP new_hook_sp = target.CreateStopHook();
> + Target::StopHookSP new_hook_sp =
> + target.CreateStopHook(m_python_class_options.GetName().empty() ?
> + Target::StopHook::StopHookKind::CommandBased
> + : Target::StopHook::StopHookKind::ScriptBased);
>
> // First step, make the specifier.
> std::unique_ptr<SymbolContextSpecifier> specifier_up;
> @@ -4715,11 +4780,30 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
>
> new_hook_sp->SetAutoContinue(m_options.m_auto_continue);
> if (m_options.m_use_one_liner) {
> - // Use one-liners.
> - for (auto cmd : m_options.m_one_liner)
> - new_hook_sp->GetCommandPointer()->AppendString(cmd.c_str());
> + // This is a command line stop hook:
> + Target::StopHookCommandLine *hook_ptr =
> + static_cast<Target::StopHookCommandLine *>(new_hook_sp.get());
> + hook_ptr->SetActionFromStrings(m_options.m_one_liner);
> result.AppendMessageWithFormat("Stop hook #%" PRIu64 " added.\n",
> new_hook_sp->GetID());
> + } else if (!m_python_class_options.GetName().empty()) {
> + // This is a scripted stop hook:
> + Target::StopHookScripted *hook_ptr =
> + static_cast<Target::StopHookScripted *>(new_hook_sp.get());
> + Status error = hook_ptr->SetScriptCallback(
> + m_python_class_options.GetName(),
> + m_python_class_options.GetStructuredData());
> + if (error.Success())
> + result.AppendMessageWithFormat("Stop hook #%" PRIu64 " added.\n",
> + new_hook_sp->GetID());
> + else {
> + // FIXME: Set the stop hook ID counter back.
> + result.AppendErrorWithFormat("Couldn't add stop hook: %s",
> + error.AsCString());
> + result.SetStatus(eReturnStatusFailed);
> + target.UndoCreateStopHook(new_hook_sp->GetID());
> + return false;
> + }
> } else {
> m_stop_hook_sp = new_hook_sp;
> m_interpreter.GetLLDBCommandsFromIOHandler("> ", // Prompt
> @@ -4732,6 +4816,9 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed,
>
> private:
> CommandOptions m_options;
> + OptionGroupPythonClassWithDict m_python_class_options;
> + OptionGroupOptions m_all_options;
> +
> Target::StopHookSP m_stop_hook_sp;
> };
>
>
> diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
> index 8c83fd20a366..ad2f5fdae8e7 100644
> --- a/lldb/source/Commands/Options.td
> +++ b/lldb/source/Commands/Options.td
> @@ -879,7 +879,7 @@ let Command = "target modules lookup" in {
> }
>
> let Command = "target stop hook add" in {
> - def target_stop_hook_add_one_liner : Option<"one-liner", "o">,
> + def target_stop_hook_add_one_liner : Option<"one-liner", "o">, GroupRange<1,3>,
> Arg<"OneLiner">, Desc<"Add a command for the stop hook. Can be specified "
> "more than once, and commands will be run in the order they appear.">;
> def target_stop_hook_add_shlib : Option<"shlib", "s">, Arg<"ShlibName">,
> @@ -897,19 +897,19 @@ let Command = "target stop hook add" in {
> def target_stop_hook_add_queue_name : Option<"queue-name", "q">,
> Arg<"QueueName">, Desc<"The stop hook is run only for threads in the queue "
> "whose name is given by this argument.">;
> - def target_stop_hook_add_file : Option<"file", "f">, Group<1>,
> + def target_stop_hook_add_file : Option<"file", "f">, Groups<[1,4]>,
> Arg<"Filename">, Desc<"Specify the source file within which the stop-hook "
> "is to be run.">, Completion<"SourceFile">;
> - def target_stop_hook_add_start_line : Option<"start-line", "l">, Group<1>,
> + def target_stop_hook_add_start_line : Option<"start-line", "l">, Groups<[1,4]>,
> Arg<"LineNum">, Desc<"Set the start of the line range for which the "
> "stop-hook is to be run.">;
> - def target_stop_hook_add_end_line : Option<"end-line", "e">, Group<1>,
> + def target_stop_hook_add_end_line : Option<"end-line", "e">, Groups<[1,4]>,
> Arg<"LineNum">, Desc<"Set the end of the line range for which the stop-hook"
> " is to be run.">;
> - def target_stop_hook_add_classname : Option<"classname", "c">, Group<2>,
> + def target_stop_hook_add_classname : Option<"classname", "c">, Groups<[2,5]>,
> Arg<"ClassName">,
> Desc<"Specify the class within which the stop-hook is to be run.">;
> - def target_stop_hook_add_name : Option<"name", "n">, Group<3>,
> + def target_stop_hook_add_name : Option<"name", "n">, Groups<[3,6]>,
> Arg<"FunctionName">, Desc<"Set the function name within which the stop hook"
> " will be run.">, Completion<"Symbol">;
> def target_stop_hook_add_auto_continue : Option<"auto-continue", "G">,
>
> diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
> index 9f56b4fa60a5..f67572c1f029 100644
> --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
> +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
> @@ -127,6 +127,16 @@ extern "C" unsigned int
> LLDBSwigPythonCallBreakpointResolver(void *implementor, const char *method_name,
> lldb_private::SymbolContext *sym_ctx);
>
> +extern "C" void *LLDBSwigPythonCreateScriptedStopHook(
> + TargetSP target_sp, const char *python_class_name,
> + const char *session_dictionary_name, lldb_private::StructuredDataImpl *args,
> + lldb_private::Status &error);
> +
> +extern "C" bool
> +LLDBSwigPythonStopHookCallHandleStop(void *implementor,
> + lldb::ExecutionContextRefSP exc_ctx,
> + lldb::StreamSP stream);
> +
> extern "C" size_t LLDBSwigPython_CalculateNumChildren(void *implementor,
> uint32_t max);
>
> @@ -1979,6 +1989,60 @@ ScriptInterpreterPythonImpl::ScriptedBreakpointResolverSearchDepth(
> return lldb::eSearchDepthModule;
> }
>
> +StructuredData::GenericSP ScriptInterpreterPythonImpl::CreateScriptedStopHook(
> + TargetSP target_sp, const char *class_name, StructuredDataImpl *args_data,
> + Status &error) {
> +
> + if (!target_sp) {
> + error.SetErrorString("No target for scripted stop-hook.");
> + return StructuredData::GenericSP();
> + }
> +
> + if (class_name == nullptr || class_name[0] == '\0') {
> + error.SetErrorString("No class name for scripted stop-hook.");
> + return StructuredData::GenericSP();
> + }
> +
> + ScriptInterpreter *script_interpreter = m_debugger.GetScriptInterpreter();
> + ScriptInterpreterPythonImpl *python_interpreter =
> + static_cast<ScriptInterpreterPythonImpl *>(script_interpreter);
> +
> + if (!script_interpreter) {
> + error.SetErrorString("No script interpreter for scripted stop-hook.");
> + return StructuredData::GenericSP();
> + }
> +
> + void *ret_val;
> +
> + {
> + Locker py_lock(this,
> + Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
> +
> + ret_val = LLDBSwigPythonCreateScriptedStopHook(
> + target_sp, class_name, python_interpreter->m_dictionary_name.c_str(),
> + args_data, error);
> + }
> +
> + return StructuredData::GenericSP(new StructuredPythonObject(ret_val));
> +}
> +
> +bool ScriptInterpreterPythonImpl::ScriptedStopHookHandleStop(
> + StructuredData::GenericSP implementor_sp, ExecutionContext &exc_ctx,
> + lldb::StreamSP stream_sp) {
> + assert(implementor_sp &&
> + "can't call a stop hook with an invalid implementor");
> + assert(stream_sp && "can't call a stop hook with an invalid stream");
> +
> + Locker py_lock(this,
> + Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
> +
> + lldb::ExecutionContextRefSP exc_ctx_ref_sp(new ExecutionContextRef(exc_ctx));
> +
> + bool ret_val = LLDBSwigPythonStopHookCallHandleStop(
> + implementor_sp->GetValue(), exc_ctx_ref_sp, stream_sp);
> + return ret_val;
> +}
> +
> StructuredData::ObjectSP
> ScriptInterpreterPythonImpl::LoadPluginModule(const FileSpec &file_spec,
> lldb_private::Status &error) {
>
> diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
> index 22b2c8152eac..f89c3d461f7f 100644
> --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
> +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
> @@ -105,6 +105,14 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
> lldb::SearchDepth ScriptedBreakpointResolverSearchDepth(
> StructuredData::GenericSP implementor_sp) override;
>
> + StructuredData::GenericSP
> + CreateScriptedStopHook(lldb::TargetSP target_sp, const char *class_name,
> + StructuredDataImpl *args_data, Status &error) override;
> +
> + bool ScriptedStopHookHandleStop(StructuredData::GenericSP implementor_sp,
> + ExecutionContext &exc_ctx,
> + lldb::StreamSP stream_sp) override;
> +
> StructuredData::GenericSP
> CreateFrameRecognizer(const char *class_name) override;
>
>
> diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp
> index 51f56704cca6..f20dc61996e0 100644
> --- a/lldb/source/Symbol/SymbolContext.cpp
> +++ b/lldb/source/Symbol/SymbolContext.cpp
> @@ -1010,11 +1010,15 @@ void SymbolContextSpecifier::Clear() {
> m_type = eNothingSpecified;
> }
>
> -bool SymbolContextSpecifier::SymbolContextMatches(SymbolContext &sc) {
> +bool SymbolContextSpecifier::SymbolContextMatches(const SymbolContext &sc) {
> if (m_type == eNothingSpecified)
> return true;
>
> - if (m_target_sp.get() != sc.target_sp.get())
> + // Only compare targets if this specifier has one and it's not the Dummy
> + // target. Otherwise if a specifier gets made in the dummy target and
> + // copied over we'll artificially fail the comparision.
> + if (m_target_sp && !m_target_sp->IsDummyTarget() &&
> + m_target_sp != sc.target_sp)
> return false;
>
> if (m_type & eModuleSpecified) {
>
> diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
> index a529df998ba7..a5250ddcef74 100644
> --- a/lldb/source/Target/Target.cpp
> +++ b/lldb/source/Target/Target.cpp
> @@ -2484,13 +2484,28 @@ ClangModulesDeclVendor *Target::GetClangModulesDeclVendor() {
> return m_clang_modules_decl_vendor_up.get();
> }
>
> -Target::StopHookSP Target::CreateStopHook() {
> +Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) {
> lldb::user_id_t new_uid = ++m_stop_hook_next_id;
> - Target::StopHookSP stop_hook_sp(new StopHook(shared_from_this(), new_uid));
> + Target::StopHookSP stop_hook_sp;
> + switch (kind) {
> + case StopHook::StopHookKind::CommandBased:
> + stop_hook_sp.reset(new StopHookCommandLine(shared_from_this(), new_uid));
> + break;
> + case StopHook::StopHookKind::ScriptBased:
> + stop_hook_sp.reset(new StopHookScripted(shared_from_this(), new_uid));
> + break;
> + }
> m_stop_hooks[new_uid] = stop_hook_sp;
> return stop_hook_sp;
> }
>
> +void Target::UndoCreateStopHook(lldb::user_id_t user_id) {
> + if (!RemoveStopHookByID(user_id))
> + return;
> + if (user_id == m_stop_hook_next_id)
> + m_stop_hook_next_id--;
> +}
> +
> bool Target::RemoveStopHookByID(lldb::user_id_t user_id) {
> size_t num_removed = m_stop_hooks.erase(user_id);
> return (num_removed != 0);
> @@ -2546,25 +2561,18 @@ void Target::RunStopHooks() {
> if (m_stop_hooks.empty())
> return;
>
> - StopHookCollection::iterator pos, end = m_stop_hooks.end();
> -
> // If there aren't any active stop hooks, don't bother either.
> - // Also see if any of the active hooks want to auto-continue.
> bool any_active_hooks = false;
> - bool auto_continue = false;
> for (auto hook : m_stop_hooks) {
> if (hook.second->IsActive()) {
> any_active_hooks = true;
> - auto_continue |= hook.second->GetAutoContinue();
> + break;
> }
> }
> if (!any_active_hooks)
> return;
>
> - CommandReturnObject result(m_debugger.GetUseColor());
> -
> std::vector<ExecutionContext> exc_ctx_with_reasons;
> - std::vector<SymbolContext> sym_ctx_with_reasons;
>
> ThreadList &cur_threadlist = m_process_sp->GetThreadList();
> size_t num_threads = cur_threadlist.GetSize();
> @@ -2572,10 +2580,8 @@ void Target::RunStopHooks() {
> lldb::ThreadSP cur_thread_sp = cur_threadlist.GetThreadAtIndex(i);
> if (cur_thread_sp->ThreadStoppedForAReason()) {
> lldb::StackFrameSP cur_frame_sp = cur_thread_sp->GetStackFrameAtIndex(0);
> - exc_ctx_with_reasons.push_back(ExecutionContext(
> - m_process_sp.get(), cur_thread_sp.get(), cur_frame_sp.get()));
> - sym_ctx_with_reasons.push_back(
> - cur_frame_sp->GetSymbolContext(eSymbolContextEverything));
> + exc_ctx_with_reasons.emplace_back(m_process_sp.get(), cur_thread_sp.get(),
> + cur_frame_sp.get());
> }
> }
>
> @@ -2584,91 +2590,86 @@ void Target::RunStopHooks() {
> if (num_exe_ctx == 0)
> return;
>
> - result.SetImmediateOutputStream(m_debugger.GetAsyncOutputStream());
> - result.SetImmediateErrorStream(m_debugger.GetAsyncErrorStream());
> + StreamSP output_sp = m_debugger.GetAsyncOutputStream();
>
> - bool keep_going = true;
> + bool auto_continue = false;
> bool hooks_ran = false;
> bool print_hook_header = (m_stop_hooks.size() != 1);
> bool print_thread_header = (num_exe_ctx != 1);
> - bool did_restart = false;
> + bool should_stop = false;
> + bool somebody_restarted = false;
>
> - for (pos = m_stop_hooks.begin(); keep_going && pos != end; pos++) {
> - // result.Clear();
> - StopHookSP cur_hook_sp = (*pos).second;
> + for (auto stop_entry : m_stop_hooks) {
> + StopHookSP cur_hook_sp = stop_entry.second;
> if (!cur_hook_sp->IsActive())
> continue;
>
> bool any_thread_matched = false;
> - for (size_t i = 0; keep_going && i < num_exe_ctx; i++) {
> - if ((cur_hook_sp->GetSpecifier() == nullptr ||
> - cur_hook_sp->GetSpecifier()->SymbolContextMatches(
> - sym_ctx_with_reasons[i])) &&
> - (cur_hook_sp->GetThreadSpecifier() == nullptr ||
> - cur_hook_sp->GetThreadSpecifier()->ThreadPassesBasicTests(
> - exc_ctx_with_reasons[i].GetThreadRef()))) {
> - if (!hooks_ran) {
> - hooks_ran = true;
> - }
> - if (print_hook_header && !any_thread_matched) {
> - const char *cmd =
> - (cur_hook_sp->GetCommands().GetSize() == 1
> - ? cur_hook_sp->GetCommands().GetStringAtIndex(0)
> - : nullptr);
> - if (cmd)
> - result.AppendMessageWithFormat("\n- Hook %" PRIu64 " (%s)\n",
> - cur_hook_sp->GetID(), cmd);
> - else
> - result.AppendMessageWithFormat("\n- Hook %" PRIu64 "\n",
> - cur_hook_sp->GetID());
> - any_thread_matched = true;
> - }
> + for (auto exc_ctx : exc_ctx_with_reasons) {
> + // We detect somebody restarted in the stop-hook loop, and broke out of
> + // that loop back to here. So break out of here too.
> + if (somebody_restarted)
> + break;
>
> - if (print_thread_header)
> - result.AppendMessageWithFormat(
> - "-- Thread %d\n",
> - exc_ctx_with_reasons[i].GetThreadPtr()->GetIndexID());
> -
> - CommandInterpreterRunOptions options;
> - options.SetStopOnContinue(true);
> - options.SetStopOnError(true);
> - options.SetEchoCommands(false);
> - options.SetPrintResults(true);
> - options.SetPrintErrors(true);
> - options.SetAddToHistory(false);
> -
> - // Force Async:
> - bool old_async = GetDebugger().GetAsyncExecution();
> - GetDebugger().SetAsyncExecution(true);
> - GetDebugger().GetCommandInterpreter().HandleCommands(
> - cur_hook_sp->GetCommands(), &exc_ctx_with_reasons[i], options,
> - result);
> - GetDebugger().SetAsyncExecution(old_async);
> - // If the command started the target going again, we should bag out of
> - // running the stop hooks.
> - if ((result.GetStatus() == eReturnStatusSuccessContinuingNoResult) ||
> - (result.GetStatus() == eReturnStatusSuccessContinuingResult)) {
> - // But only complain if there were more stop hooks to do:
> - StopHookCollection::iterator tmp = pos;
> - if (++tmp != end)
> - result.AppendMessageWithFormat(
> - "\nAborting stop hooks, hook %" PRIu64
> - " set the program running.\n"
> - " Consider using '-G true' to make "
> - "stop hooks auto-continue.\n",
> - cur_hook_sp->GetID());
> - keep_going = false;
> - did_restart = true;
> - }
> + if (!cur_hook_sp->ExecutionContextPasses(exc_ctx))
> + continue;
> +
> + // We only consult the auto-continue for a stop hook if it matched the
> + // specifier.
> + auto_continue |= cur_hook_sp->GetAutoContinue();
> +
> + if (!hooks_ran)
> + hooks_ran = true;
> +
> + if (print_hook_header && !any_thread_matched) {
> + StreamString s;
> + cur_hook_sp->GetDescription(&s, eDescriptionLevelBrief);
> + if (s.GetSize() != 0)
> + output_sp->Printf("\n- Hook %" PRIu64 " (%s)\n", cur_hook_sp->GetID(),
> + s.GetData());
> + else
> + output_sp->Printf("\n- Hook %" PRIu64 "\n", cur_hook_sp->GetID());
> + any_thread_matched = true;
> + }
> +
> + if (print_thread_header)
> + output_sp->Printf("-- Thread %d\n",
> + exc_ctx.GetThreadPtr()->GetIndexID());
> +
> + bool this_should_stop = cur_hook_sp->HandleStop(exc_ctx, output_sp);
> + // If this hook is set to auto-continue that should override the
> + // HandleStop result...
> + if (cur_hook_sp->GetAutoContinue())
> + this_should_stop = false;
> +
> + // If anybody wanted to stop, we should all stop.
> + if (!should_stop)
> + should_stop = this_should_stop;
> +
> + // We don't have a good way to prohibit people from restarting the target
> + // willy nilly in a stop hook. So see if the private state is running
> + // here and bag out if it is.
> + // FIXME: when we are doing non-stop mode for realz we'll have to instead
> + // track each thread, and only bag out if a thread is set running.
> + if (m_process_sp->GetPrivateState() != eStateStopped) {
> + output_sp->Printf("\nAborting stop hooks, hook %" PRIu64
> + " set the program running.\n"
> + " Consider using '-G true' to make "
> + "stop hooks auto-continue.\n",
> + cur_hook_sp->GetID());
> + somebody_restarted = true;
> + break;
> }
> }
> }
> +
> + output_sp->Flush();
> +
> // Finally, if auto-continue was requested, do it now:
> - if (!did_restart && auto_continue)
> + // We only compute should_stop against the hook results if a hook got to run
> + // which is why we have to do this conjoint test.
> + if (!somebody_restarted && ((hooks_ran && !should_stop) || auto_continue))
> m_process_sp->PrivateResume();
> -
> - result.GetImmediateOutputStream()->Flush();
> - result.GetImmediateErrorStream()->Flush();
> }
>
> const TargetPropertiesSP &Target::GetGlobalProperties() {
> @@ -3128,20 +3129,17 @@ void Target::FinalizeFileActions(ProcessLaunchInfo &info) {
>
> // Target::StopHook
> Target::StopHook::StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid)
> - : UserID(uid), m_target_sp(target_sp), m_commands(), m_specifier_sp(),
> + : UserID(uid), m_target_sp(target_sp), m_specifier_sp(),
> m_thread_spec_up() {}
>
> Target::StopHook::StopHook(const StopHook &rhs)
> : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp),
> - m_commands(rhs.m_commands), m_specifier_sp(rhs.m_specifier_sp),
> - m_thread_spec_up(), m_active(rhs.m_active),
> - m_auto_continue(rhs.m_auto_continue) {
> + m_specifier_sp(rhs.m_specifier_sp), m_thread_spec_up(),
> + m_active(rhs.m_active), m_auto_continue(rhs.m_auto_continue) {
> if (rhs.m_thread_spec_up)
> m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up);
> }
>
> -Target::StopHook::~StopHook() = default;
> -
> void Target::StopHook::SetSpecifier(SymbolContextSpecifier *specifier) {
> m_specifier_sp.reset(specifier);
> }
> @@ -3150,8 +3148,31 @@ void Target::StopHook::SetThreadSpecifier(ThreadSpec *specifier) {
> m_thread_spec_up.reset(specifier);
> }
>
> +bool Target::StopHook::ExecutionContextPasses(const ExecutionContext &exc_ctx) {
> + SymbolContextSpecifier *specifier = GetSpecifier();
> + if (!specifier)
> + return true;
> +
> + bool will_run = true;
> + if (exc_ctx.GetFramePtr())
> + will_run = GetSpecifier()->SymbolContextMatches(
> + exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything));
> + if (will_run && GetThreadSpecifier() != nullptr)
> + will_run =
> + GetThreadSpecifier()->ThreadPassesBasicTests(exc_ctx.GetThreadRef());
> +
> + return will_run;
> +}
> +
> void Target::StopHook::GetDescription(Stream *s,
> lldb::DescriptionLevel level) const {
> +
> + // For brief descriptions, only print the subclass description:
> + if (level == eDescriptionLevelBrief) {
> + GetSubclassDescription(s, level);
> + return;
> + }
> +
> unsigned indent_level = s->GetIndentLevel();
>
> s->SetIndentLevel(indent_level + 2);
> @@ -3182,15 +3203,148 @@ void Target::StopHook::GetDescription(Stream *s,
> s->PutCString("\n");
> s->SetIndentLevel(indent_level + 2);
> }
> + GetSubclassDescription(s, level);
> +}
>
> +void Target::StopHookCommandLine::GetSubclassDescription(
> + Stream *s, lldb::DescriptionLevel level) const {
> + // The brief description just prints the first command.
> + if (level == eDescriptionLevelBrief) {
> + if (m_commands.GetSize() == 1)
> + s->PutCString(m_commands.GetStringAtIndex(0));
> + return;
> + }
> s->Indent("Commands: \n");
> - s->SetIndentLevel(indent_level + 4);
> + s->SetIndentLevel(s->GetIndentLevel() + 4);
> uint32_t num_commands = m_commands.GetSize();
> for (uint32_t i = 0; i < num_commands; i++) {
> s->Indent(m_commands.GetStringAtIndex(i));
> s->PutCString("\n");
> }
> - s->SetIndentLevel(indent_level);
> + s->SetIndentLevel(s->GetIndentLevel() - 4);
> +}
> +
> +// Target::StopHookCommandLine
> +void Target::StopHookCommandLine::SetActionFromString(const std::string &string) {
> + GetCommands().SplitIntoLines(string);
> +}
> +
> +void Target::StopHookCommandLine::SetActionFromStrings(
> + const std::vector<std::string> &strings) {
> + for (auto string : strings)
> + GetCommands().AppendString(string.c_str());
> +}
> +
> +bool Target::StopHookCommandLine::HandleStop(ExecutionContext &exc_ctx,
> + StreamSP output_sp) {
> + assert(exc_ctx.GetTargetPtr() && "Can't call PerformAction on a context "
> + "with no target");
> +
> + if (!m_commands.GetSize())
> + return true;
> +
> + CommandReturnObject result(false);
> + result.SetImmediateOutputStream(output_sp);
> + Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger();
> + CommandInterpreterRunOptions options;
> + options.SetStopOnContinue(true);
> + options.SetStopOnError(true);
> + options.SetEchoCommands(false);
> + options.SetPrintResults(true);
> + options.SetPrintErrors(true);
> + options.SetAddToHistory(false);
> +
> + // Force Async:
> + bool old_async = debugger.GetAsyncExecution();
> + debugger.SetAsyncExecution(true);
> + debugger.GetCommandInterpreter().HandleCommands(GetCommands(), &exc_ctx,
> + options, result);
> + debugger.SetAsyncExecution(old_async);
> +
> + return true;
> +}
> +
> +// Target::StopHookScripted
> +Status Target::StopHookScripted::SetScriptCallback(
> + std::string class_name, StructuredData::ObjectSP extra_args_sp) {
> + Status error;
> +
> + ScriptInterpreter *script_interp =
> + GetTarget()->GetDebugger().GetScriptInterpreter();
> + if (!script_interp) {
> + error.SetErrorString("No script interpreter installed.");
> + return error;
> + }
> +
> + m_class_name = class_name;
> +
> + m_extra_args = new StructuredDataImpl();
> +
> + if (extra_args_sp)
> + m_extra_args->SetObjectSP(extra_args_sp);
> +
> + m_implementation_sp = script_interp->CreateScriptedStopHook(
> + GetTarget(), m_class_name.c_str(), m_extra_args, error);
> +
> + return error;
> +}
> +
> +bool Target::StopHookScripted::HandleStop(ExecutionContext &exc_ctx,
> + StreamSP output_sp) {
> + assert(exc_ctx.GetTargetPtr() && "Can't call HandleStop on a context "
> + "with no target");
> +
> + ScriptInterpreter *script_interp =
> + GetTarget()->GetDebugger().GetScriptInterpreter();
> + if (!script_interp)
> + return true;
> +
> + bool should_stop = script_interp->ScriptedStopHookHandleStop(
> + m_implementation_sp, exc_ctx, output_sp);
> +
> + return should_stop;
> +}
> +
> +void Target::StopHookScripted::GetSubclassDescription(
> + Stream *s, lldb::DescriptionLevel level) const {
> + if (level == eDescriptionLevelBrief) {
> + s->PutCString(m_class_name);
> + return;
> + }
> + s->Indent("Class:");
> + s->Printf("%s\n", m_class_name.c_str());
> +
> + // Now print the extra args:
> + // FIXME: We should use StructuredData.GetDescription on the m_extra_args
> + // but that seems to rely on some printing plugin that doesn't exist.
> + if (!m_extra_args->IsValid())
> + return;
> + StructuredData::ObjectSP object_sp = m_extra_args->GetObjectSP();
> + if (!object_sp || !object_sp->IsValid())
> + return;
> +
> + StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary();
> + if (!as_dict || !as_dict->IsValid())
> + return;
> +
> + uint32_t num_keys = as_dict->GetSize();
> + if (num_keys == 0)
> + return;
> +
> + s->Indent("Args:\n");
> + s->SetIndentLevel(s->GetIndentLevel() + 4);
> +
> + auto print_one_element = [&s](ConstString key,
> + StructuredData::Object *object) {
> + s->Indent();
> + s->Printf("%s : %s\n", key.GetCString(),
> + object->GetStringValue().str().c_str());
> + return true;
> + };
> +
> + as_dict->ForEach(print_one_element);
> +
> + s->SetIndentLevel(s->GetIndentLevel() - 4);
> }
>
> static constexpr OptionEnumValueElement g_dynamic_value_types[] = {
>
> diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
> new file mode 100644
> index 000000000000..e650778fe8e3
> --- /dev/null
> +++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py
> @@ -0,0 +1,146 @@
> +"""
> +Test stop hook functionality
> +"""
> +
> +
> +
> +import lldb
> +import lldbsuite.test.lldbutil as lldbutil
> +from lldbsuite.test.lldbtest import *
> +
> +
> +class TestStopHooks(TestBase):
> +
> + mydir = TestBase.compute_mydir(__file__)
> +
> + # If your test case doesn't stress debug info, the
> + # set this to true. That way it won't be run once for
> + # each debug info format.
> + NO_DEBUG_INFO_TESTCASE = True
> +
> + def setUp(self):
> + TestBase.setUp(self)
> + self.build()
> + self.main_source_file = lldb.SBFileSpec("main.c")
> + full_path = os.path.join(self.getSourceDir(), "main.c")
> + self.main_start_line = line_number(full_path, "main()")
> +
> + def test_bad_handler(self):
> + """Test that we give a good error message when the handler is bad"""
> + self.script_setup()
> + result = lldb.SBCommandReturnObject()
> +
> + # First try the wrong number of args handler:
> + command = "target stop-hook add -P stop_hook.bad_handle_stop"
> + self.interp.HandleCommand(command, result)
> + self.assertFalse(result.Succeeded(), "Set the target stop hook")
> + self.assertIn("Wrong number of args", result.GetError(), "Got the wrong number of args error")
> +
> + # Next the no handler at all handler:
> + command = "target stop-hook add -P stop_hook.no_handle_stop"
> +
> + self.interp.HandleCommand(command, result)
> + self.assertFalse(result.Succeeded(), "Set the target stop hook")
> + self.assertIn('Class "stop_hook.no_handle_stop" is missing the required handle_stop callback', result.GetError(), "Got the right error")
> +
> + def test_stop_hooks_scripted(self):
> + """Test that a scripted stop hook works with no specifiers"""
> + self.stop_hooks_scripted(5)
> +
> + def test_stop_hooks_scripted_right_func(self):
> + """Test that a scripted stop hook fires when there is a function match"""
> + self.stop_hooks_scripted(5, "-n step_out_of_me")
> +
> + def test_stop_hooks_scripted_wrong_func(self):
> + """Test that a scripted stop hook doesn't fire when the function does not match"""
> + self.stop_hooks_scripted(0, "-n main")
> +
> + def test_stop_hooks_scripted_right_lines(self):
> + """Test that a scripted stop hook fires when there is a function match"""
> + self.stop_hooks_scripted(5, "-f main.c -l 1 -e %d"%(self.main_start_line))
> +
> + def test_stop_hooks_scripted_wrong_lines(self):
> + """Test that a scripted stop hook doesn't fire when the function does not match"""
> + self.stop_hooks_scripted(0, "-f main.c -l %d -e 100"%(self.main_start_line))
> +
> + def test_stop_hooks_scripted_auto_continue(self):
> + """Test that the --auto-continue flag works"""
> + self.do_test_auto_continue(False)
> +
> + def test_stop_hooks_scripted_return_false(self):
> + """Test that the returning False from a stop hook works"""
> + self.do_test_auto_continue(True)
> +
> + def do_test_auto_continue(self, return_true):
> + """Test that auto-continue works."""
> + # We set auto-continue to 1 but the stop hook only applies to step_out_of_me,
> + # so we should end up stopped in main, having run the expression only once.
> + self.script_setup()
> +
> + result = lldb.SBCommandReturnObject()
> +
> + if return_true:
> + command = "target stop-hook add -P stop_hook.stop_handler -k increment -v 5 -k return_false -v 1 -n step_out_of_me"
> + else:
> + command = "target stop-hook add -G 1 -P stop_hook.stop_handler -k increment -v 5 -n step_out_of_me"
> +
> + self.interp.HandleCommand(command, result)
> + self.assertTrue(result.Succeeded, "Set the target stop hook")
> +
> + # First run to main. If we go straight to the first stop hook hit,
> + # run_to_source_breakpoint will fail because we aren't at original breakpoint
> +
> + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
> + "Stop here first", self.main_source_file)
> +
> + # Now set the breakpoint on step_out_of_me, and make sure we run the
> + # expression, then continue back to main.
> + bkpt = target.BreakpointCreateBySourceRegex("Set a breakpoint here and step out", self.main_source_file)
> + self.assertTrue(bkpt.GetNumLocations() > 0, "Got breakpoints in step_out_of_me")
> + process.Continue()
> +
> + var = target.FindFirstGlobalVariable("g_var")
> + self.assertTrue(var.IsValid())
> + self.assertEqual(var.GetValueAsUnsigned(), 5, "Updated g_var")
> +
> + func_name = process.GetSelectedThread().frames[0].GetFunctionName()
> + self.assertEqual("main", func_name, "Didn't stop at the expected function.")
> +
> + def script_setup(self):
> + self.interp = self.dbg.GetCommandInterpreter()
> + result = lldb.SBCommandReturnObject()
> +
> + # Bring in our script file:
> + script_name = os.path.join(self.getSourceDir(), "stop_hook.py")
> + command = "command script import " + script_name
> + self.interp.HandleCommand(command, result)
> + self.assertTrue(result.Succeeded(), "com scr imp failed: %s"%(result.GetError()))
> +
> + # set a breakpoint at the end of main to catch our auto-continue tests.
> + # Do it in the dummy target so it will get copied to our target even when
> + # we don't have a chance to stop.
> + dummy_target = self.dbg.GetDummyTarget()
> + dummy_target.BreakpointCreateBySourceRegex("return result", self.main_source_file)
> +
> +
> + def stop_hooks_scripted(self, g_var_value, specifier = None):
> + self.script_setup()
> +
> + result = lldb.SBCommandReturnObject()
> +
> + command = "target stop-hook add -P stop_hook.stop_handler -k increment -v 5 "
> + if specifier:
> + command += specifier
> +
> + self.interp.HandleCommand(command, result)
> + self.assertTrue(result.Succeeded, "Set the target stop hook")
> + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
> + "Set a breakpoint here", self.main_source_file)
> + # At this point we've hit our stop hook so we should have run our expression,
> + # which increments g_var by the amount specified by the increment key's value.
> + while process.GetState() == lldb.eStateRunning:
> + continue
> +
> + var = target.FindFirstGlobalVariable("g_var")
> + self.assertTrue(var.IsValid())
> + self.assertEqual(var.GetValueAsUnsigned(), g_var_value, "Updated g_var")
>
> diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHooks.py b/lldb/test/API/commands/target/stop-hooks/TestStopHooks.py
> index 64686afe627d..43447a845156 100644
> --- a/lldb/test/API/commands/target/stop-hooks/TestStopHooks.py
> +++ b/lldb/test/API/commands/target/stop-hooks/TestStopHooks.py
> @@ -1,5 +1,5 @@
> """
> -Test that stop hooks trigger on "step-out"
> +Test stop hook functionality
> """
>
>
> @@ -18,10 +18,15 @@ class TestStopHooks(TestBase):
> # each debug info format.
> NO_DEBUG_INFO_TESTCASE = True
>
> - def test_stop_hooks_step_out(self):
> - """Test that stop hooks fire on step-out."""
> + def setUp(self):
> + TestBase.setUp(self)
> self.build()
> self.main_source_file = lldb.SBFileSpec("main.c")
> + full_path = os.path.join(self.getSourceDir(), "main.c")
> + self.main_start_line = line_number(full_path, "main()")
> +
> + def test_stop_hooks_step_out(self):
> + """Test that stop hooks fire on step-out."""
> self.step_out_test()
>
> def step_out_test(self):
> @@ -37,4 +42,3 @@ def step_out_test(self):
> self.assertTrue(var.IsValid())
> self.assertEqual(var.GetValueAsUnsigned(), 1, "Updated g_var")
>
> -
>
> diff --git a/lldb/test/API/commands/target/stop-hooks/main.c b/lldb/test/API/commands/target/stop-hooks/main.c
> index d08ad14776b5..16bfc0ce5db6 100644
> --- a/lldb/test/API/commands/target/stop-hooks/main.c
> +++ b/lldb/test/API/commands/target/stop-hooks/main.c
> @@ -10,5 +10,6 @@ int step_out_of_me()
> int
> main()
> {
> - return step_out_of_me();
> + int result = step_out_of_me(); // Stop here first
> + return result;
> }
>
> diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
> new file mode 100644
> index 000000000000..1abc2bdeeb31
> --- /dev/null
> +++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py
> @@ -0,0 +1,49 @@
> +import lldb
> +
> +class stop_handler:
> + def __init__(self, target, extra_args, dict):
> + self.extra_args = extra_args
> + self.target = target
> + self.counter = 0
> + ret_val = self.extra_args.GetValueForKey("return_false")
> + if ret_val:
> + self.ret_val = False
> + else:
> + self.ret_val = True
> +
> + def handle_stop(self, exe_ctx, stream):
> + self.counter += 1
> + stream.Print("I have stopped %d times.\n"%(self.counter))
> + increment = 1
> + value = self.extra_args.GetValueForKey("increment")
> + if value:
> + incr_as_str = value.GetStringValue(100)
> + increment = int(incr_as_str)
> + else:
> + stream.Print("Could not find increment in extra_args\n")
> + frame = exe_ctx.GetFrame()
> + expression = "g_var += %d"%(increment)
> + expr_result = frame.EvaluateExpression(expression)
> + if not expr_result.GetError().Success():
> + stream.Print("Error running expression: %s"%(expr_result.GetError().GetCString()))
> + value = exe_ctx.target.FindFirstGlobalVariable("g_var")
> + if not value.IsValid():
> + stream.Print("Didn't get a valid value for g_var.")
> + else:
> + int_val = value.GetValueAsUnsigned()
> + stream.Print("Returning value: %d from handle_stop.\n"%(self.ret_val))
> + return self.ret_val
> +
> +class bad_handle_stop:
> + def __init__(self, target, extra_args, dict):
> + print("I am okay")
> +
> + def handle_stop(self):
> + print("I am bad")
> +
> +class no_handle_stop:
> + def __init__(self, target, extra_args, dict):
> + print("I am okay")
> +
> +
> +
>
> diff --git a/lldb/test/Shell/Commands/Inputs/stop_hook.py b/lldb/test/Shell/Commands/Inputs/stop_hook.py
> new file mode 100644
> index 000000000000..e319ca9ec5bc
> --- /dev/null
> +++ b/lldb/test/Shell/Commands/Inputs/stop_hook.py
> @@ -0,0 +1,10 @@
> +import lldb
> +
> +class stop_handler:
> + def __init__(self, target, extra_args, dict):
> + self.extra_args = extra_args
> + self.target = target
> +
> + def handle_stop(self, exe_ctx, stream):
> + stream.Print("I did indeed run\n")
> + return True
>
> diff --git a/lldb/test/Shell/Commands/command-stop-hook-output.test b/lldb/test/Shell/Commands/command-stop-hook-output.test
> new file mode 100644
> index 000000000000..7890bb3ca5e7
> --- /dev/null
> +++ b/lldb/test/Shell/Commands/command-stop-hook-output.test
> @@ -0,0 +1,19 @@
> +# REQUIRES: python
> +# RUN: %clang_host -g %S/Inputs/main.c -o %t
> +# RUN: %lldb %t -O 'command script import %S/Inputs/stop_hook.py' -s %s -o exit | FileCheck %s
> +
> +b main
> +# CHECK-LABEL: b main
> +# CHECK: Breakpoint 1: where = {{.*}}`main
> +
> +target stop-hook add -P stop_hook.stop_handler
> +# CHECK-LABEL: target stop-hook add -P stop_hook.stop_handler
> +# CHECK: Stop hook #1 added.
> +
> +run
> +# CHECK-LABEL: run
> +# CHECK: I did indeed run
> +# CHECK: Process {{.*}} stopped
> +# CHECK: stop reason = breakpoint 1
> +# CHECK: frame #0: {{.*}}`main at main.c
> +
>
> diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
> index f661835d191b..58ddf0c40a26 100644
> --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
> +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
> @@ -254,3 +254,17 @@ LLDBSWIGPython_GetDynamicSetting(void *module, const char *setting,
> const lldb::TargetSP &target_sp) {
> return nullptr;
> }
> +
> +extern "C" void *LLDBSwigPythonCreateScriptedStopHook(
> + lldb::TargetSP target_sp, const char *python_class_name,
> + const char *session_dictionary_name,
> + lldb_private::StructuredDataImpl *args_impl, Status &error) {
> + return nullptr;
> +}
> +
> +extern "C" bool
> +LLDBSwigPythonStopHookCallHandleStop(void *implementor,
> + lldb::ExecutionContextRefSP exc_ctx_sp,
> + lldb::StreamSP stream) {
> + return false;
> +}
>
>
>
> _______________________________________________
> lldb-commits mailing list
> lldb-commits at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
>
More information about the lldb-commits
mailing list