Skip to content

Commit 72ba78c

Browse files
committed
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
1 parent d21e1b7 commit 72ba78c

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3689,12 +3689,25 @@ thread_result_t ProcessGDBRemote::AsyncThread(void *arg) {
36893689
__FUNCTION__, arg, process->GetID());
36903690

36913691
EventSP event_sp;
3692+
3693+
// We need to ignore any packets that come in after we have
3694+
// have decided the process has exited. There are some
3695+
// situations, for instance when we try to interrupt a running
3696+
// process and the interrupt fails, where another packet might
3697+
// get delivered after we've decided to give up on the process.
3698+
// But once we've decided we are done with the process we will
3699+
// not be in a state to do anything useful with new packets.
3700+
// So it is safer to simply ignore any remaining packets by
3701+
// explicitly checking for eStateExited before reentering the
3702+
// fetch loop.
3703+
36923704
bool done = false;
3693-
while (!done) {
3705+
while (!done && process->GetPrivateState() != eStateExited) {
36943706
LLDB_LOGF(log,
36953707
"ProcessGDBRemote::%s (arg = %p, pid = %" PRIu64
36963708
") listener.WaitForEvent (NULL, event_sp)...",
36973709
__FUNCTION__, arg, process->GetID());
3710+
36983711
if (process->m_async_listener_sp->GetEvent(event_sp, llvm::None)) {
36993712
const uint32_t event_type = event_sp->GetType();
37003713
if (event_sp->BroadcasterIs(&process->m_async_broadcaster)) {
@@ -3793,6 +3806,7 @@ thread_result_t ProcessGDBRemote::AsyncThread(void *arg) {
37933806
} else {
37943807
process->SetExitStatus(-1, "lost connection");
37953808
}
3809+
done = true;
37963810
break;
37973811
}
37983812

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import print_function
2+
import lldb
3+
from lldbsuite.test.lldbtest import *
4+
from lldbsuite.test.decorators import *
5+
from gdbclientutils import *
6+
7+
8+
class TestHaltFails(GDBRemoteTestBase):
9+
10+
class MyResponder(MockGDBServerResponder):
11+
12+
def setBreakpoint(self, packet):
13+
return "OK"
14+
15+
def interrupt(self):
16+
# Simulate process waiting longer than the interrupt
17+
# timeout to stop, then sending the reply.
18+
time.sleep(14)
19+
return "T02reason:signal"
20+
21+
def cont(self):
22+
# No response, wait for the client to interrupt us.
23+
return None
24+
25+
def wait_for_and_check_event(self, wait_time, value):
26+
event = lldb.SBEvent()
27+
got_event = self.dbg.GetListener().WaitForEvent(wait_time, event)
28+
self.assertTrue(got_event, "Failed to get event after wait")
29+
self.assertTrue(lldb.SBProcess.EventIsProcessEvent(event), "Event was not a process event")
30+
event_type = lldb.SBProcess.GetStateFromEvent(event)
31+
self.assertEqual(event_type, value)
32+
33+
def get_to_running(self):
34+
self.server.responder = self.MyResponder()
35+
self.target = self.createTarget("a.yaml")
36+
process = self.connect(self.target)
37+
self.dbg.SetAsync(True)
38+
39+
# There should be a stopped event, consume that:
40+
self.wait_for_and_check_event(2, lldb.eStateStopped)
41+
process.Continue()
42+
43+
# There should be a running event, consume that:
44+
self.wait_for_and_check_event(2, lldb.eStateRunning)
45+
return process
46+
47+
@skipIfReproducer # FIXME: Unexpected packet during (passive) replay
48+
def test_destroy_while_running(self):
49+
process = self.get_to_running()
50+
process.Destroy()
51+
52+
# Again pretend that after failing to be interrupted, we delivered the stop
53+
# and make sure we still exit properly.
54+
self.wait_for_and_check_event(14, lldb.eStateExited)
55+
56+
@skipIfReproducer # FIXME: Unexpected packet during (passive) replay
57+
def test_async_interrupt(self):
58+
"""
59+
Test that explicitly calling AsyncInterrupt, which then fails, leads
60+
to an "eStateExited" state.
61+
"""
62+
process = self.get_to_running()
63+
# Now do the interrupt:
64+
process.SendAsyncInterrupt()
65+
66+
# That should have caused the Halt to time out and we should
67+
# be in eStateExited:
68+
self.wait_for_and_check_event(15, lldb.eStateExited)
69+
70+
71+
72+

0 commit comments

Comments
 (0)