[Lldb-commits] [lldb] 1b1d981 - Revert "Revert "Add the ability to write target stop-hooks using the ScriptInterpreter.""

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 30 11:45:19 PDT 2020


I also used to get e-mails when a test failed and I was on the changes list.  But I haven’t gotten any failure e-mails.  Does that only happen for some of the bots (or has that stopped working) or should I look to my filters?

Jim


> On Sep 30, 2020, at 10:40 AM, Jim Ingham via lldb-commits <lldb-commits at lists.llvm.org> wrote:
> 
> It looks like all the failing builds are incremental builds.  Can you kick off a clean build and see if that also gets a failure?  I’m worried that all the SWIG bits aren’t getting rebuilt.  The code that is failing here is not at all time dependent, or particularly platform specific, so it would be odd for it to fail on ubuntu but not anywhere else.
> 
> Jim
> 
> 
>> On Sep 30, 2020, at 5:50 AM, Pavel Labath <pavel at labath.sk> wrote:
>> 
>> 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
>>> 
>> 
> 
> _______________________________________________
> 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