diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -3689,12 +3689,25 @@ __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 @@ } 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 --- /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) + + + +