[Lldb-commits] [lldb] 72ba78c - When SendContinuePacketAndWaitForResponse returns eStateInvalid, don't fetch more packets.

Jim Ingham via lldb-commits lldb-commits at lists.llvm.org
Thu May 6 14:12:36 PDT 2021


Author: Jim Ingham
Date: 2021-05-06T14:11:42-07:00
New Revision: 72ba78c29e923cb6e5d89eb5ea8180bf723188be

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

LOG: When SendContinuePacketAndWaitForResponse returns eStateInvalid, don't fetch more packets.

This looks like just an oversight in the AsyncThread function.  It gets a result of
eStateInvalid, and then marks the process as exited, but doesn't set "done" to true,
so we go to fetch another event.  That is not safe, since you don't know when that
extra packet is going to arrive.  If it arrives while you are tearing down the
process, the internal-state-thread might try to handle it when the process in not
in a good state.

Rather than put more effort into checking all the shutdown paths to make sure this
extra packet doesn't cause problems, just don't fetch it.  We weren't going to do
anything useful with it anyway.

The main part of the patch is setting "done = true" when we get the eStateInvalid.
I also added a check at the beginning of the while(done) loop to prevent another error
from getting us to fetch packets for an exited process.

I added a test case to ensure that if an Interrupt fails, we call the process
exited.  I can't test exactly the error I'm fixing, there's no good way to know
that the stop reply for the failed interrupt wasn't fetched.  But at least this
asserts that the overall behavior is correct.

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

Added: 
    lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py

Modified: 
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 5ff00f8566990..f8e0567aeddb3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -3689,12 +3689,25 @@ thread_result_t ProcessGDBRemote::AsyncThread(void *arg) {
             __FUNCTION__, arg, process->GetID());
 
   EventSP event_sp;
+
+  // We need to ignore any packets that come in after we have
+  // have decided the process has exited.  There are some
+  // situations, for instance when we try to interrupt a running
+  // process and the interrupt fails, where another packet might
+  // get delivered after we've decided to give up on the process.
+  // But once we've decided we are done with the process we will
+  // not be in a state to do anything useful with new packets.
+  // So it is safer to simply ignore any remaining packets by
+  // explicitly checking for eStateExited before reentering the
+  // fetch loop.
+  
   bool done = false;
-  while (!done) {
+  while (!done && process->GetPrivateState() != eStateExited) {
     LLDB_LOGF(log,
               "ProcessGDBRemote::%s (arg = %p, pid = %" PRIu64
               ") listener.WaitForEvent (NULL, event_sp)...",
               __FUNCTION__, arg, process->GetID());
+
     if (process->m_async_listener_sp->GetEvent(event_sp, llvm::None)) {
       const uint32_t event_type = event_sp->GetType();
       if (event_sp->BroadcasterIs(&process->m_async_broadcaster)) {
@@ -3793,6 +3806,7 @@ thread_result_t ProcessGDBRemote::AsyncThread(void *arg) {
                 } else {
                   process->SetExitStatus(-1, "lost connection");
                 }
+                done = true;
                 break;
               }
 

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py b/lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py
new file mode 100644
index 0000000000000..9f8e39e8ecca5
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py
@@ -0,0 +1,72 @@
+from __future__ import print_function
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from gdbclientutils import *
+
+
+class TestHaltFails(GDBRemoteTestBase):
+
+    class MyResponder(MockGDBServerResponder):
+        
+        def setBreakpoint(self, packet):
+            return "OK"
+        
+        def interrupt(self):
+            # Simulate process waiting longer than the interrupt
+            # timeout to stop, then sending the reply.
+            time.sleep(14)
+            return "T02reason:signal"
+        
+        def cont(self):
+            # No response, wait for the client to interrupt us.
+            return None
+        
+    def wait_for_and_check_event(self, wait_time, value):
+        event = lldb.SBEvent()
+        got_event = self.dbg.GetListener().WaitForEvent(wait_time, event)
+        self.assertTrue(got_event, "Failed to get event after wait")
+        self.assertTrue(lldb.SBProcess.EventIsProcessEvent(event), "Event was not a process event")
+        event_type = lldb.SBProcess.GetStateFromEvent(event)
+        self.assertEqual(event_type, value)
+        
+    def get_to_running(self):
+        self.server.responder = self.MyResponder()
+        self.target = self.createTarget("a.yaml")
+        process = self.connect(self.target)
+        self.dbg.SetAsync(True)
+
+        # There should be a stopped event, consume that:
+        self.wait_for_and_check_event(2, lldb.eStateStopped)
+        process.Continue()
+
+        # There should be a running event, consume that:
+        self.wait_for_and_check_event(2, lldb.eStateRunning)
+        return process
+
+    @skipIfReproducer # FIXME: Unexpected packet during (passive) replay
+    def test_destroy_while_running(self):
+        process = self.get_to_running()
+        process.Destroy()
+
+        # Again pretend that after failing to be interrupted, we delivered the stop
+        # and make sure we still exit properly.
+        self.wait_for_and_check_event(14, lldb.eStateExited)
+            
+    @skipIfReproducer # FIXME: Unexpected packet during (passive) replay
+    def test_async_interrupt(self):
+        """
+        Test that explicitly calling AsyncInterrupt, which then fails, leads
+        to an "eStateExited" state.
+        """
+        process = self.get_to_running()
+        # Now do the interrupt:
+        process.SendAsyncInterrupt()
+
+        # That should have caused the Halt to time out and we should
+        # be in eStateExited:
+        self.wait_for_and_check_event(15, lldb.eStateExited)
+
+        
+
+        


        


More information about the lldb-commits mailing list