jingham created this revision. jingham added reviewers: clayborg, JDevlieghere, labath. jingham requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
When a gdb-remote plugin process doesn't respond to an interrupt, it returns from SendContinuePacketAndWaitForResponse with eStateInvalid. The AsyncThread code responds to this return by setting the state to eStateExited. But it doesn't immediately exit the ProcessGDBRemote::AsyncThread's packet fetch loop. This looks like just an oversight in the AsyncThread function. It's important not to go back to wait for another packet. 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. We weren't going to do anything useful with the extra packet anyway, we've already declared the process exited. So rather than put more checks into all the shutdown paths to make sure this extra packet doesn't cause problems, just don't fetch it. 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 also 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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D101933 Files: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py
Index: lldb/test/API/functionalities/gdb_remote_client/TestHaltFails.py =================================================================== --- /dev/null +++ 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) + + + + Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -3695,6 +3695,11 @@ "ProcessGDBRemote::%s (arg = %p, pid = %" PRIu64 ") listener.WaitForEvent (NULL, event_sp)...", __FUNCTION__, arg, process->GetID()); + // If the process has exited, don't try to fetch more events from the + // server for it. We wouldn't be in a correct state to handle them. + if (process->GetPrivateState() == eStateExited) + break; + 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 +3798,7 @@ } else { process->SetExitStatus(-1, "lost connection"); } + done = true; break; }
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits