Add a first pass at a "stop hook" mechanism.  This allows you to add commands that get run every time the debugger stops, whether due to a breakpoint, the end of a step, interrupt, etc.  You can also specify in which context you want the stop hook to run, for instance only on a particular thread, or only in a particular shared library, function, file, line range within a file.

Still need to add "in methods of a class" to the specifiers, and the ability to write the stop hooks in the Scripting language as well as in the Command Language.

git-svn-id: https://llvm.org/svn/llvm-project/llvdb/trunk@127457 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/source/Commands/CommandObjectTarget.cpp b/source/Commands/CommandObjectTarget.cpp
index 01bdfa9..d18d34e 100644
--- a/source/Commands/CommandObjectTarget.cpp
+++ b/source/Commands/CommandObjectTarget.cpp
@@ -17,13 +17,14 @@
 // Project includes
 #include "lldb/Interpreter/Args.h"
 #include "lldb/Core/Debugger.h"
+#include "lldb/Core/InputReader.h"
 #include "lldb/Core/Timer.h"
-#include "lldb/Core/Debugger.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/StackFrame.h"
 #include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadSpec.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -447,7 +448,7 @@
 //};
 
 
-#pragma mark CommandObjectMultiwordTarget
+#pragma mark CommandObjectMultiwordImageSearchPaths
 
 //-------------------------------------------------------------------------
 // CommandObjectMultiwordImageSearchPaths
@@ -475,6 +476,620 @@
     }
 };
 
+#pragma mark CommandObjectTargetStopHookAdd
+
+//-------------------------------------------------------------------------
+// CommandObjectTargetStopHookAdd
+//-------------------------------------------------------------------------
+
+class CommandObjectTargetStopHookAdd : public CommandObject
+{
+public:
+
+    class CommandOptions : public Options
+    {
+    public:
+        CommandOptions () :
+            Options(),
+            m_line_start(0),
+            m_line_end (UINT_MAX),
+            m_func_name_type_mask (eFunctionNameTypeAuto),
+            m_sym_ctx_specified (false),
+            m_thread_specified (false)
+        {
+        }
+        
+        ~CommandOptions () {}
+        
+        const lldb::OptionDefinition*
+        GetDefinitions ()
+        {
+            return g_option_table;
+        }
+
+        virtual Error
+        SetOptionValue (int option_idx, const char *option_arg)
+        {
+            Error error;
+            char short_option = (char) m_getopt_table[option_idx].val;
+            bool success;
+
+            switch (short_option)
+            {
+                case 'c':
+                    m_class_name = option_arg;
+                    m_sym_ctx_specified = true;
+                break;
+                
+                case 'e':
+                    m_line_end = Args::StringToUInt32 (option_arg, UINT_MAX, 0, &success);
+                    if (!success)
+                    {
+                        error.SetErrorStringWithFormat ("Invalid end line number: \"%s\".", option_arg);
+                        break;
+                    }
+                    m_sym_ctx_specified = true;
+                break;
+                
+                case 'l':
+                    m_line_start = Args::StringToUInt32 (option_arg, 0, 0, &success);
+                    if (!success)
+                    {
+                        error.SetErrorStringWithFormat ("Invalid start line number: \"%s\".", option_arg);
+                        break;
+                    }
+                    m_sym_ctx_specified = true;
+                break;
+                
+                case 'n':
+                    m_function_name = option_arg;
+                    m_func_name_type_mask |= eFunctionNameTypeAuto;
+                    m_sym_ctx_specified = true;
+                break;
+                
+                case 'f':
+                    m_file_name = option_arg;
+                    m_sym_ctx_specified = true;
+                break;
+                case 's':
+                    m_module_name = option_arg;
+                    m_sym_ctx_specified = true;
+                break;
+                case 't' :
+                {
+                    m_thread_id = Args::StringToUInt64(optarg, LLDB_INVALID_THREAD_ID, 0);
+                    if (m_thread_id == LLDB_INVALID_THREAD_ID)
+                       error.SetErrorStringWithFormat ("Invalid thread id string '%s'.\n", optarg);
+                    m_thread_specified = true;
+                }
+                break;
+                case 'T':
+                    m_thread_name = option_arg;
+                    m_thread_specified = true;
+                break;
+                case 'q':
+                    m_queue_name = option_arg;
+                    m_thread_specified = true;
+                    break;
+                case 'x':
+                {
+                    m_thread_index = Args::StringToUInt32(optarg, UINT32_MAX, 0);
+                    if (m_thread_id == UINT32_MAX)
+                       error.SetErrorStringWithFormat ("Invalid thread index string '%s'.\n", optarg);
+                    m_thread_specified = true;
+                }
+                break;
+                default:
+                    error.SetErrorStringWithFormat ("Unrecognized option %c.");
+                break;
+            }
+            return error;
+        }
+
+        void
+        ResetOptionValues ()
+        {
+            m_class_name.clear();
+            m_function_name.clear();
+            m_line_start = 0;
+            m_line_end = UINT_MAX;
+            m_file_name.clear();
+            m_module_name.clear();
+            m_func_name_type_mask = eFunctionNameTypeAuto;
+            m_thread_id = LLDB_INVALID_THREAD_ID;
+            m_thread_index = UINT32_MAX;
+            m_thread_name.clear();
+            m_queue_name.clear();
+
+            m_sym_ctx_specified = false;
+            m_thread_specified = false;
+        }
+
+        
+        static lldb::OptionDefinition g_option_table[];
+        
+        std::string m_class_name;
+        std::string m_function_name;
+        uint32_t    m_line_start;
+        uint32_t    m_line_end;
+        std::string m_file_name;
+        std::string m_module_name;
+        uint32_t m_func_name_type_mask;  // A pick from lldb::FunctionNameType.
+        lldb::tid_t m_thread_id;
+        uint32_t m_thread_index;
+        std::string m_thread_name;
+        std::string m_queue_name;
+        bool        m_sym_ctx_specified;
+        bool        m_thread_specified;
+    
+    };
+
+    Options *
+    GetOptions ()
+    {
+        return &m_options;
+    }
+
+    CommandObjectTargetStopHookAdd (CommandInterpreter &interpreter) :
+        CommandObject (interpreter,
+                       "target stop-hook add ",
+                       "Add a hook to be executed when the target stops.",
+                       "target stop-hook add")
+    {
+    }
+
+    ~CommandObjectTargetStopHookAdd ()
+    {
+    }
+
+    static size_t 
+    ReadCommandsCallbackFunction (void *baton, 
+                                  InputReader &reader, 
+                                  lldb::InputReaderAction notification,
+                                  const char *bytes, 
+                                  size_t bytes_len)
+    {
+        File &out_file = reader.GetDebugger().GetOutputFile();
+        Target::StopHook *new_stop_hook = ((Target::StopHook *) baton);
+
+        switch (notification)
+        {
+        case eInputReaderActivate:
+            out_file.Printf ("%s\n", "Enter your stop hook command(s).  Type 'DONE' to end.");
+            if (reader.GetPrompt())
+                out_file.Printf ("%s", reader.GetPrompt());
+            out_file.Flush();
+            break;
+
+        case eInputReaderDeactivate:
+            break;
+
+        case eInputReaderReactivate:
+            if (reader.GetPrompt())
+            {
+                out_file.Printf ("%s", reader.GetPrompt());
+                out_file.Flush();
+            }
+            break;
+
+        case eInputReaderGotToken:
+            if (bytes && bytes_len && baton)
+            {
+                StringList *commands = new_stop_hook->GetCommandPointer();
+                if (commands)
+                {
+                    commands->AppendString (bytes, bytes_len); 
+                }
+            }
+            if (!reader.IsDone() && reader.GetPrompt())
+            {
+                out_file.Printf ("%s", reader.GetPrompt());
+                out_file.Flush();
+            }
+            break;
+            
+        case eInputReaderInterrupt:
+            {
+                // Finish, and cancel the stop hook.
+                new_stop_hook->GetTarget()->RemoveStopHookByID(new_stop_hook->GetID());
+                out_file.Printf ("Stop hook cancelled.\n");
+
+                reader.SetIsDone (true);
+            }
+            break;
+            
+        case eInputReaderEndOfFile:
+            reader.SetIsDone (true);
+            break;
+            
+        case eInputReaderDone:
+            out_file.Printf ("Stop hook #%d added.\n", new_stop_hook->GetID());
+            break;
+        }
+
+        return bytes_len;
+    }
+
+    bool
+    Execute (Args& command,
+             CommandReturnObject &result)
+    {
+        Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get();
+        if (target)
+        {
+            Target::StopHookSP new_hook_sp;
+            target->AddStopHook (new_hook_sp);
+
+            //  First step, make the specifier.
+            std::auto_ptr<SymbolContextSpecifier> specifier_ap;
+            if (m_options.m_sym_ctx_specified)
+            {
+                specifier_ap.reset(new SymbolContextSpecifier(m_interpreter.GetDebugger().GetSelectedTarget()));
+                
+                if (!m_options.m_module_name.empty())
+                {
+                    specifier_ap->AddSpecification (m_options.m_module_name.c_str(), SymbolContextSpecifier::eModuleSpecified);
+                }
+                
+                if (!m_options.m_class_name.empty())
+                {
+                    specifier_ap->AddSpecification (m_options.m_class_name.c_str(), SymbolContextSpecifier::eClassOrNamespaceSpecified);
+                }
+                
+                if (!m_options.m_file_name.empty())
+                {
+                    specifier_ap->AddSpecification (m_options.m_file_name.c_str(), SymbolContextSpecifier::eFileSpecified);
+                }
+                
+                if (m_options.m_line_start != 0)
+                {
+                    specifier_ap->AddLineSpecification (m_options.m_line_start, SymbolContextSpecifier::eLineStartSpecified);
+                }
+                
+                if (m_options.m_line_end != UINT_MAX)
+                {
+                    specifier_ap->AddLineSpecification (m_options.m_line_end, SymbolContextSpecifier::eLineEndSpecified);
+                }
+                
+                if (!m_options.m_function_name.empty())
+                {
+                    specifier_ap->AddSpecification (m_options.m_function_name.c_str(), SymbolContextSpecifier::eFunctionSpecified);
+                }
+            }
+            
+            if (specifier_ap.get())
+                new_hook_sp->SetSpecifier (specifier_ap.release());
+
+            // Next see if any of the thread options have been entered:
+            
+            if (m_options.m_thread_specified)
+            {
+                ThreadSpec *thread_spec = new ThreadSpec();
+                
+                if (m_options.m_thread_id != LLDB_INVALID_THREAD_ID)
+                {
+                    thread_spec->SetTID (m_options.m_thread_id);
+                }
+                
+                if (m_options.m_thread_index != UINT32_MAX)
+                    thread_spec->SetIndex (m_options.m_thread_index);
+                
+                if (!m_options.m_thread_name.empty())
+                    thread_spec->SetName (m_options.m_thread_name.c_str());
+                
+                if (!m_options.m_queue_name.empty())
+                    thread_spec->SetQueueName (m_options.m_queue_name.c_str());
+                    
+                new_hook_sp->SetThreadSpecifier (thread_spec);
+            
+            }
+            // Next gather up the command list, we'll push an input reader and suck the data from that directly into
+            // the new stop hook's command string.
+            
+            InputReaderSP reader_sp (new InputReader(m_interpreter.GetDebugger()));
+            if (!reader_sp)
+            {
+                result.AppendError("out of memory");
+                result.SetStatus (eReturnStatusFailed);
+                target->RemoveStopHookByID (new_hook_sp->GetID());
+                return false;
+            }
+            
+            Error err (reader_sp->Initialize (CommandObjectTargetStopHookAdd::ReadCommandsCallbackFunction,
+                                              new_hook_sp.get(), // baton
+                                              eInputReaderGranularityLine,  // token size, to pass to callback function
+                                              "DONE",                       // end token
+                                              "> ",                         // prompt
+                                              true));                       // echo input
+            if (!err.Success())
+            {
+                result.AppendError (err.AsCString());
+                result.SetStatus (eReturnStatusFailed);
+                target->RemoveStopHookByID (new_hook_sp->GetID());
+                return false;
+            }
+            m_interpreter.GetDebugger().PushInputReader (reader_sp);
+
+            result.SetStatus (eReturnStatusSuccessFinishNoResult);
+        }
+        else
+        {
+            result.AppendError ("invalid target");
+            result.SetStatus (eReturnStatusFailed);
+        }
+        
+        return result.Succeeded();
+    }
+private:
+    CommandOptions m_options;
+};
+
+lldb::OptionDefinition
+CommandObjectTargetStopHookAdd::CommandOptions::g_option_table[] =
+{
+    { LLDB_OPT_SET_ALL, false, "shlib", 's', required_argument, NULL, CommandCompletions::eModuleCompletion, eArgTypeShlibName,
+        "Set the module within which the stop-hook is to be run."},
+    { LLDB_OPT_SET_ALL, false, "thread-index", 'x', required_argument, NULL, NULL, eArgTypeThreadIndex,
+        "The stop hook is run only for the thread whose index matches this argument."},
+    { LLDB_OPT_SET_ALL, false, "thread-id", 't', required_argument, NULL, NULL, eArgTypeThreadID,
+        "The stop hook is run only for the thread whose TID matches this argument."},
+    { LLDB_OPT_SET_ALL, false, "thread-name", 'T', required_argument, NULL, NULL, eArgTypeThreadName,
+        "The stop hook is run only for the thread whose thread name matches this argument."},
+    { LLDB_OPT_SET_ALL, false, "queue-name", 'q', required_argument, NULL, NULL, eArgTypeQueueName,
+        "The stop hook is run only for threads in the queue whose name is given by this argument."},
+    { LLDB_OPT_SET_1, false, "file", 'f', required_argument, NULL, CommandCompletions::eSourceFileCompletion, eArgTypeFilename,
+        "Specify the source file within which the stop-hook is to be run." },
+    { LLDB_OPT_SET_1, false, "start-line", 'l', required_argument, NULL, 0, eArgTypeLineNum,
+        "Set the start of the line range for which the stop-hook is to be run."},
+    { LLDB_OPT_SET_1, false, "end-line", 'e', required_argument, NULL, 0, eArgTypeLineNum,
+        "Set the end of the line range for which the stop-hook is to be run."},
+    { LLDB_OPT_SET_2, false, "classname", 'c', required_argument, NULL, NULL, eArgTypeClassName,
+        "Specify the class within which the stop-hook is to be run." },
+    { LLDB_OPT_SET_3, false, "name", 'n', required_argument, NULL, CommandCompletions::eSymbolCompletion, eArgTypeFunctionName,
+        "Set the function name within which the stop hook will be run." },
+    { 0, false, NULL, 0, 0, NULL, 0, eArgTypeNone, NULL }
+};
+
+#pragma mark CommandObjectTargetStopHookDelete
+
+//-------------------------------------------------------------------------
+// CommandObjectTargetStopHookDelete
+//-------------------------------------------------------------------------
+
+class CommandObjectTargetStopHookDelete : public CommandObject
+{
+public:
+
+    CommandObjectTargetStopHookDelete (CommandInterpreter &interpreter) :
+        CommandObject (interpreter,
+                       "target stop-hook delete [<id>]",
+                       "Delete a stop-hook.",
+                       "target stop-hook delete")
+    {
+    }
+
+    ~CommandObjectTargetStopHookDelete ()
+    {
+    }
+
+    bool
+    Execute (Args& command,
+             CommandReturnObject &result)
+    {
+        Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get();
+        if (target)
+        {
+            // FIXME: see if we can use the breakpoint id style parser?
+            size_t num_args = command.GetArgumentCount();
+            if (num_args == 0)
+            {
+                if (!m_interpreter.Confirm ("Delete all stop hooks?", true))
+                {
+                    result.SetStatus (eReturnStatusFailed);
+                    return false;
+                }
+                else
+                {
+                    target->RemoveAllStopHooks();
+                }
+            }
+            else
+            {
+                bool success;
+                for (size_t i = 0; i < num_args; i++)
+                {
+                    lldb::user_id_t user_id = Args::StringToUInt32 (command.GetArgumentAtIndex(i), 0, 0, &success);
+                    if (!success)
+                    {
+                        result.AppendErrorWithFormat ("invalid stop hook id: \"%s\".", command.GetArgumentAtIndex(i));
+                        result.SetStatus(eReturnStatusFailed);
+                        return false;
+                    }
+                    success = target->RemoveStopHookByID (user_id);
+                    if (!success)
+                    {
+                        result.AppendErrorWithFormat ("unknown stop hook id: \"%s\".", command.GetArgumentAtIndex(i));
+                        result.SetStatus(eReturnStatusFailed);
+                        return false;
+                    }
+                }
+            }
+            result.SetStatus (eReturnStatusSuccessFinishNoResult);
+        }
+        else
+        {
+            result.AppendError ("invalid target");
+            result.SetStatus (eReturnStatusFailed);
+        }
+        
+        return result.Succeeded();
+    }
+};
+#pragma mark CommandObjectTargetStopHookEnableDisable
+
+//-------------------------------------------------------------------------
+// CommandObjectTargetStopHookEnableDisable
+//-------------------------------------------------------------------------
+
+class CommandObjectTargetStopHookEnableDisable : public CommandObject
+{
+public:
+
+    CommandObjectTargetStopHookEnableDisable (CommandInterpreter &interpreter, bool enable, const char *name, const char *help, const char *syntax) :
+        CommandObject (interpreter,
+                       name,
+                       help,
+                       syntax),
+        m_enable (enable)
+    {
+    }
+
+    ~CommandObjectTargetStopHookEnableDisable ()
+    {
+    }
+
+    bool
+    Execute (Args& command,
+             CommandReturnObject &result)
+    {
+        Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get();
+        if (target)
+        {
+            // FIXME: see if we can use the breakpoint id style parser?
+            size_t num_args = command.GetArgumentCount();
+            bool success;
+            
+            if (num_args == 0)
+            {
+                target->SetAllStopHooksActiveState (m_enable);
+            }
+            else
+            {
+                for (size_t i = 0; i < num_args; i++)
+                {
+                    lldb::user_id_t user_id = Args::StringToUInt32 (command.GetArgumentAtIndex(i), 0, 0, &success);
+                    if (!success)
+                    {
+                        result.AppendErrorWithFormat ("invalid stop hook id: \"%s\".", command.GetArgumentAtIndex(i));
+                        result.SetStatus(eReturnStatusFailed);
+                        return false;
+                    }
+                    success = target->SetStopHookActiveStateByID (user_id, m_enable);
+                    if (!success)
+                    {
+                        result.AppendErrorWithFormat ("unknown stop hook id: \"%s\".", command.GetArgumentAtIndex(i));
+                        result.SetStatus(eReturnStatusFailed);
+                        return false;
+                    }
+                }
+            }
+            result.SetStatus (eReturnStatusSuccessFinishNoResult);
+        }
+        else
+        {
+            result.AppendError ("invalid target");
+            result.SetStatus (eReturnStatusFailed);
+        }
+        return result.Succeeded();
+    }
+private:
+    bool m_enable;
+};
+
+#pragma mark CommandObjectTargetStopHookList
+
+//-------------------------------------------------------------------------
+// CommandObjectTargetStopHookList
+//-------------------------------------------------------------------------
+
+class CommandObjectTargetStopHookList : public CommandObject
+{
+public:
+
+    CommandObjectTargetStopHookList (CommandInterpreter &interpreter) :
+        CommandObject (interpreter,
+                       "target stop-hook list [<type>]",
+                       "List all stop-hooks.",
+                       "target stop-hook list")
+    {
+    }
+
+    ~CommandObjectTargetStopHookList ()
+    {
+    }
+
+    bool
+    Execute (Args& command,
+             CommandReturnObject &result)
+    {
+        Target *target = m_interpreter.GetDebugger().GetSelectedTarget().get();
+        if (target)
+        {
+            bool notify = true;
+            target->GetImageSearchPathList().Clear(notify);
+            result.SetStatus (eReturnStatusSuccessFinishNoResult);
+        }
+        else
+        {
+            result.AppendError ("invalid target");
+            result.SetStatus (eReturnStatusFailed);
+        }
+        
+        size_t num_hooks = target->GetNumStopHooks ();
+        if (num_hooks == 0)
+        {
+            result.GetOutputStream().PutCString ("No stop hooks.\n");
+        }
+        else
+        {
+            for (size_t i = 0; i < num_hooks; i++)
+            {
+                Target::StopHookSP this_hook = target->GetStopHookAtIndex (i);
+                if (i > 0)
+                    result.GetOutputStream().PutCString ("\n");
+                this_hook->GetDescription (&(result.GetOutputStream()), eDescriptionLevelFull);
+            }
+        }
+        return result.Succeeded();
+    }
+};
+
+#pragma mark CommandObjectMultiwordTargetStopHooks
+//-------------------------------------------------------------------------
+// CommandObjectMultiwordTargetStopHooks
+//-------------------------------------------------------------------------
+
+class CommandObjectMultiwordTargetStopHooks : public CommandObjectMultiword
+{
+public:
+
+    CommandObjectMultiwordTargetStopHooks (CommandInterpreter &interpreter) :
+        CommandObjectMultiword (interpreter, 
+                                "target stop-hook",
+                                "A set of commands for operating on debugger target stop-hooks.",
+                                "target stop-hook <subcommand> [<subcommand-options>]")
+    {
+        LoadSubCommand ("add",      CommandObjectSP (new CommandObjectTargetStopHookAdd (interpreter)));
+        LoadSubCommand ("delete",   CommandObjectSP (new CommandObjectTargetStopHookDelete (interpreter)));
+        LoadSubCommand ("disable",  CommandObjectSP (new CommandObjectTargetStopHookEnableDisable (interpreter, 
+                                                                                                   false, 
+                                                                                                   "target stop-hook disable [<id>]",
+                                                                                                   "Disable a stop-hook.",
+                                                                                                   "target stop-hook disable")));
+        LoadSubCommand ("enable",   CommandObjectSP (new CommandObjectTargetStopHookEnableDisable (interpreter, 
+                                                                                                   true, 
+                                                                                                   "target stop-hook enable [<id>]",
+                                                                                                   "Enable a stop-hook.",
+                                                                                                   "target stop-hook enable")));
+        LoadSubCommand ("list",     CommandObjectSP (new CommandObjectTargetStopHookList (interpreter)));
+    }
+
+    ~CommandObjectMultiwordTargetStopHooks()
+    {
+    }
+};
+
+
 
 #pragma mark CommandObjectMultiwordTarget
 
@@ -489,6 +1104,7 @@
                             "target <subcommand> [<subcommand-options>]")
 {
     LoadSubCommand ("image-search-paths", CommandObjectSP (new CommandObjectMultiwordImageSearchPaths (interpreter)));
+    LoadSubCommand ("stop-hook", CommandObjectSP (new CommandObjectMultiwordTargetStopHooks (interpreter)));
 }
 
 CommandObjectMultiwordTarget::~CommandObjectMultiwordTarget ()