[Lldb-commits] [lldb] dbbed97 - Handle the case where a thread exits while we are running a function on it.

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Thu May 21 17:56:06 PDT 2020


Author: Jim Ingham
Date: 2020-05-21T17:55:53-07:00
New Revision: dbbed971e3a282f44242297b75a527256eb862dc

URL: https://github.com/llvm/llvm-project/commit/dbbed971e3a282f44242297b75a527256eb862dc
DIFF: https://github.com/llvm/llvm-project/commit/dbbed971e3a282f44242297b75a527256eb862dc.diff

LOG: Handle the case where a thread exits while we are running a function on it.

Added: 
    lldb/test/API/functionalities/thread/exit_during_expression/Makefile
    lldb/test/API/functionalities/thread/exit_during_expression/TestExitDuringExpression.py
    lldb/test/API/functionalities/thread/exit_during_expression/main.c

Modified: 
    lldb/include/lldb/lldb-enumerations.h
    lldb/source/Expression/FunctionCaller.cpp
    lldb/source/Expression/LLVMUserExpression.cpp
    lldb/source/Target/Process.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index e5032a6cd97f..b3e8d604913f 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -269,7 +269,8 @@ enum ExpressionResults {
   eExpressionHitBreakpoint,
   eExpressionTimedOut,
   eExpressionResultUnavailable,
-  eExpressionStoppedForDebug
+  eExpressionStoppedForDebug,
+  eExpressionThreadVanished
 };
 
 enum SearchDepth {

diff  --git a/lldb/source/Expression/FunctionCaller.cpp b/lldb/source/Expression/FunctionCaller.cpp
index f4b02268fd1a..26ab4bfaff53 100644
--- a/lldb/source/Expression/FunctionCaller.cpp
+++ b/lldb/source/Expression/FunctionCaller.cpp
@@ -364,8 +364,9 @@ lldb::ExpressionResults FunctionCaller::ExecuteFunction(
     if (return_value != lldb::eExpressionCompleted) {
       LLDB_LOGF(log,
                 "== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "
-                "completed abnormally ==",
-                m_name.c_str());
+                "completed abnormally: %s ==",
+                m_name.c_str(),
+                Process::ExecutionResultAsCString(return_value));
     } else {
       LLDB_LOGF(log,
                 "== [FunctionCaller::ExecuteFunction] Execution of \"%s\" "

diff  --git a/lldb/source/Expression/LLVMUserExpression.cpp b/lldb/source/Expression/LLVMUserExpression.cpp
index d7958a50f9bb..187b427e66aa 100644
--- a/lldb/source/Expression/LLVMUserExpression.cpp
+++ b/lldb/source/Expression/LLVMUserExpression.cpp
@@ -134,6 +134,10 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
       return lldb::eExpressionSetupError;
     }
 
+    // Store away the thread ID for error reporting, in case it exits
+    // during execution:
+    lldb::tid_t expr_thread_id = exe_ctx.GetThreadRef().GetID();
+
     Address wrapper_address(m_jit_start_addr);
 
     std::vector<lldb::addr_t> args;
@@ -223,6 +227,14 @@ LLVMUserExpression::DoExecute(DiagnosticManager &diagnostic_manager,
           "Use \"thread return -x\" to return to the state before expression "
           "evaluation.");
       return execution_result;
+    } else if (execution_result == lldb::eExpressionThreadVanished) {
+      diagnostic_manager.Printf(
+          eDiagnosticSeverityError,
+          "Couldn't complete execution; the thread "
+          "on which the expression was being run: 0x%" PRIx64
+          " exited during its execution.", 
+          expr_thread_id);
+      return execution_result;
     } else if (execution_result != lldb::eExpressionCompleted) {
       diagnostic_manager.Printf(
           eDiagnosticSeverityError, "Couldn't execute function; result was %s",

diff  --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 7bd53af985d4..0eb9866e4a4b 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -4652,13 +4652,27 @@ GetExpressionTimeout(const EvaluateExpressionOptions &options,
 }
 
 static llvm::Optional<ExpressionResults>
-HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
+HandleStoppedEvent(lldb::tid_t thread_id, const ThreadPlanSP &thread_plan_sp,
                    RestorePlanState &restorer, const EventSP &event_sp,
                    EventSP &event_to_broadcast_sp,
-                   const EvaluateExpressionOptions &options, bool handle_interrupts) {
+                   const EvaluateExpressionOptions &options,
+                   bool handle_interrupts) {
   Log *log = GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STEP | LIBLLDB_LOG_PROCESS);
 
-  ThreadPlanSP plan = thread.GetCompletedPlan();
+  ThreadSP thread_sp = thread_plan_sp->GetTarget()
+                           .GetProcessSP()
+                           ->GetThreadList()
+                           .FindThreadByID(thread_id);
+  if (!thread_sp) {
+    LLDB_LOG(log,
+             "The thread on which we were running the "
+             "expression: tid = {0}, exited while "
+             "the expression was running.",
+             thread_id);
+    return eExpressionThreadVanished;
+  }
+
+  ThreadPlanSP plan = thread_sp->GetCompletedPlan();
   if (plan == thread_plan_sp && plan->PlanSucceeded()) {
     LLDB_LOG(log, "execution completed successfully");
 
@@ -4668,7 +4682,7 @@ HandleStoppedEvent(Thread &thread, const ThreadPlanSP &thread_plan_sp,
     return eExpressionCompleted;
   }
 
-  StopInfoSP stop_info_sp = thread.GetStopInfo();
+  StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
   if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint &&
       stop_info_sp->ShouldNotify(event_sp.get())) {
     LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription());
@@ -4730,6 +4744,10 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
     return eExpressionSetupError;
   }
 
+  // Record the thread's id so we can tell when a thread we were using
+  // to run the expression exits during the expression evaluation.
+  lldb::tid_t expr_thread_id = thread->GetID();
+
   // We need to change some of the thread plan attributes for the thread plan
   // runner.  This will restore them when we are done:
 
@@ -4874,7 +4892,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
       LLDB_LOGF(log,
                 "Process::RunThreadPlan(): Resuming thread %u - 0x%4.4" PRIx64
                 " to run thread plan \"%s\".",
-                thread->GetIndexID(), thread->GetID(), s.GetData());
+                thread_idx_id, expr_thread_id, s.GetData());
     }
 
     bool got_event;
@@ -5074,33 +5092,23 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
 
             switch (stop_state) {
             case lldb::eStateStopped: {
-              // We stopped, figure out what we are going to do now.
-              ThreadSP thread_sp =
-                  GetThreadList().FindThreadByIndexID(thread_idx_id);
-              if (!thread_sp) {
-                // Ooh, our thread has vanished.  Unlikely that this was
-                // successful execution...
-                LLDB_LOGF(log,
-                          "Process::RunThreadPlan(): execution completed "
-                          "but our thread (index-id=%u) has vanished.",
-                          thread_idx_id);
-                return_value = eExpressionInterrupted;
-              } else if (Process::ProcessEventData::GetRestartedFromEvent(
-                             event_sp.get())) {
+              if (Process::ProcessEventData::GetRestartedFromEvent(
+                      event_sp.get())) {
                 // If we were restarted, we just need to go back up to fetch
                 // another event.
-                if (log) {
-                  LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
-                                 "restart, so we'll continue waiting.");
-                }
+                LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and "
+                               "restart, so we'll continue waiting.");
                 keep_going = true;
                 do_resume = false;
                 handle_running_event = true;
               } else {
                 const bool handle_interrupts = true;
                 return_value = *HandleStoppedEvent(
-                    *thread, thread_plan_sp, thread_plan_restorer, event_sp,
-                    event_to_broadcast_sp, options, handle_interrupts);
+                    expr_thread_id, thread_plan_sp, thread_plan_restorer,
+                    event_sp, event_to_broadcast_sp, options,
+                    handle_interrupts);
+                if (return_value == eExpressionThreadVanished)
+                  keep_going = false;
               }
             } break;
 
@@ -5222,8 +5230,9 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
                 // job.  Check that here:
                 const bool handle_interrupts = false;
                 if (auto result = HandleStoppedEvent(
-                        *thread, thread_plan_sp, thread_plan_restorer, event_sp,
-                        event_to_broadcast_sp, options, handle_interrupts)) {
+                        expr_thread_id, thread_plan_sp, thread_plan_restorer,
+                        event_sp, event_to_broadcast_sp, options,
+                        handle_interrupts)) {
                   return_value = *result;
                   back_to_top = false;
                   break;
@@ -5295,6 +5304,13 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
         m_public_state.SetValueNoLock(old_state);
     }
 
+    // If our thread went away on us, we need to get out of here without
+    // doing any more work.  We don't have to clean up the thread plan, that
+    // will have happened when the Thread was destroyed.
+    if (return_value == eExpressionThreadVanished) {
+      return return_value;
+    }
+
     if (return_value != eExpressionCompleted && log) {
       // Print a backtrace into the log so we can figure out where we are:
       StreamString s;
@@ -5483,7 +5499,7 @@ Process::RunThreadPlan(ExecutionContext &exe_ctx,
 }
 
 const char *Process::ExecutionResultAsCString(ExpressionResults result) {
-  const char *result_name;
+  const char *result_name = "<unknown>";
 
   switch (result) {
   case eExpressionCompleted:
@@ -5513,6 +5529,8 @@ const char *Process::ExecutionResultAsCString(ExpressionResults result) {
   case eExpressionStoppedForDebug:
     result_name = "eExpressionStoppedForDebug";
     break;
+  case eExpressionThreadVanished:
+    result_name = "eExpressionThreadVanished";
   }
   return result_name;
 }

diff  --git a/lldb/test/API/functionalities/thread/exit_during_expression/Makefile b/lldb/test/API/functionalities/thread/exit_during_expression/Makefile
new file mode 100644
index 000000000000..695335e068c0
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/exit_during_expression/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -std=c99
+
+include Makefile.rules

diff  --git a/lldb/test/API/functionalities/thread/exit_during_expression/TestExitDuringExpression.py b/lldb/test/API/functionalities/thread/exit_during_expression/TestExitDuringExpression.py
new file mode 100644
index 000000000000..bfdfdf53cdb1
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/exit_during_expression/TestExitDuringExpression.py
@@ -0,0 +1,106 @@
+"""
+Make sure that we handle an expression on a thread, if
+the thread exits while the expression is running.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+
+class TestExitDuringExpression(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipIfWindows
+    def test_exit_before_one_thread_unwind(self):
+        """Test the case where we exit within the one thread timeout"""
+        self.exiting_expression_test(True, True)
+
+    @skipIfWindows
+    def test_exit_before_one_thread_no_unwind(self):
+        """Test the case where we exit within the one thread timeout"""
+        self.exiting_expression_test(True, False)
+
+    @skipIfWindows
+    def test_exit_after_one_thread_unwind(self):
+        """Test the case where we exit within the one thread timeout"""
+        self.exiting_expression_test(False, True)
+
+    @skipIfWindows
+    def test_exit_after_one_thread_no_unwind(self):
+        """Test the case where we exit within the one thread timeout"""
+        self.exiting_expression_test(False, False)
+    
+    def setUp(self):
+        TestBase.setUp(self)
+        self.main_source_file = lldb.SBFileSpec("main.c")
+        self.build()
+        
+    def exiting_expression_test(self, before_one_thread_timeout , unwind):
+        """function_to_call sleeps for g_timeout microseconds, then calls pthread_exit.
+           This test calls function_to_call with an overall timeout of 500
+           microseconds, and a one_thread_timeout as passed in.
+           It also sets unwind_on_exit for the call to the unwind passed in.
+           This allows you to have the thread exit either before the one thread
+           timeout is passed. """
+        
+        (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
+                                   "Break here and cause the thread to exit", self.main_source_file)
+
+        # We'll continue to this breakpoint after running our expression:
+        return_bkpt = target.BreakpointCreateBySourceRegex("Break here to make sure the thread exited", self.main_source_file)
+        frame = thread.frames[0]
+        tid = thread.GetThreadID()
+        # Find the timeout:
+        var_options = lldb.SBVariablesOptions()
+        var_options.SetIncludeArguments(False)
+        var_options.SetIncludeLocals(False)
+        var_options.SetIncludeStatics(True)
+        
+        value_list = frame.GetVariables(var_options)
+        g_timeout = value_list.GetFirstValueByName("g_timeout")
+        self.assertTrue(g_timeout.IsValid(), "Found g_timeout")
+        
+        error = lldb.SBError()
+        timeout_value = g_timeout.GetValueAsUnsigned(error)
+        self.assertTrue(error.Success(), "Couldn't get timeout value: %s"%(error.GetCString()))
+        
+        one_thread_timeout = 0
+        if (before_one_thread_timeout):
+            one_thread_timeout = timeout_value * 2
+        else:
+            one_thread_timeout = int(timeout_value / 2)
+
+        options = lldb.SBExpressionOptions()
+        options.SetUnwindOnError(unwind)
+        options.SetOneThreadTimeoutInMicroSeconds(one_thread_timeout)
+        options.SetTimeoutInMicroSeconds(4 * timeout_value)
+            
+        result = frame.EvaluateExpression("function_to_call()", options)
+
+        # Make sure the thread actually exited:
+        thread = process.GetThreadByID(tid)
+        self.assertFalse(thread.IsValid(), "The thread exited")
+
+        # Make sure the expression failed:
+        self.assertFalse(result.GetError().Success(), "Expression failed.")
+
+        # Make sure we can keep going:
+        threads = lldbutil.continue_to_breakpoint(process, return_bkpt)
+        if not threads:
+            self.fail("didn't get any threads back after continuing")
+
+        self.assertEqual(len(threads), 1, "One thread hit our breakpoint")
+        thread = threads[0]
+        frame = thread.frames[0]
+        # Now get the return value, if we successfully caused the thread to exit
+        # it should be 10, not 20.
+        ret_val = frame.FindVariable("ret_val")
+        self.assertTrue(ret_val.GetError().Success(), "Found ret_val")
+        ret_val_value = ret_val.GetValueAsSigned(error)
+        self.assertTrue(error.Success(), "Got ret_val's value")
+        self.assertEqual(ret_val_value, 10, "We put the right value in ret_val")
+        

diff  --git a/lldb/test/API/functionalities/thread/exit_during_expression/main.c b/lldb/test/API/functionalities/thread/exit_during_expression/main.c
new file mode 100644
index 000000000000..66b6018976cf
--- /dev/null
+++ b/lldb/test/API/functionalities/thread/exit_during_expression/main.c
@@ -0,0 +1,38 @@
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static unsigned int g_timeout = 200;
+
+int function_to_call() {
+
+  errno = 0;
+  while (1) {
+    int result = usleep(g_timeout);
+    if (errno != EINTR)
+      break;
+  }
+
+  pthread_exit((void *)10);
+
+  return 20; // Prevent warning
+}
+
+void *exiting_thread_func(void *unused) {
+  function_to_call(); // Break here and cause the thread to exit
+  return NULL;
+}
+
+int main() {
+  char *exit_ptr;
+  pthread_t exiting_thread;
+
+  pthread_create(&exiting_thread, NULL, exiting_thread_func, NULL);
+
+  pthread_join(exiting_thread, &exit_ptr);
+  int ret_val = (int)exit_ptr;
+  usleep(g_timeout * 4); // Make sure in the "run all threads" case
+                         // that we don't run past our breakpoint.
+  return ret_val;        // Break here to make sure the thread exited.
+}


        


More information about the lldb-commits mailing list