jingham created this revision.
jingham added a reviewer: JDevlieghere.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.
jingham requested review of this revision.

stop-hooks were not intended to be interactive, that would be really confusing 
when there are many in flight.  So this patch marks the results as 
non-interactive.

Beyond correctness, then main reason for doing this patch is that when you go 
to run a command based stop hook that includes a Python command, if the command 
is marked as interactive, Python would try to act on stdin (fflush it) when 
switching to the Python Session, and if stdin was backed by an lldb NativeFile, 
the the fflush would deadlock against the I/O handler thread that was sitting 
in read on the same file handle.

That is a more general problem that this patch does not address.  The only 
place it has shown up IRL is in this scenario, and since doing the right thing 
for the stop-hook execution works around it, the more general fix can wait on 
somebody else's free time.  But I did add a test that will deadlock without 
this patch, and succeeds with it.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D90332

Files:
  lldb/packages/Python/lldbsuite/test/lldbtest.py
  lldb/source/Interpreter/CommandInterpreter.cpp
  lldb/source/Target/Target.cpp
  lldb/test/API/api/multithreaded/TestMultithreaded.py
  lldb/test/API/api/multithreaded/some_cmd.py
  lldb/test/API/api/multithreaded/test_stop-hook.cpp.template

Index: lldb/test/API/api/multithreaded/test_stop-hook.cpp.template
===================================================================
--- /dev/null
+++ lldb/test/API/api/multithreaded/test_stop-hook.cpp.template
@@ -0,0 +1,130 @@
+// LLDB C++ API Test: Verify that when the Debugger stdin
+// is set to a FILE *, lldb can still successfully run a
+// python command in a stop hook.
+
+#include <errno.h>
+#include <mutex>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+%include_SB_APIs%
+
+#include "common.h"
+
+using namespace lldb;
+
+void test(SBDebugger &dbg, std::vector<std::string> args) {
+  // The problem we had was that when the thread that was
+  // waiting on input went into the call to 'read' it had
+  // the file handle lock.  Then when the python interpreter
+  // Initialized itself to run the python command, it tried
+  // to flush the file channel, and that deadlocked.
+  // This only happens when Async is true, since otherwise
+  // the process event is handled on the I/O read thread,
+  // which sidestepped the problem.
+  dbg.SetAsync(true);
+
+  SBTarget target = dbg.CreateTarget(args.at(0).c_str());
+  if (!target.IsValid())
+    throw Exception("invalid target");
+
+  SBBreakpoint breakpoint = target.BreakpointCreateByName("next");
+  if (!breakpoint.IsValid())
+    throw Exception("invalid breakpoint");
+
+  SBCommandInterpreter interp = dbg.GetCommandInterpreter();
+  SBCommandReturnObject result;
+
+  // Bring in the python command. We actually add two commands,
+  // one that runs in the stop hook and sets a variable when it
+  // runs, and one that reports out the variable so we can ensure
+  // that we did indeed run the stop hook.
+  const char *source_dir = "%SOURCE_DIR%";
+  SBFileSpec script_spec(source_dir);
+  script_spec.AppendPathComponent("some_cmd.py");
+  char path[PATH_MAX];
+  script_spec.GetPath(path, PATH_MAX);
+
+  std::string import_command("command script import ");
+  import_command.append(path);
+  interp.HandleCommand(import_command.c_str(), result);
+  if (!result.Succeeded())
+    throw Exception("Couldn't import %SOURCE_DIR%/some_cmd.py");
+
+  SBProcess process = target.LaunchSimple(nullptr, nullptr, nullptr);
+  if (!process.IsValid())
+    throw Exception("Couldn't launch process.");
+  if (process.GetState() != lldb::eStateStopped)
+    throw Exception("Process was not stopped");
+
+  process.SetSelectedThreadByIndexID(0);
+
+  // Now add the stop hook:
+  interp.HandleCommand("target stop-hook add -o some-cmd", result);
+  if (!result.Succeeded())
+    throw Exception("Couldn't add a stop hook.");
+
+  // Now switch the I/O over to a pipe, which will be handled by the
+  // NativeFile class:
+  int to_lldb_des[2];
+  int pipe_result = pipe(to_lldb_des);
+  FILE *fh_lldb_in = fdopen(to_lldb_des[0], "r");
+  FILE *fh_to_lldb = fdopen(to_lldb_des[1], "w");
+
+  // We need to reset the handle before destroying the debugger
+  // or the same deadlock will stall exiting:
+  class Cleanup {
+  public:
+    Cleanup(SBDebugger dbg, int filedes[2]) : m_dbg(dbg) {
+      m_file = m_dbg.GetInputFileHandle();
+      m_filedes[0] = filedes[0];
+      m_filedes[1] = filedes[1];
+    }
+    ~Cleanup() {
+      m_dbg.SetInputFileHandle(m_file, false);
+      close(m_filedes[0]);
+      close(m_filedes[1]);
+    }
+
+  private:
+    FILE *m_file;
+    SBDebugger m_dbg;
+    int m_filedes[2];
+  };
+  Cleanup cleanup(dbg, to_lldb_des);
+
+  dbg.SetInputFileHandle(fh_lldb_in, false);
+
+  // Now run the command interpreter.  You have to pass true to
+  // start thread so we will run the I/O in a separate thread.
+  dbg.RunCommandInterpreter(false, true);
+
+  // Now issue a stepi, and fetch the running and stopped events:
+  fprintf(fh_to_lldb, "thread step-inst\n");
+
+  SBEvent proc_event;
+  StateType state;
+  bool got_event;
+
+  got_event = dbg.GetListener().WaitForEventForBroadcaster(
+      100, process.GetBroadcaster(), proc_event);
+  if (!got_event)
+    throw Exception("Didn't get running event");
+  state = SBProcess::GetStateFromEvent(proc_event);
+  if (state != eStateRunning)
+    throw Exception("Event wasn't a running event.");
+
+  got_event = dbg.GetListener().WaitForEventForBroadcaster(
+      100, process.GetBroadcaster(), proc_event);
+  if (!got_event)
+    throw Exception("Didn't get a stopped event");
+  state = SBProcess::GetStateFromEvent(proc_event);
+  if (state != eStateStopped)
+    throw Exception("Event wasn't a stop event.");
+
+  // At this point the stop hook should have run.  Check that:
+  interp.HandleCommand("report-cmd", result);
+  if (!result.Succeeded())
+    throw Exception("Didn't actually call stop hook.");
+}
Index: lldb/test/API/api/multithreaded/some_cmd.py
===================================================================
--- /dev/null
+++ lldb/test/API/api/multithreaded/some_cmd.py
@@ -0,0 +1,33 @@
+""" Test command for checking the Python commands can run in a stop-hook """
+import lldb
+
+did_run = False
+
+class SomeCommand:
+    def __init__(self, debugger, unused):
+        self.dbg = debugger
+    def __call__(self, debugger, command, exe_ctx, result):
+        global did_run
+        did_run = True
+        result.PutCString("some output\n")
+
+    def get_short_help(self):
+        return "Test command - sets a variable."
+
+class OtherCommand:
+    def __init__(self, debugger, unused):
+        self.dbg = debugger
+    def __call__(self, debugger, command, exe_ctx, result):
+        global did_run
+        if did_run:
+            result.SetStatus(lldb.eReturnStatusSuccessFinishNoResult)
+        else:
+            result.SetStatus(lldb.eReturnStatusFailed)
+
+    def get_short_help(self):
+        return "Test command - sets a variable."
+
+def __lldb_init_module(debugger, unused):
+    print("Adding command some-cmd and report-cmd")
+    debugger.HandleCommand("command script add -c some_cmd.SomeCommand some-cmd")
+    debugger.HandleCommand("command script add -c some_cmd.OtherCommand report-cmd")
Index: lldb/test/API/api/multithreaded/TestMultithreaded.py
===================================================================
--- lldb/test/API/api/multithreaded/TestMultithreaded.py
+++ lldb/test/API/api/multithreaded/TestMultithreaded.py
@@ -23,9 +23,19 @@
         self.generateSource('test_listener_event_description.cpp')
         self.generateSource('test_listener_event_process_state.cpp')
         self.generateSource('test_listener_resume.cpp')
+        self.generateSource('test_stop-hook.cpp')
 
     mydir = TestBase.compute_mydir(__file__)
 
+    @skipIfRemote
+    @skipIfNoSBHeaders
+    # clang-cl does not support throw or catch (llvm.org/pr24538)
+    @skipIfWindows
+    def test_python_stop_hook(self):
+        """Test that you can run a python command in a stop-hook when stdin is File based. """
+        self.build_and_test('driver.cpp test_stop-hook.cpp',
+                            'test_python_stop_hook')
+
     @skipIfRemote
     @skipIfNoSBHeaders
     # clang-cl does not support throw or catch (llvm.org/pr24538)
Index: lldb/source/Target/Target.cpp
===================================================================
--- lldb/source/Target/Target.cpp
+++ lldb/source/Target/Target.cpp
@@ -3286,6 +3286,7 @@
 
   CommandReturnObject result(false);
   result.SetImmediateOutputStream(output_sp);
+  result.SetInteractive(false);
   Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger();
   CommandInterpreterRunOptions options;
   options.SetStopOnContinue(true);
Index: lldb/source/Interpreter/CommandInterpreter.cpp
===================================================================
--- lldb/source/Interpreter/CommandInterpreter.cpp
+++ lldb/source/Interpreter/CommandInterpreter.cpp
@@ -2307,6 +2307,8 @@
     }
 
     CommandReturnObject tmp_result(m_debugger.GetUseColor());
+    tmp_result.SetInteractive(result.GetInteractive());
+
     // If override_context is not NULL, pass no_context_switching = true for
     // HandleCommand() since we updated our context already.
 
Index: lldb/packages/Python/lldbsuite/test/lldbtest.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1927,6 +1927,7 @@
             header.startswith("SB") and header.endswith(".h"))]
         includes = '\n'.join(list)
         new_content = content.replace('%include_SB_APIs%', includes)
+        new_content = new_content.replace('%SOURCE_DIR%', self.getSourceDir())
         src = os.path.join(self.getBuildDir(), source)
         with open(src, 'w') as f:
             f.write(new_content)
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
  • [Lldb-commits] [PATCH] D90332:... Jim Ingham via Phabricator via lldb-commits

Reply via email to