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@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 <pa...@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@lists.llvm.org
>>> https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
>>> 
>> 
> 
> _______________________________________________
> lldb-commits mailing list
> lldb-commits@lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to