From 8ecc9e713c55e192a4e72d8334a56e9bfd62ef60 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Mon, 28 Sep 2020 10:28:29 -0700 Subject: [PATCH 1/6] 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. (cherry picked from commit 1b1d9815987a753f2f3524cfad050b85972dae5b) --- lldb/bindings/python/python-swigsafecast.swig | 7 + lldb/bindings/python/python-wrapper.swig | 121 +++++++ lldb/docs/use/python-reference.rst | 46 +++ .../lldb/Interpreter/ScriptInterpreter.h | 17 + lldb/include/lldb/Symbol/SymbolContext.h | 2 +- lldb/include/lldb/Target/Target.h | 86 ++++- lldb/source/Commands/CommandObjectTarget.cpp | 113 +++++- lldb/source/Commands/Options.td | 12 +- .../Python/ScriptInterpreterPython.cpp | 64 ++++ .../Python/ScriptInterpreterPythonImpl.h | 8 + lldb/source/Symbol/SymbolContext.cpp | 8 +- lldb/source/Target/Target.cpp | 338 +++++++++++++----- .../target/stop-hooks/TestStopHookScripted.py | 146 ++++++++ .../target/stop-hooks/TestStopHooks.py | 12 +- .../API/commands/target/stop-hooks/main.c | 3 +- .../commands/target/stop-hooks/stop_hook.py | 49 +++ lldb/test/Shell/Commands/Inputs/stop_hook.py | 10 + .../Commands/command-stop-hook-output.test | 19 + .../Python/PythonTestSuite.cpp | 14 + 19 files changed, 943 insertions(+), 132 deletions(-) create mode 100644 lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py create mode 100644 lldb/test/API/commands/target/stop-hooks/stop_hook.py create mode 100644 lldb/test/Shell/Commands/Inputs/stop_hook.py create mode 100644 lldb/test/Shell/Commands/command-stop-hook-output.test diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig index d5cafbfa67cb2..091fc29b1057d 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 516590ed57713..c00deba6073b4 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( + session_dictionary_name); + auto pfunc = + PythonObject::ResolveNameWithDictionary( + 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("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(implementor)); + auto pfunc = self.ResolveName("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 8c76ef1a08307..60474c94f1850 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 491923e6a6c4f..c38786fd50d42 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 cc49ce51c7139..0f99364596c27 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 92904682ffb63..94c6ebeac10da 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, 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, 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, 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 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 &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 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 c6a7dd3c77baa..cbfebbfee0fd7 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" @@ -4414,10 +4415,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() {} @@ -4431,7 +4432,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': @@ -4561,20 +4563,75 @@ class CommandObjectTargetStopHookAdd : public CommandObjectParsed, // Instance variables to hold the values for one_liner options. bool m_use_one_liner; std::vector 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 { @@ -4598,10 +4655,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(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", @@ -4618,7 +4680,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 specifier_up; @@ -4687,11 +4752,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(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(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 @@ -4704,6 +4788,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 1d604606a7efc..1a23436f9fdf5 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -873,7 +873,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">, @@ -891,19 +891,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 9f56b4fa60a50..f67572c1f0299 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(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 22b2c8152eac0..f89c3d461f7fd 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 51f56704cca66..f20dc61996e0b 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 a879152393853..293845189d49d 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 exc_ctx_with_reasons; - std::vector 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() { @@ -3133,20 +3134,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(*rhs.m_thread_spec_up); } -Target::StopHook::~StopHook() = default; - void Target::StopHook::SetSpecifier(SymbolContextSpecifier *specifier) { m_specifier_sp.reset(specifier); } @@ -3155,8 +3153,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); @@ -3187,15 +3208,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 &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 0000000000000..e650778fe8e3b --- /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 64686afe627da..43447a845156d 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 d08ad14776b5a..16bfc0ce5db6b 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 0000000000000..1abc2bdeeb31b --- /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 0000000000000..e319ca9ec5bc8 --- /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 0000000000000..7890bb3ca5e75 --- /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 f661835d191b1..58ddf0c40a267 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; +} From d932a0b00031ee5e3b5d6baf2d60257d345bd4e9 Mon Sep 17 00:00:00 2001 From: Jim Ingham Date: Fri, 2 Oct 2020 12:43:24 -0700 Subject: [PATCH 2/6] Fix raciness in the StopHook check for "has the target run". This was looking at the privateState, but it's possible that the actual process has started up and then stopped again by the time we get to the check, which would lead us to get out of running the stop hooks too early. Instead we need to track the intention of the stop hooks directly. Differential Revision: https://reviews.llvm.org/D88753 (cherry picked from commit be66987e2047636d9ed9d2a4d88b762d59ae88f2) --- lldb/include/lldb/Target/Target.h | 20 ++++-- lldb/source/Target/Process.cpp | 3 +- lldb/source/Target/Target.cpp | 104 ++++++++++++++++++++---------- 3 files changed, 86 insertions(+), 41 deletions(-) diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 94c6ebeac10da..7ee27a9776d5c 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -1145,6 +1145,11 @@ class Target : public std::enable_shared_from_this, virtual ~StopHook() = default; enum class StopHookKind : uint32_t { CommandBased = 0, ScriptBased }; + enum class StopHookResult : uint32_t { + KeepStopped = 0, + RequestContinue, + AlreadyContinued + }; lldb::TargetSP &GetTarget() { return m_target_sp; } @@ -1160,8 +1165,8 @@ class Target : public std::enable_shared_from_this, // 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; + virtual StopHookResult HandleStop(ExecutionContext &exe_ctx, + lldb::StreamSP output) = 0; // Set the Thread Specifier. The stop hook will own the thread specifier, // and is responsible for deleting it when we're done. @@ -1201,8 +1206,8 @@ class Target : public std::enable_shared_from_this, void SetActionFromString(const std::string &strings); void SetActionFromStrings(const std::vector &strings); - bool HandleStop(ExecutionContext &exc_ctx, - lldb::StreamSP output_sp) override; + StopHookResult HandleStop(ExecutionContext &exc_ctx, + lldb::StreamSP output_sp) override; void GetSubclassDescription(Stream *s, lldb::DescriptionLevel level) const override; @@ -1219,7 +1224,8 @@ class Target : public std::enable_shared_from_this, class StopHookScripted : public StopHook { public: virtual ~StopHookScripted() = default; - bool HandleStop(ExecutionContext &exc_ctx, lldb::StreamSP output) override; + StopHookResult HandleStop(ExecutionContext &exc_ctx, + lldb::StreamSP output) override; Status SetScriptCallback(std::string class_name, StructuredData::ObjectSP extra_args_sp); @@ -1254,7 +1260,9 @@ class Target : public std::enable_shared_from_this, /// remove the stop hook, as it will also reset the stop hook counter. void UndoCreateStopHook(lldb::user_id_t uid); - void RunStopHooks(); + // Runs the stop hooks that have been registered for this target. + // Returns true if the stop hooks cause the target to resume. + bool RunStopHooks(); size_t GetStopHookSize(); diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index d87f57f8ccacd..55e1d281c799e 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -4175,8 +4175,7 @@ void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) { // public (or SyncResume) broadcasters. StopHooks are just for // real public stops. They might also restart the target, // so watch for that. - process_sp->GetTarget().RunStopHooks(); - if (process_sp->GetPrivateState() == eStateRunning) + if (process_sp->GetTarget().RunStopHooks()) SetRestarted(true); } } diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 293845189d49d..8f22ee2528669 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -2541,25 +2541,26 @@ void Target::SetAllStopHooksActiveState(bool active_state) { } } -void Target::RunStopHooks() { +bool Target::RunStopHooks() { if (m_suppress_stop_hooks) - return; + return false; if (!m_process_sp) - return; + return false; // Somebody might have restarted the process: + // Still return false, the return value is about US restarting the target. if (m_process_sp->GetState() != eStateStopped) - return; + return false; // make sure we check that we are not stopped // because of us running a user expression since in that case we do not want // to run the stop-hooks if (m_process_sp->GetModIDRef().IsLastResumeForUserExpression()) - return; + return false; if (m_stop_hooks.empty()) - return; + return false; // If there aren't any active stop hooks, don't bother either. bool any_active_hooks = false; @@ -2570,7 +2571,7 @@ void Target::RunStopHooks() { } } if (!any_active_hooks) - return; + return false; std::vector exc_ctx_with_reasons; @@ -2588,7 +2589,7 @@ void Target::RunStopHooks() { // If no threads stopped for a reason, don't run the stop-hooks. size_t num_exe_ctx = exc_ctx_with_reasons.size(); if (num_exe_ctx == 0) - return; + return false; StreamSP output_sp = m_debugger.GetAsyncOutputStream(); @@ -2636,22 +2637,27 @@ void Target::RunStopHooks() { 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; + StopHook::StopHookResult this_result = + cur_hook_sp->HandleStop(exc_ctx, output_sp); + bool this_should_stop = true; - // If anybody wanted to stop, we should all stop. - if (!should_stop) - should_stop = this_should_stop; + switch (this_result) { + case StopHook::StopHookResult::KeepStopped: + // If this hook is set to auto-continue that should override the + // HandleStop result... + if (cur_hook_sp->GetAutoContinue()) + this_should_stop = false; + else + this_should_stop = true; - // 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) { + break; + case StopHook::StopHookResult::RequestContinue: + this_should_stop = false; + break; + case StopHook::StopHookResult::AlreadyContinued: + // We don't have a good way to prohibit people from restarting the + // target willy nilly in a stop hook. If the hook did so, give a + // gentle suggestion here and bag out if the hook processing. output_sp->Printf("\nAborting stop hooks, hook %" PRIu64 " set the program running.\n" " Consider using '-G true' to make " @@ -2660,16 +2666,42 @@ void Target::RunStopHooks() { somebody_restarted = true; break; } + // If we're already restarted, stop processing stop hooks. + // FIXME: if we are doing non-stop mode for real, we would have to + // check that OUR thread was restarted, otherwise we should keep + // processing stop hooks. + if (somebody_restarted) + break; + + // If anybody wanted to stop, we should all stop. + if (!should_stop) + should_stop = this_should_stop; } } output_sp->Flush(); + // If one of the commands in the stop hook already restarted the target, + // report that fact. + if (somebody_restarted) + return true; + // Finally, if auto-continue was requested, do it now: // 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(); + if ((hooks_ran && !should_stop) || auto_continue) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + Status error = m_process_sp->PrivateResume(); + if (error.Success()) { + LLDB_LOG(log, "Resuming from RunStopHooks"); + return true; + } else { + LLDB_LOG(log, "Resuming from RunStopHooks failed: {0}", error); + return false; + } + } + + return false; } const TargetPropertiesSP &Target::GetGlobalProperties() { @@ -3240,13 +3272,14 @@ void Target::StopHookCommandLine::SetActionFromStrings( GetCommands().AppendString(string.c_str()); } -bool Target::StopHookCommandLine::HandleStop(ExecutionContext &exc_ctx, - StreamSP output_sp) { +Target::StopHook::StopHookResult +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; + return StopHookResult::KeepStopped; CommandReturnObject result(false); result.SetImmediateOutputStream(output_sp); @@ -3265,8 +3298,11 @@ bool Target::StopHookCommandLine::HandleStop(ExecutionContext &exc_ctx, debugger.GetCommandInterpreter().HandleCommands(GetCommands(), &exc_ctx, options, result); debugger.SetAsyncExecution(old_async); - - return true; + lldb::ReturnStatus status = result.GetStatus(); + if (status == eReturnStatusSuccessContinuingNoResult || + status == eReturnStatusSuccessContinuingResult) + return StopHookResult::AlreadyContinued; + return StopHookResult::KeepStopped; } // Target::StopHookScripted @@ -3294,20 +3330,22 @@ Status Target::StopHookScripted::SetScriptCallback( return error; } -bool Target::StopHookScripted::HandleStop(ExecutionContext &exc_ctx, - StreamSP output_sp) { +Target::StopHook::StopHookResult +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; + return StopHookResult::KeepStopped; bool should_stop = script_interp->ScriptedStopHookHandleStop( m_implementation_sp, exc_ctx, output_sp); - return should_stop; + return should_stop ? StopHookResult::KeepStopped + : StopHookResult::RequestContinue; } void Target::StopHookScripted::GetSubclassDescription( From fa050c6615f966edcb3f3360f8445f0a7c359957 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Tue, 6 Oct 2020 15:12:23 -0700 Subject: [PATCH 3/6] [lldb] Change the xcrun (fallback) logic in GetXcodeSDK This changes the logic in GetXcodeSDK to find an SDK with xcrun. The code now executes the following steps: 1. If DEVELOPER_DIR is set in the environment, it invokes xcrun with the given developer dir. If this fails we stop and don't fall back. 2. If the shlib dir is set and exists,it invokes xcrun with the developer dir corresponding to the shlib dir. If this fails we fall back to 3. 3. We run xcrun without a developer dir. The new behavior introduced in this patch is that we fall back to running xcrun without a developer dir if running it based on the shlib dir failed. A situation where this matters is when you're running lldb from an Xcode that has no SDKs and that is not xcode-selected. Based on lldb's shlib dir pointing into this Xcode installation, it will do an xcrun with the developer set to the Xcode without any SDKs which will fail. With this patch, when that happens, we'll fall back to trying the xcode-selected Xcode by running xcrun without a developer dir. Differential revision: https://reviews.llvm.org/D88866 (cherry picked from commit e3b0414b0ea305396a1fcfb2821ad643b0731880) --- .../Host/macosx/objcxx/HostInfoMacOSX.mm | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm index b325bd2c5b745..5e06792e0fbf6 100644 --- a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm +++ b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm @@ -373,26 +373,19 @@ static void ParseOSVersion(llvm::VersionTuple &version, NSString *Key) { static std::string GetXcodeSDK(XcodeSDK sdk) { XcodeSDK::Info info = sdk.Parse(); std::string sdk_name = XcodeSDK::GetCanonicalName(info); - auto find_sdk = [](std::string sdk_name) -> std::string { - std::string xcrun_cmd; - std::string developer_dir = GetEnvDeveloperDir(); - if (developer_dir.empty()) - if (FileSpec fspec = HostInfo::GetShlibDir()) - if (FileSystem::Instance().Exists(fspec)) { - FileSpec path( - XcodeSDK::FindXcodeContentsDirectoryInPath(fspec.GetPath())); - if (path.RemoveLastPathComponent()) - developer_dir = path.GetPath(); - } + + auto xcrun = [](const std::string &sdk, + llvm::StringRef developer_dir = "") -> std::string { + std::string xcrun_cmd = "xcrun --show-sdk-path --sdk " + sdk; if (!developer_dir.empty()) - xcrun_cmd = "/usr/bin/env DEVELOPER_DIR=\"" + developer_dir + "\" "; - xcrun_cmd += "xcrun --show-sdk-path --sdk " + sdk_name; + xcrun_cmd = "/usr/bin/env DEVELOPER_DIR=\"" + developer_dir.str() + + "\" " + xcrun_cmd; int status = 0; int signo = 0; std::string output_str; lldb_private::Status error = - Host::RunShellCommand(xcrun_cmd.c_str(), FileSpec(), &status, &signo, + Host::RunShellCommand(xcrun_cmd, FileSpec(), &status, &signo, &output_str, std::chrono::seconds(15)); // Check that xcrun return something useful. @@ -414,6 +407,33 @@ FileSpec path( return output.str(); }; + auto find_sdk = [&xcrun](const std::string &sdk_name) -> std::string { + // Invoke xcrun with the developer dir specified in the environment. + std::string developer_dir = GetEnvDeveloperDir(); + if (!developer_dir.empty()) { + // Don't fallback if DEVELOPER_DIR was set. + return xcrun(sdk_name, developer_dir); + } + + // Invoke xcrun with the shlib dir. + if (FileSpec fspec = HostInfo::GetShlibDir()) { + if (FileSystem::Instance().Exists(fspec)) { + std::string contents_dir = + XcodeSDK::FindXcodeContentsDirectoryInPath(fspec.GetPath()); + llvm::StringRef shlib_developer_dir = + llvm::sys::path::parent_path(contents_dir); + if (!shlib_developer_dir.empty()) { + std::string sdk = xcrun(sdk_name, std::move(shlib_developer_dir)); + if (!sdk.empty()) + return sdk; + } + } + } + + // Invoke xcrun without a developer dir as a last resort. + return xcrun(sdk_name); + }; + std::string path = find_sdk(sdk_name); while (path.empty()) { // Try an alternate spelling of the name ("macosx10.9internal"). From e84852be644d34867a604997fd328bf411b1977d Mon Sep 17 00:00:00 2001 From: Shivanshu Goyal Date: Tue, 6 Oct 2020 16:12:48 +0200 Subject: [PATCH 4/6] Add ability to turn off -fpch-instantiate-templates in clang-cl A lot of our code building with clang-cl.exe using Clang 11 was failing with the following 2 type of errors: 1. explicit specialization of 'foo' after instantiation 2. no matching function for call to 'bar' Note that we also use -fdelayed-template-parsing in our builds. I tried pretty hard to get a small repro for these failures, but couldn't. So there is some subtle edge case in the -fpch-instantiate-templates feature introduced by this change: https://reviews.llvm.org/D69585 When I tried turning this off using -fno-pch-instantiate-templates, builds would silently fail with the same error without any indication that -fno-pch-instantiate-templates was being ignored by the compiler. Then I realized this "no" option wasn't actually working when I ran Clang under a debugger. Differential revision: https://reviews.llvm.org/D88680 (cherry picked from commit 66e4f07198761bbb4dcd55235024c1081ed15c75) --- clang/include/clang/Driver/Options.td | 4 ++-- clang/lib/Driver/ToolChains/Clang.cpp | 6 +++++- clang/test/Driver/pch-instantiate-templates.c | 13 +++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 clang/test/Driver/pch-instantiate-templates.c diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index f818acb39d510..966cb907b7e20 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1435,11 +1435,11 @@ def fno_pch_validate_input_files_content: Group, Flags<[DriverOption]>; def fpch_instantiate_templates: Flag <["-"], "fpch-instantiate-templates">, - Group, Flags<[CC1Option]>, + Group, Flags<[CC1Option, CoreOption]>, HelpText<"Instantiate templates already while building a PCH">; def fno_pch_instantiate_templates: Flag <["-"], "fno-pch-instantiate-templates">, - Group, Flags<[CC1Option]>; + Group, Flags<[CC1Option, CoreOption]>; defm pch_codegen: OptInFFlag<"pch-codegen", "Generate ", "Do not generate ", "code for uses of this PCH that assumes an explicit object file will be built for the PCH">; defm pch_debuginfo: OptInFFlag<"pch-debuginfo", "Generate ", "Do not generate ", diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f0a5451322aaa..af4bcf951e6c8 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -1197,7 +1197,11 @@ void Clang::AddPreprocessingOptions(Compilation &C, const JobAction &JA, if (YcArg && JA.getKind() >= Action::PrecompileJobClass && JA.getKind() <= Action::AssembleJobClass) { CmdArgs.push_back(Args.MakeArgString("-building-pch-with-obj")); - CmdArgs.push_back(Args.MakeArgString("-fpch-instantiate-templates")); + // -fpch-instantiate-templates is the default when creating + // precomp using /Yc + if (Args.hasFlag(options::OPT_fpch_instantiate_templates, + options::OPT_fno_pch_instantiate_templates, true)) + CmdArgs.push_back(Args.MakeArgString("-fpch-instantiate-templates")); } if (YcArg || YuArg) { StringRef ThroughHeader = YcArg ? YcArg->getValue() : YuArg->getValue(); diff --git a/clang/test/Driver/pch-instantiate-templates.c b/clang/test/Driver/pch-instantiate-templates.c new file mode 100644 index 0000000000000..b0f7f34739938 --- /dev/null +++ b/clang/test/Driver/pch-instantiate-templates.c @@ -0,0 +1,13 @@ +// CL driver test cases +// RUN: %clang_cl -### /Yc /Fpfoo.pch /Fofoo.obj -- %s 2>&1 | FileCheck --check-prefix=CLANG_CL_YC %s +// RUN: %clang_cl -### /Yc /Fpfoo.pch /Fofoo.obj -fno-pch-instantiate-templates -- %s 2>&1 | FileCheck --check-prefix=CLANG_CL_YC_DISABLE %s + +// CLANG_CL_YC: "-fpch-instantiate-templates" +// CLANG_CL_YC_DISABLE-NOT: "-fpch-instantiate-templates" + +// GCC driver test cases +// RUN: %clang -### -x c-header %s -o %t/foo.pch 2>&1 | FileCheck -check-prefix=GCC_DEFAULT %s +// RUN: %clang -### -x c-header %s -o %t/foo.pch -fpch-instantiate-templates 2>&1 | FileCheck -check-prefix=GCC_DEFAULT_ENABLE %s + +// GCC_DEFAULT-NOT: "-fpch-instantiate-templates" +// GCC_DEFAULT_ENABLE: "-fpch-instantiate-templates" From 176249bd6732a8044d457092ed932768724a6f06 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Tue, 6 Oct 2020 17:54:36 -0700 Subject: [PATCH 5/6] [CodeGen][TailDuplicator] Don't duplicate blocks with INLINEASM_BR Tail duplication of a block with an INLINEASM_BR may result in a PHI node on the indirect branch. This is okay, but it also introduces a copy for that PHI node *after* the INLINEASM_BR, which is not okay. See: https://github.com/ClangBuiltLinux/linux/issues/1125 Differential Revision: https://reviews.llvm.org/D88823 (cherry picked from commit d2c61d2bf9bd1efad49acba2f2751112522686aa) --- llvm/lib/CodeGen/TailDuplicator.cpp | 8 +++ llvm/test/CodeGen/X86/tail-dup-asm-goto.ll | 61 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 llvm/test/CodeGen/X86/tail-dup-asm-goto.ll diff --git a/llvm/lib/CodeGen/TailDuplicator.cpp b/llvm/lib/CodeGen/TailDuplicator.cpp index bd554189f12b5..f9773f74a7bdd 100644 --- a/llvm/lib/CodeGen/TailDuplicator.cpp +++ b/llvm/lib/CodeGen/TailDuplicator.cpp @@ -627,6 +627,14 @@ bool TailDuplicator::shouldTailDuplicate(bool IsSimple, if (PreRegAlloc && MI.isCall()) return false; + // TailDuplicator::appendCopies will erroneously place COPYs after + // INLINEASM_BR instructions after 4b0aa5724fea, which demonstrates the same + // bug that was fixed in f7a53d82c090. + // FIXME: Use findPHICopyInsertPoint() to find the correct insertion point + // for the COPY when replacing PHIs. + if (MI.getOpcode() == TargetOpcode::INLINEASM_BR) + return false; + if (MI.isBundle()) InstrCount += MI.getBundleSize(); else if (!MI.isPHI() && !MI.isMetaInstruction()) diff --git a/llvm/test/CodeGen/X86/tail-dup-asm-goto.ll b/llvm/test/CodeGen/X86/tail-dup-asm-goto.ll new file mode 100644 index 0000000000000..77aa3adf0fc69 --- /dev/null +++ b/llvm/test/CodeGen/X86/tail-dup-asm-goto.ll @@ -0,0 +1,61 @@ +; NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py +; RUN: llc -mtriple=x86_64-linux -stop-after=early-tailduplication < %s | FileCheck %s + +; Ensure that we don't duplicate a block with an "INLINEASM_BR" instruction +; during code gen. +declare void @foo() + +define i8* @test1(i8** %arg1, i8* %arg2) { + ; CHECK-LABEL: name: test1 + ; CHECK: bb.0.bb: + ; CHECK: successors: %bb.1(0x50000000), %bb.2(0x30000000) + ; CHECK: liveins: $rdi, $rsi + ; CHECK: [[COPY:%[0-9]+]]:gr64 = COPY $rsi + ; CHECK: [[COPY1:%[0-9]+]]:gr64 = COPY $rdi + ; CHECK: [[MOV64rm:%[0-9]+]]:gr64 = MOV64rm [[COPY1]], 1, $noreg, 0, $noreg :: (load 8 from %ir.arg1) + ; CHECK: [[SUB64rr:%[0-9]+]]:gr64 = SUB64rr [[MOV64rm]], [[COPY]], implicit-def $eflags + ; CHECK: JCC_1 %bb.2, 4, implicit $eflags + ; CHECK: JMP_1 %bb.1 + ; CHECK: bb.1.bb100: + ; CHECK: successors: %bb.3(0x80000000) + ; CHECK: MOV64mi32 [[COPY1]], 1, $noreg, 0, $noreg, 0 :: (store 8 into %ir.arg1) + ; CHECK: JMP_1 %bb.3 + ; CHECK: bb.2.bb106: + ; CHECK: successors: %bb.3(0x80000000) + ; CHECK: ADJCALLSTACKDOWN64 0, 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp + ; CHECK: CALL64pcrel32 @foo, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp + ; CHECK: ADJCALLSTACKUP64 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp + ; CHECK: bb.3.bb110: + ; CHECK: successors: %bb.5(0x80000000), %bb.4(0x00000000) + ; CHECK: [[PHI:%[0-9]+]]:gr64 = PHI [[COPY]], %bb.2, [[MOV64rm]], %bb.1 + ; CHECK: INLINEASM_BR &"#$0 $1 $2", 9 /* sideeffect mayload attdialect */, 13 /* imm */, 42, 13 /* imm */, 0, 13 /* imm */, blockaddress(@test1, %ir-block.bb17.i.i.i), 12 /* clobber */, implicit-def early-clobber $df, 12 /* clobber */, implicit-def early-clobber $fpsw, 12 /* clobber */, implicit-def early-clobber $eflags + ; CHECK: JMP_1 %bb.5 + ; CHECK: bb.4.bb17.i.i.i (address-taken): + ; CHECK: successors: %bb.5(0x80000000) + ; CHECK: bb.5.kmem_cache_has_cpu_partial.exit: + ; CHECK: $rax = COPY [[PHI]] + ; CHECK: RET 0, $rax +bb: + %i28.i = load i8*, i8** %arg1, align 8 + %if = icmp ne i8* %i28.i, %arg2 + br i1 %if, label %bb100, label %bb106 + +bb100: ; preds = %bb + store i8* null, i8** %arg1, align 8 + br label %bb110 + +bb106: ; preds = %bb + call void @foo() + br label %bb110 + +bb110: ; preds = %bb106, %bb100 + %i10.1 = phi i8* [ %arg2, %bb106 ], [ %i28.i, %bb100 ] + callbr void asm sideeffect "#$0 $1 $2", "i,i,X,~{dirflag},~{fpsr},~{flags}"(i32 42, i1 false, i8* blockaddress(@test1, %bb17.i.i.i)) + to label %kmem_cache_has_cpu_partial.exit [label %bb17.i.i.i] + +bb17.i.i.i: ; preds = %bb110 + br label %kmem_cache_has_cpu_partial.exit + +kmem_cache_has_cpu_partial.exit: ; preds = %bb110 + ret i8* %i10.1 +} From adb227f71c466ed53e9ea5a59e85c4dbb61522d8 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Wed, 7 Oct 2020 14:52:24 -0700 Subject: [PATCH 6/6] [lldb] Regenerate the static bindings --- .../python/static-binding/LLDBWrapPython.cpp | 189 ++++++++++++++++++ lldb/bindings/python/static-binding/lldb.py | 52 ++++- 2 files changed, 240 insertions(+), 1 deletion(-) diff --git a/lldb/bindings/python/static-binding/LLDBWrapPython.cpp b/lldb/bindings/python/static-binding/LLDBWrapPython.cpp index 269e1173311d7..b481edaf48a06 100644 --- a/lldb/bindings/python/static-binding/LLDBWrapPython.cpp +++ b/lldb/bindings/python/static-binding/LLDBWrapPython.cpp @@ -64135,6 +64135,66 @@ SWIGINTERN PyObject *_wrap_SBThreadPlan_IsPlanStale(PyObject *SWIGUNUSEDPARM(sel } +SWIGINTERN PyObject *_wrap_SBThreadPlan_GetStopOthers(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + lldb::SBThreadPlan *arg1 = (lldb::SBThreadPlan *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject *swig_obj[1] ; + bool result; + + if (!args) SWIG_fail; + swig_obj[0] = args; + res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_lldb__SBThreadPlan, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "SBThreadPlan_GetStopOthers" "', argument " "1"" of type '" "lldb::SBThreadPlan *""'"); + } + arg1 = reinterpret_cast< lldb::SBThreadPlan * >(argp1); + { + SWIG_PYTHON_THREAD_BEGIN_ALLOW; + result = (bool)(arg1)->GetStopOthers(); + SWIG_PYTHON_THREAD_END_ALLOW; + } + resultobj = SWIG_From_bool(static_cast< bool >(result)); + return resultobj; +fail: + return NULL; +} + + +SWIGINTERN PyObject *_wrap_SBThreadPlan_SetStopOthers(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + lldb::SBThreadPlan *arg1 = (lldb::SBThreadPlan *) 0 ; + bool arg2 ; + void *argp1 = 0 ; + int res1 = 0 ; + bool val2 ; + int ecode2 = 0 ; + PyObject *swig_obj[2] ; + + if (!SWIG_Python_UnpackTuple(args, "SBThreadPlan_SetStopOthers", 2, 2, swig_obj)) SWIG_fail; + res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_lldb__SBThreadPlan, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "SBThreadPlan_SetStopOthers" "', argument " "1"" of type '" "lldb::SBThreadPlan *""'"); + } + arg1 = reinterpret_cast< lldb::SBThreadPlan * >(argp1); + ecode2 = SWIG_AsVal_bool(swig_obj[1], &val2); + if (!SWIG_IsOK(ecode2)) { + SWIG_exception_fail(SWIG_ArgError(ecode2), "in method '" "SBThreadPlan_SetStopOthers" "', argument " "2"" of type '" "bool""'"); + } + arg2 = static_cast< bool >(val2); + { + SWIG_PYTHON_THREAD_BEGIN_ALLOW; + (arg1)->SetStopOthers(arg2); + SWIG_PYTHON_THREAD_END_ALLOW; + } + resultobj = SWIG_Py_Void(); + return resultobj; +fail: + return NULL; +} + + SWIGINTERN PyObject *_wrap_SBThreadPlan_QueueThreadPlanForStepOverRange(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *resultobj = 0; lldb::SBThreadPlan *arg1 = (lldb::SBThreadPlan *) 0 ; @@ -80183,6 +80243,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( + session_dictionary_name); + auto pfunc = + PythonObject::ResolveNameWithDictionary( + 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("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(implementor)); + auto pfunc = self.ResolveName("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 @@ -83893,6 +84074,11 @@ static PyMethodDef SwigMethods[] = { { "SBThreadPlan_SetPlanComplete", _wrap_SBThreadPlan_SetPlanComplete, METH_VARARGS, "SBThreadPlan_SetPlanComplete(SBThreadPlan self, bool success)"}, { "SBThreadPlan_IsPlanComplete", _wrap_SBThreadPlan_IsPlanComplete, METH_O, "SBThreadPlan_IsPlanComplete(SBThreadPlan self) -> bool"}, { "SBThreadPlan_IsPlanStale", _wrap_SBThreadPlan_IsPlanStale, METH_O, "SBThreadPlan_IsPlanStale(SBThreadPlan self) -> bool"}, + { "SBThreadPlan_GetStopOthers", _wrap_SBThreadPlan_GetStopOthers, METH_O, "\n" + "SBThreadPlan_GetStopOthers(SBThreadPlan self) -> bool\n" + "Return whether this plan will ask to stop other threads when it runs.\n" + ""}, + { "SBThreadPlan_SetStopOthers", _wrap_SBThreadPlan_SetStopOthers, METH_VARARGS, "SBThreadPlan_SetStopOthers(SBThreadPlan self, bool stop_others)"}, { "SBThreadPlan_QueueThreadPlanForStepOverRange", _wrap_SBThreadPlan_QueueThreadPlanForStepOverRange, METH_VARARGS, "SBThreadPlan_QueueThreadPlanForStepOverRange(SBThreadPlan self, SBAddress start_address, lldb::addr_t range_size) -> SBThreadPlan"}, { "SBThreadPlan_QueueThreadPlanForStepInRange", _wrap_SBThreadPlan_QueueThreadPlanForStepInRange, METH_VARARGS, "SBThreadPlan_QueueThreadPlanForStepInRange(SBThreadPlan self, SBAddress start_address, lldb::addr_t range_size) -> SBThreadPlan"}, { "SBThreadPlan_QueueThreadPlanForStepOut", _wrap_SBThreadPlan_QueueThreadPlanForStepOut, METH_VARARGS, "SBThreadPlan_QueueThreadPlanForStepOut(SBThreadPlan self, uint32_t frame_idx_to_step_to, bool first_insn=False) -> SBThreadPlan"}, @@ -86308,6 +86494,7 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "LLDB_INVALID_SIGNAL_NUMBER",SWIG_From_int(static_cast< int >(2147483647))); SWIG_Python_SetConstant(d, "LLDB_INVALID_OFFSET",SWIG_From_unsigned_SS_long_SS_long(static_cast< unsigned long long >(18446744073709551615ULL))); SWIG_Python_SetConstant(d, "LLDB_INVALID_LINE_NUMBER",SWIG_From_unsigned_SS_int(static_cast< unsigned int >(4294967295U))); + SWIG_Python_SetConstant(d, "LLDB_INVALID_COLUMN_NUMBER",SWIG_From_int(static_cast< int >(0))); SWIG_Python_SetConstant(d, "LLDB_INVALID_QUEUE_ID",SWIG_From_int(static_cast< int >(0))); SWIG_Python_SetConstant(d, "LLDB_ARCH_DEFAULT",SWIG_FromCharPtr("systemArch")); SWIG_Python_SetConstant(d, "LLDB_ARCH_DEFAULT_32BIT",SWIG_FromCharPtr("systemArch32")); @@ -86326,6 +86513,7 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "LLDB_OPT_SET_9",SWIG_From_unsigned_SS_int(static_cast< unsigned int >((1U << 8)))); SWIG_Python_SetConstant(d, "LLDB_OPT_SET_10",SWIG_From_unsigned_SS_int(static_cast< unsigned int >((1U << 9)))); SWIG_Python_SetConstant(d, "LLDB_OPT_SET_11",SWIG_From_unsigned_SS_int(static_cast< unsigned int >((1U << 10)))); + SWIG_Python_SetConstant(d, "LLDB_OPT_SET_12",SWIG_From_unsigned_SS_int(static_cast< unsigned int >((1U << 11)))); SWIG_Python_SetConstant(d, "eStateInvalid",SWIG_From_int(static_cast< int >(lldb::eStateInvalid))); SWIG_Python_SetConstant(d, "eStateUnloaded",SWIG_From_int(static_cast< int >(lldb::eStateUnloaded))); SWIG_Python_SetConstant(d, "eStateConnected",SWIG_From_int(static_cast< int >(lldb::eStateConnected))); @@ -86608,6 +86796,7 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "eArgTypeExpression",SWIG_From_int(static_cast< int >(lldb::eArgTypeExpression))); SWIG_Python_SetConstant(d, "eArgTypeExpressionPath",SWIG_From_int(static_cast< int >(lldb::eArgTypeExpressionPath))); SWIG_Python_SetConstant(d, "eArgTypeExprFormat",SWIG_From_int(static_cast< int >(lldb::eArgTypeExprFormat))); + SWIG_Python_SetConstant(d, "eArgTypeFileLineColumn",SWIG_From_int(static_cast< int >(lldb::eArgTypeFileLineColumn))); SWIG_Python_SetConstant(d, "eArgTypeFilename",SWIG_From_int(static_cast< int >(lldb::eArgTypeFilename))); SWIG_Python_SetConstant(d, "eArgTypeFormat",SWIG_From_int(static_cast< int >(lldb::eArgTypeFormat))); SWIG_Python_SetConstant(d, "eArgTypeFrameIndex",SWIG_From_int(static_cast< int >(lldb::eArgTypeFrameIndex))); diff --git a/lldb/bindings/python/static-binding/lldb.py b/lldb/bindings/python/static-binding/lldb.py index f5c61fb05da4d..e3c1a5c8a2b91 100644 --- a/lldb/bindings/python/static-binding/lldb.py +++ b/lldb/bindings/python/static-binding/lldb.py @@ -199,6 +199,8 @@ def __init__(self): LLDB_INVALID_LINE_NUMBER = _lldb.LLDB_INVALID_LINE_NUMBER +LLDB_INVALID_COLUMN_NUMBER = _lldb.LLDB_INVALID_COLUMN_NUMBER + LLDB_INVALID_QUEUE_ID = _lldb.LLDB_INVALID_QUEUE_ID LLDB_ARCH_DEFAULT = _lldb.LLDB_ARCH_DEFAULT @@ -235,6 +237,8 @@ def __init__(self): LLDB_OPT_SET_11 = _lldb.LLDB_OPT_SET_11 +LLDB_OPT_SET_12 = _lldb.LLDB_OPT_SET_12 + eStateInvalid = _lldb.eStateInvalid eStateUnloaded = _lldb.eStateUnloaded @@ -799,6 +803,8 @@ def __init__(self): eArgTypeExprFormat = _lldb.eArgTypeExprFormat +eArgTypeFileLineColumn = _lldb.eArgTypeFileLineColumn + eArgTypeFilename = _lldb.eArgTypeFilename eArgTypeFormat = _lldb.eArgTypeFormat @@ -11645,6 +11651,17 @@ def IsPlanStale(self): r"""IsPlanStale(SBThreadPlan self) -> bool""" return _lldb.SBThreadPlan_IsPlanStale(self) + def GetStopOthers(self): + r""" + GetStopOthers(SBThreadPlan self) -> bool + Return whether this plan will ask to stop other threads when it runs. + """ + return _lldb.SBThreadPlan_GetStopOthers(self) + + def SetStopOthers(self, stop_others): + r"""SetStopOthers(SBThreadPlan self, bool stop_others)""" + return _lldb.SBThreadPlan_SetStopOthers(self, stop_others) + def QueueThreadPlanForStepOverRange(self, start_address, range_size): r"""QueueThreadPlanForStepOverRange(SBThreadPlan self, SBAddress start_address, lldb::addr_t range_size) -> SBThreadPlan""" return _lldb.SBThreadPlan_QueueThreadPlanForStepOverRange(self, start_address, range_size) @@ -12653,7 +12670,19 @@ def __str__(self): _lldb.SBTypeEnumMember_swigregister(SBTypeEnumMember) class SBTypeEnumMemberList(object): - r"""Represents a list of SBTypeEnumMembers.""" + r""" + Represents a list of SBTypeEnumMembers. + SBTypeEnumMemberList supports SBTypeEnumMember iteration. + It also supports [] access either by index, or by enum + element name by doing: + + myType = target.FindFirstType('MyEnumWithElementA') + members = myType.GetEnumMembers() + first_elem = members[0] + elem_A = members['A'] + + + """ thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag") __repr__ = _swig_repr @@ -12688,6 +12717,27 @@ def GetSize(self): r"""GetSize(SBTypeEnumMemberList self) -> uint32_t""" return _lldb.SBTypeEnumMemberList_GetSize(self) + def __iter__(self): + '''Iterate over all members in a lldb.SBTypeEnumMemberList object.''' + return lldb_iter(self, 'GetSize', 'GetTypeEnumMemberAtIndex') + + def __len__(self): + '''Return the number of members in a lldb.SBTypeEnumMemberList object.''' + return self.GetSize() + + def __getitem__(self, key): + num_elements = self.GetSize() + if type(key) is int: + if key < num_elements: + return self.GetTypeEnumMemberAtIndex(key) + elif type(key) is str: + for idx in range(num_elements): + item = self.GetTypeEnumMemberAtIndex(idx) + if item.name == key: + return item + return None + + # Register SBTypeEnumMemberList in _lldb: _lldb.SBTypeEnumMemberList_swigregister(SBTypeEnumMemberList)