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