[Lldb-commits] [lldb] r315037 - Implement interactive command interruption

Leonard Mosescu via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 5 16:41:28 PDT 2017


Author: lemo
Date: Thu Oct  5 16:41:28 2017
New Revision: 315037

URL: http://llvm.org/viewvc/llvm-project?rev=315037&view=rev
Log:
Implement interactive command interruption

The core of this change is the new CommandInterpreter::m_command_state,
which models the state transitions for interactive commands, including
an "interrupted" state transition.

In general, command interruption requires cooperation from the code
executing the command, which needs to poll for interruption requests
through CommandInterpreter::WasInterrupted().

CommandInterpreter::PrintCommandOutput() implements an optionally
interruptible printing of the command output, which for large outputs 
was likely the longest blocking part.
(ex. target modules dump symtab on a complex binary could take 10+ minutes)

Differential Revision: https://reviews.llvm.org/D37923


Added:
    lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/commands.txt
Modified:
    lldb/trunk/include/lldb/API/SBCommandInterpreter.h
    lldb/trunk/include/lldb/Core/IOHandler.h
    lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h
    lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/.lldb
    lldb/trunk/scripts/interface/SBCommandInterpreter.i
    lldb/trunk/source/API/SBCommandInterpreter.cpp
    lldb/trunk/source/Commands/CommandObjectTarget.cpp
    lldb/trunk/source/Core/Debugger.cpp
    lldb/trunk/source/Interpreter/CommandInterpreter.cpp

Modified: lldb/trunk/include/lldb/API/SBCommandInterpreter.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/API/SBCommandInterpreter.h?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/include/lldb/API/SBCommandInterpreter.h (original)
+++ lldb/trunk/include/lldb/API/SBCommandInterpreter.h Thu Oct  5 16:41:28 2017
@@ -165,6 +165,8 @@ public:
                        int match_start_point, int max_return_elements,
                        lldb::SBStringList &matches);
 
+  bool WasInterrupted() const;
+
   // Catch commands before they execute by registering a callback that will
   // get called when the command gets executed. This allows GUI or command
   // line interfaces to intercept a command and stop it from happening

Modified: lldb/trunk/include/lldb/Core/IOHandler.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Core/IOHandler.h?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Core/IOHandler.h (original)
+++ lldb/trunk/include/lldb/Core/IOHandler.h Thu Oct  5 16:41:28 2017
@@ -195,7 +195,7 @@ public:
   enum class Completion { None, LLDBCommand, Expression };
 
   IOHandlerDelegate(Completion completion = Completion::None)
-      : m_completion(completion), m_io_handler_done(false) {}
+      : m_completion(completion) {}
 
   virtual ~IOHandlerDelegate() = default;
 
@@ -296,7 +296,6 @@ public:
 
 protected:
   Completion m_completion; // Support for common builtin completions
-  bool m_io_handler_done;
 };
 
 //----------------------------------------------------------------------

Modified: lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h (original)
+++ lldb/trunk/include/lldb/Interpreter/CommandInterpreter.h Thu Oct  5 16:41:28 2017
@@ -242,6 +242,8 @@ public:
                      bool repeat_on_empty_command = true,
                      bool no_context_switching = false);
 
+  bool WasInterrupted() const;
+
   //------------------------------------------------------------------
   /// Execute a list of commands in sequence.
   ///
@@ -522,6 +524,25 @@ private:
                               StringList &commands_help,
                               CommandObject::CommandMap &command_map);
 
+  // An interruptible wrapper around the stream output
+  void PrintCommandOutput(Stream &stream, llvm::StringRef str);
+
+  // A very simple state machine which models the command handling transitions
+  enum class CommandHandlingState {
+    eIdle,
+    eInProgress,
+    eInterrupted,
+  };
+
+  std::atomic<CommandHandlingState> m_command_state{
+      CommandHandlingState::eIdle};
+
+  int m_iohandler_nesting_level = 0;
+
+  void StartHandlingCommand();
+  void FinishHandlingCommand();
+  bool InterruptCommand();
+
   Debugger &m_debugger; // The debugger session that this interpreter is
                         // associated with
   ExecutionContextRef m_exe_ctx_ref; // The current execution context to use

Modified: lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/.lldb
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/.lldb?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/.lldb (original)
+++ lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/.lldb Thu Oct  5 16:41:28 2017
@@ -1 +1,2 @@
-script import my
+# one more level of indirection to stress the command interpreter reentrancy
+command source commands.txt

Added: lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/commands.txt
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/commands.txt?rev=315037&view=auto
==============================================================================
--- lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/commands.txt (added)
+++ lldb/trunk/packages/Python/lldbsuite/test/functionalities/command_source/commands.txt Thu Oct  5 16:41:28 2017
@@ -0,0 +1,2 @@
+script import my
+p 1 + 1

Modified: lldb/trunk/scripts/interface/SBCommandInterpreter.i
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/scripts/interface/SBCommandInterpreter.i?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/scripts/interface/SBCommandInterpreter.i (original)
+++ lldb/trunk/scripts/interface/SBCommandInterpreter.i Thu Oct  5 16:41:28 2017
@@ -125,18 +125,18 @@ public:
     {
         eBroadcastBitThreadShouldExit       = (1 << 0),
         eBroadcastBitResetPrompt            = (1 << 1),
-        eBroadcastBitQuitCommandReceived    = (1 << 2),           // User entered quit 
+        eBroadcastBitQuitCommandReceived    = (1 << 2),           // User entered quit
         eBroadcastBitAsynchronousOutputData = (1 << 3),
         eBroadcastBitAsynchronousErrorData  = (1 << 4)
     };
 
     SBCommandInterpreter (const lldb::SBCommandInterpreter &rhs);
-    
+
     ~SBCommandInterpreter ();
 
-    static const char * 
+    static const char *
     GetArgumentTypeAsCString (const lldb::CommandArgumentType arg_type);
-    
+
     static const char *
     GetArgumentDescriptionAsCString (const lldb::CommandArgumentType arg_type);
 
@@ -181,7 +181,7 @@ public:
 
     lldb::SBProcess
     GetProcess ();
-    
+
     lldb::SBDebugger
     GetDebugger ();
 
@@ -209,10 +209,12 @@ public:
                       int match_start_point,
                       int max_return_elements,
                       lldb::SBStringList &matches);
-    
+
     bool
     IsActive ();
 
+    bool
+    WasInterrupted () const;
 };
 
 } // namespace lldb

Modified: lldb/trunk/source/API/SBCommandInterpreter.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/API/SBCommandInterpreter.cpp?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/source/API/SBCommandInterpreter.cpp (original)
+++ lldb/trunk/source/API/SBCommandInterpreter.cpp Thu Oct  5 16:41:28 2017
@@ -161,6 +161,10 @@ bool SBCommandInterpreter::IsActive() {
   return (IsValid() ? m_opaque_ptr->IsActive() : false);
 }
 
+bool SBCommandInterpreter::WasInterrupted() const {
+  return (IsValid() ? m_opaque_ptr->WasInterrupted() : false);
+}
+
 const char *SBCommandInterpreter::GetIOHandlerControlSequence(char ch) {
   return (IsValid()
               ? m_opaque_ptr->GetDebugger()

Modified: lldb/trunk/source/Commands/CommandObjectTarget.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandObjectTarget.cpp?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/source/Commands/CommandObjectTarget.cpp (original)
+++ lldb/trunk/source/Commands/CommandObjectTarget.cpp Thu Oct  5 16:41:28 2017
@@ -2053,6 +2053,8 @@ protected:
               result.GetOutputStream().EOL();
               result.GetOutputStream().EOL();
             }
+            if (m_interpreter.WasInterrupted())
+              break;
             num_dumped++;
             DumpModuleSymtab(
                 m_interpreter, result.GetOutputStream(),
@@ -2081,6 +2083,8 @@ protected:
                   result.GetOutputStream().EOL();
                   result.GetOutputStream().EOL();
                 }
+                if (m_interpreter.WasInterrupted())
+                  break;
                 num_dumped++;
                 DumpModuleSymtab(m_interpreter, result.GetOutputStream(),
                                  module, m_options.m_sort_order);
@@ -2146,6 +2150,8 @@ protected:
                                           " modules.\n",
                                           (uint64_t)num_modules);
           for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
+            if (m_interpreter.WasInterrupted())
+              break;
             num_dumped++;
             DumpModuleSections(
                 m_interpreter, result.GetOutputStream(),
@@ -2167,6 +2173,8 @@ protected:
               FindModulesByName(target, arg_cstr, module_list, true);
           if (num_matches > 0) {
             for (size_t i = 0; i < num_matches; ++i) {
+              if (m_interpreter.WasInterrupted())
+                break;
               Module *module = module_list.GetModulePointerAtIndex(i);
               if (module) {
                 num_dumped++;
@@ -2239,6 +2247,8 @@ protected:
                                           " modules.\n",
                                           (uint64_t)num_modules);
           for (uint32_t image_idx = 0; image_idx < num_modules; ++image_idx) {
+            if (m_interpreter.WasInterrupted())
+              break;
             if (DumpModuleSymbolVendor(
                     result.GetOutputStream(),
                     target_modules.GetModulePointerAtIndexUnlocked(image_idx)))
@@ -2260,6 +2270,8 @@ protected:
               FindModulesByName(target, arg_cstr, module_list, true);
           if (num_matches > 0) {
             for (size_t i = 0; i < num_matches; ++i) {
+              if (m_interpreter.WasInterrupted())
+                break;
               Module *module = module_list.GetModulePointerAtIndex(i);
               if (module) {
                 if (DumpModuleSymbolVendor(result.GetOutputStream(), module))
@@ -2327,6 +2339,8 @@ protected:
         if (num_modules > 0) {
           uint32_t num_dumped = 0;
           for (uint32_t i = 0; i < num_modules; ++i) {
+            if (m_interpreter.WasInterrupted())
+              break;
             if (DumpCompileUnitLineTable(
                     m_interpreter, result.GetOutputStream(),
                     target_modules.GetModulePointerAtIndexUnlocked(i),

Modified: lldb/trunk/source/Core/Debugger.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Core/Debugger.cpp?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/source/Core/Debugger.cpp (original)
+++ lldb/trunk/source/Core/Debugger.cpp Thu Oct  5 16:41:28 2017
@@ -1170,7 +1170,7 @@ TestPromptFormats (StackFrame *frame)
         return;
 
     StreamString s;
-    const char *prompt_format =         
+    const char *prompt_format =
     "{addr = '${addr}'\n}"
     "{addr-file-or-load = '${addr-file-or-load}'\n}"
     "{current-pc-arrow = '${current-pc-arrow}'\n}"

Modified: lldb/trunk/source/Interpreter/CommandInterpreter.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Interpreter/CommandInterpreter.cpp?rev=315037&r1=315036&r2=315037&view=diff
==============================================================================
--- lldb/trunk/source/Interpreter/CommandInterpreter.cpp (original)
+++ lldb/trunk/source/Interpreter/CommandInterpreter.cpp Thu Oct  5 16:41:28 2017
@@ -546,7 +546,7 @@ void CommandInterpreter::LoadCommandDict
       char buffer[1024];
       int num_printed =
           snprintf(buffer, 1024, "%s %s", break_regexes[i][1], "-o");
-      assert(num_printed < 1024);
+      lldbassert(num_printed < 1024);
       UNUSED_IF_ASSERT_DISABLED(num_printed);
       success =
           tbreak_regex_cmd_ap->AddRegexCommand(break_regexes[i][0], buffer);
@@ -891,8 +891,8 @@ bool CommandInterpreter::AddCommand(llvm
                                     const lldb::CommandObjectSP &cmd_sp,
                                     bool can_replace) {
   if (cmd_sp.get())
-    assert((this == &cmd_sp->GetCommandInterpreter()) &&
-           "tried to add a CommandObject from a different interpreter");
+    lldbassert((this == &cmd_sp->GetCommandInterpreter()) &&
+               "tried to add a CommandObject from a different interpreter");
 
   if (name.empty())
     return false;
@@ -913,8 +913,8 @@ bool CommandInterpreter::AddUserCommand(
                                         const lldb::CommandObjectSP &cmd_sp,
                                         bool can_replace) {
   if (cmd_sp.get())
-    assert((this == &cmd_sp->GetCommandInterpreter()) &&
-           "tried to add a CommandObject from a different interpreter");
+    lldbassert((this == &cmd_sp->GetCommandInterpreter()) &&
+               "tried to add a CommandObject from a different interpreter");
 
   if (!name.empty()) {
     // do not allow replacement of internal commands
@@ -1062,8 +1062,8 @@ CommandInterpreter::AddAlias(llvm::Strin
                              lldb::CommandObjectSP &command_obj_sp,
                              llvm::StringRef args_string) {
   if (command_obj_sp.get())
-    assert((this == &command_obj_sp->GetCommandInterpreter()) &&
-           "tried to add a CommandObject from a different interpreter");
+    lldbassert((this == &command_obj_sp->GetCommandInterpreter()) &&
+               "tried to add a CommandObject from a different interpreter");
 
   std::unique_ptr<CommandAlias> command_alias_up(
       new CommandAlias(*this, command_obj_sp, args_string, alias_name));
@@ -1541,6 +1541,12 @@ bool CommandInterpreter::HandleCommand(c
   if (!no_context_switching)
     UpdateExecutionContext(override_context);
 
+  if (WasInterrupted()) {
+    result.AppendError("interrupted");
+    result.SetStatus(eReturnStatusFailed);
+    return false;
+  }
+
   bool add_to_history;
   if (lazy_add_to_history == eLazyBoolCalculate)
     add_to_history = (m_command_source_depth == 0);
@@ -1839,7 +1845,7 @@ int CommandInterpreter::HandleCompletion
   matches.Clear();
 
   // Only max_return_elements == -1 is supported at present:
-  assert(max_return_elements == -1);
+  lldbassert(max_return_elements == -1);
   bool word_complete;
   num_command_matches = HandleCompletionMatches(
       parsed_line, cursor_index, cursor_char_position, match_start_point,
@@ -2210,7 +2216,7 @@ void CommandInterpreter::HandleCommands(
     m_debugger.SetAsyncExecution(false);
   }
 
-  for (size_t idx = 0; idx < num_lines; idx++) {
+  for (size_t idx = 0; idx < num_lines && !WasInterrupted(); idx++) {
     const char *cmd = commands.GetStringAtIndex(idx);
     if (cmd[0] == '\0')
       continue;
@@ -2677,8 +2683,67 @@ size_t CommandInterpreter::GetProcessOut
   return total_bytes;
 }
 
+void CommandInterpreter::StartHandlingCommand() {
+  auto idle_state = CommandHandlingState::eIdle;
+  if (m_command_state.compare_exchange_strong(
+          idle_state, CommandHandlingState::eInProgress))
+    lldbassert(m_iohandler_nesting_level == 0);
+  else
+    lldbassert(m_iohandler_nesting_level > 0);
+  ++m_iohandler_nesting_level;
+}
+
+void CommandInterpreter::FinishHandlingCommand() {
+  lldbassert(m_iohandler_nesting_level > 0);
+  if (--m_iohandler_nesting_level == 0) {
+    auto prev_state = m_command_state.exchange(CommandHandlingState::eIdle);
+    lldbassert(prev_state != CommandHandlingState::eIdle);
+  }
+}
+
+bool CommandInterpreter::InterruptCommand() {
+  auto in_progress = CommandHandlingState::eInProgress;
+  return m_command_state.compare_exchange_strong(
+      in_progress, CommandHandlingState::eInterrupted);
+}
+
+bool CommandInterpreter::WasInterrupted() const {
+  bool was_interrupted =
+      (m_command_state == CommandHandlingState::eInterrupted);
+  lldbassert(!was_interrupted || m_iohandler_nesting_level > 0);
+  return was_interrupted;
+}
+
+void CommandInterpreter::PrintCommandOutput(Stream &stream,
+                                            llvm::StringRef str) {
+  // Split the output into lines and poll for interrupt requests
+  const char *data = str.data();
+  size_t size = str.size();
+  while (size > 0 && !WasInterrupted()) {
+    size_t chunk_size = 0;
+    for (; chunk_size < size; ++chunk_size) {
+      lldbassert(data[chunk_size] != '\0');
+      if (data[chunk_size] == '\n') {
+        ++chunk_size;
+        break;
+      }
+    }
+    chunk_size = stream.Write(data, chunk_size);
+    lldbassert(size >= chunk_size);
+    data += chunk_size;
+    size -= chunk_size;
+  }
+  if (size > 0) {
+    stream.Printf("\n... Interrupted.\n");
+  }
+}
+
 void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
                                                 std::string &line) {
+    // If we were interrupted, bail out...
+    if (WasInterrupted())
+      return;
+
   const bool is_interactive = io_handler.GetIsInteractive();
   if (is_interactive == false) {
     // When we are not interactive, don't execute blank lines. This will happen
@@ -2700,6 +2765,8 @@ void CommandInterpreter::IOHandlerInputC
                                                line.c_str());
   }
 
+  StartHandlingCommand();
+
   lldb_private::CommandReturnObject result;
   HandleCommand(line.c_str(), eLazyBoolCalculate, result);
 
@@ -2710,18 +2777,18 @@ void CommandInterpreter::IOHandlerInputC
 
     if (!result.GetImmediateOutputStream()) {
       llvm::StringRef output = result.GetOutputData();
-      if (!output.empty())
-        io_handler.GetOutputStreamFile()->PutCString(output);
+      PrintCommandOutput(*io_handler.GetOutputStreamFile(), output);
     }
 
     // Now emit the command error text from the command we just executed
     if (!result.GetImmediateErrorStream()) {
       llvm::StringRef error = result.GetErrorData();
-      if (!error.empty())
-        io_handler.GetErrorStreamFile()->PutCString(error);
+      PrintCommandOutput(*io_handler.GetErrorStreamFile(), error);
     }
   }
 
+  FinishHandlingCommand();
+
   switch (result.GetStatus()) {
   case eReturnStatusInvalid:
   case eReturnStatusSuccessFinishNoResult:
@@ -2777,6 +2844,9 @@ bool CommandInterpreter::IOHandlerInterr
   ExecutionContext exe_ctx(GetExecutionContext());
   Process *process = exe_ctx.GetProcessPtr();
 
+  if (InterruptCommand())
+    return true;
+
   if (process) {
     StateType state = process->GetState();
     if (StateIsRunningState(state)) {
@@ -2998,7 +3068,7 @@ CommandInterpreter::ResolveCommandImpl(s
         result.AppendRawError(error_msg.GetString());
       } else {
         // We didn't have only one match, otherwise we wouldn't get here.
-        assert(num_matches == 0);
+        lldbassert(num_matches == 0);
         result.AppendErrorWithFormat("'%s' is not a valid command.\n",
                                      next_word.c_str());
       }




More information about the lldb-commits mailing list