Skip to content

Commit d1bf194

Browse files
authored
Send an explicit interrupt to cancel an attach waitfor. (#72565)
Currently when you interrupt a: (lldb) process attach -w -n some_process lldb just closes the connection to the stub and kills the lldb_private::Process it made for the attach. The stub at the other end notices the connection go down and exits because of that. But when communication to a device is handled through some kind of proxy server which isn't as well behaved as one would wish, that signal might not be reliable, causing debugserver to persist on the machine, waiting to steal the next instance of that process. We can work around those failures by sending an explicit interrupt before closing down the connection. The stub will also have to be waiting for the interrupt for this to make any difference. I changed debugserver to do that. I didn't make the equivalent change in lldb-server. So long as you aren't faced with a flakey connection, this should not be necessary.
1 parent cb13e92 commit d1bf194

File tree

4 files changed

+97
-5
lines changed

4 files changed

+97
-5
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,8 +2371,10 @@ Status ProcessGDBRemote::DoHalt(bool &caused_stop) {
23712371
Status error;
23722372

23732373
if (m_public_state.GetValue() == eStateAttaching) {
2374-
// We are being asked to halt during an attach. We need to just close our
2375-
// file handle and debugserver will go away, and we can be done...
2374+
// We are being asked to halt during an attach. We used to just close our
2375+
// file handle and debugserver will go away, but with remote proxies, it
2376+
// is better to send a positive signal, so let's send the interrupt first...
2377+
caused_stop = m_gdb_comm.Interrupt(GetInterruptTimeout());
23762378
m_gdb_comm.Disconnect();
23772379
} else
23782380
caused_stop = m_gdb_comm.Interrupt(GetInterruptTimeout());

lldb/source/Target/Process.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3156,8 +3156,8 @@ Status Process::Halt(bool clear_thread_plans, bool use_run_lock) {
31563156
// Don't hijack and eat the eStateExited as the code that was doing the
31573157
// attach will be waiting for this event...
31583158
RestoreProcessEvents();
3159-
SetExitStatus(SIGKILL, "Cancelled async attach.");
31603159
Destroy(false);
3160+
SetExitStatus(SIGKILL, "Cancelled async attach.");
31613161
return Status();
31623162
}
31633163

@@ -3843,6 +3843,13 @@ thread_result_t Process::RunPrivateStateThread(bool is_secondary_thread) {
38433843
") woke up with an interrupt while attaching - "
38443844
"forwarding interrupt.",
38453845
__FUNCTION__, static_cast<void *>(this), GetID());
3846+
// The server may be spinning waiting for a process to appear, in which
3847+
// case we should tell it to stop doing that. Normally, we don't NEED
3848+
// to do that because we will next close the communication to the stub
3849+
// and that will get it to shut down. But there are remote debugging
3850+
// cases where relying on that side-effect causes the shutdown to be
3851+
// flakey, so we should send a positive signal to interrupt the wait.
3852+
Status error = HaltPrivate();
38463853
BroadcastEvent(eBroadcastBitInterrupt, nullptr);
38473854
} else if (StateIsRunningState(m_last_broadcast_state)) {
38483855
LLDB_LOGF(log,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
Test using SendAsyncInterrupt to interrupt an "attach wait"
3+
"""
4+
5+
import lldb
6+
import sys
7+
import time
8+
import threading
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
import lldbsuite.test.lldbutil
12+
13+
14+
class AttachCancelTestCase(TestBase):
15+
NO_DEBUG_INFO_TESTCASE = True
16+
17+
def test_scripted_implementation(self):
18+
"""Test that cancelling a stuck "attach waitfor" works."""
19+
# First make an empty target for the attach:
20+
target = self.dbg.CreateTarget(None)
21+
22+
# We need to cancel this, so we need to do the attach
23+
# on a separate thread:
24+
class AttachThread(threading.Thread):
25+
def __init__(self, target, error):
26+
# Make this a daemon thread so if we don't manage to interrupt,
27+
# Python will keep this thread from hanging the test.
28+
threading.Thread.__init__(self, daemon=True)
29+
self.target = target
30+
self.error = error
31+
32+
def run(self):
33+
self.target.AttachToProcessWithName(lldb.SBListener(), "LLDB-No-Such-Process", True, self.error)
34+
35+
error = lldb.SBError()
36+
thread = AttachThread(target, error)
37+
thread.start()
38+
39+
# Now wait till the attach on the child thread has made a process
40+
# for the attach attempt:
41+
while not target.process.IsValid():
42+
time.sleep(1)
43+
# I don't have a positive signal for "we started the attach attempt"
44+
# so the best I can do is sleep a bit more here to give that a chance
45+
# to start:
46+
time.sleep(1)
47+
48+
# Now send the attach interrupt:
49+
target.process.SendAsyncInterrupt()
50+
# We don't want to stall if we can't interrupt, so join with a timeout:
51+
thread.join(60)
52+
if thread.is_alive():
53+
self.fail("The attach thread is alive after timeout interval")
54+
55+
# Now check the error, should say the attach was interrupted:
56+
self.assertTrue(error.Fail(), "We succeeded in not attaching")
57+

lldb/tools/debugserver/source/DNB.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "MacOSX/MachProcess.h"
5151
#include "MacOSX/MachTask.h"
5252
#include "MacOSX/ThreadInfo.h"
53+
#include "RNBRemote.h"
5354

5455
typedef std::shared_ptr<MachProcess> MachProcessSP;
5556
typedef std::map<nub_process_t, MachProcessSP> ProcessMap;
@@ -745,7 +746,6 @@ DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name,
745746
break;
746747
}
747748
} else {
748-
749749
// Get the current process list, and check for matches that
750750
// aren't in our original list. If anyone wants to attach
751751
// to an existing process by name, they should do it with
@@ -799,7 +799,33 @@ DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name,
799799
break;
800800
}
801801

802-
::usleep(waitfor_interval); // Sleep for WAITFOR_INTERVAL, then poll again
802+
// Now we're going to wait a while before polling again. But we also
803+
// need to check whether we've gotten an event from the debugger
804+
// telling us to interrupt the wait. So we'll use the wait for a possible
805+
// next event to also be our short pause...
806+
struct timespec short_timeout;
807+
DNBTimer::OffsetTimeOfDay(&short_timeout, 0, waitfor_interval);
808+
uint32_t event_mask = RNBContext::event_read_packet_available
809+
| RNBContext::event_read_thread_exiting;
810+
nub_event_t set_events = ctx->Events().WaitForSetEvents(event_mask,
811+
&short_timeout);
812+
if (set_events & RNBContext::event_read_packet_available) {
813+
// If we get any packet from the debugger while waiting on the async,
814+
// it has to be telling us to interrupt. So always exit here.
815+
// Over here in DNB land we can see that there was a packet, but all
816+
// the methods to actually handle it are protected. It's not worth
817+
// rearranging all that just to get which packet we were sent...
818+
DNBLogError("Interrupted by packet while waiting for '%s' to appear.\n",
819+
waitfor_process_name);
820+
break;
821+
}
822+
if (set_events & RNBContext::event_read_thread_exiting) {
823+
// The packet thread is shutting down, get out of here...
824+
DNBLogError("Interrupted by packet thread shutdown while waiting for "
825+
"%s to appear.\n", waitfor_process_name);
826+
break;
827+
}
828+
803829
}
804830
}
805831

0 commit comments

Comments
 (0)