-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Send an explicit interrupt to cancel an attach waitfor. #72565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
Test using SendAsyncInterrupt to interrupt an "attach wait" | ||
""" | ||
|
||
import lldb | ||
import sys | ||
import time | ||
import threading | ||
from lldbsuite.test.decorators import * | ||
from lldbsuite.test.lldbtest import * | ||
import lldbsuite.test.lldbutil | ||
|
||
|
||
class AttachCancelTestCase(TestBase): | ||
NO_DEBUG_INFO_TESTCASE = True | ||
|
||
def test_scripted_implementation(self): | ||
"""Test that cancelling a stuck "attach waitfor" works.""" | ||
# First make an empty target for the attach: | ||
target = self.dbg.CreateTarget(None) | ||
|
||
# We need to cancel this, so we need to do the attach | ||
# on a separate thread: | ||
class AttachThread(threading.Thread): | ||
def __init__(self, target, error): | ||
# Make this a daemon thread so if we don't manage to interrupt, | ||
# Python will keep this thread from hanging the test. | ||
threading.Thread.__init__(self, daemon=True) | ||
self.target = target | ||
self.error = error | ||
|
||
def run(self): | ||
self.target.AttachToProcessWithName(lldb.SBListener(), "LLDB-No-Such-Process", True, self.error) | ||
|
||
error = lldb.SBError() | ||
thread = AttachThread(target, error) | ||
thread.start() | ||
|
||
# Now wait till the attach on the child thread has made a process | ||
# for the attach attempt: | ||
while not target.process.IsValid(): | ||
time.sleep(1) | ||
# I don't have a positive signal for "we started the attach attempt" | ||
# so the best I can do is sleep a bit more here to give that a chance | ||
# to start: | ||
time.sleep(1) | ||
|
||
# Now send the attach interrupt: | ||
target.process.SendAsyncInterrupt() | ||
# We don't want to stall if we can't interrupt, so join with a timeout: | ||
thread.join(60) | ||
if thread.is_alive(): | ||
self.fail("The attach thread is alive after timeout interval") | ||
|
||
# Now check the error, should say the attach was interrupted: | ||
self.assertTrue(error.Fail(), "We succeeded in not attaching") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,6 +50,7 @@ | |
#include "MacOSX/MachProcess.h" | ||
#include "MacOSX/MachTask.h" | ||
#include "MacOSX/ThreadInfo.h" | ||
#include "RNBRemote.h" | ||
|
||
typedef std::shared_ptr<MachProcess> MachProcessSP; | ||
typedef std::map<nub_process_t, MachProcessSP> ProcessMap; | ||
|
@@ -745,7 +746,6 @@ DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name, | |
break; | ||
} | ||
} else { | ||
|
||
// Get the current process list, and check for matches that | ||
// aren't in our original list. If anyone wants to attach | ||
// to an existing process by name, they should do it with | ||
|
@@ -799,7 +799,33 @@ DNBProcessAttachWait(RNBContext *ctx, const char *waitfor_process_name, | |
break; | ||
} | ||
|
||
::usleep(waitfor_interval); // Sleep for WAITFOR_INTERVAL, then poll again | ||
// Now we're going to wait a while before polling again. But we also | ||
// need to check whether we've gotten an event from the debugger | ||
// telling us to interrupt the wait. So we'll use the wait for a possible | ||
// next event to also be our short pause... | ||
struct timespec short_timeout; | ||
DNBTimer::OffsetTimeOfDay(&short_timeout, 0, waitfor_interval); | ||
uint32_t event_mask = RNBContext::event_read_packet_available | ||
| RNBContext::event_read_thread_exiting; | ||
nub_event_t set_events = ctx->Events().WaitForSetEvents(event_mask, | ||
&short_timeout); | ||
if (set_events & RNBContext::event_read_packet_available) { | ||
// If we get any packet from the debugger while waiting on the async, | ||
// it has to be telling us to interrupt. So always exit here. | ||
// Over here in DNB land we can see that there was a packet, but all | ||
// the methods to actually handle it are protected. It's not worth | ||
// rearranging all that just to get which packet we were sent... | ||
DNBLogError("Interrupted by packet while waiting for '%s' to appear.\n", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we check the packet and assert/log when it's not an interrupt? I know that's technically not allowed by the GDB remote protocol, but that would make it easy to catch such a mistake. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the code that does the actual packet handling (as opposed to just waiting for a packet type) is private to RNBRemote.cpp, and not available here. I didn't think getting the packet here was worth rejiggering that relationship. You can turn on packet logging in debugserver if you wanted to see which packet this was. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The structure of the gdb remote protocol is such that when you've sent a packet, you can't send anything else until you get a response. "continue" is special in that you can send ^C to the remote stub to interrupt execution. A "waitfor" attach request most reasonably behaves the same as "continue" - the only thing it would make any sense for the debugger to send when we're waiting is ^C. If the debugger were to send anything else, yeah, something has gone very wrong, but when Jim described the layering and how it wasn't straightforward to check that, I didn't push further, it seemed fine to me to assume the debugger didn't do something unsupported like send a non-interrupt packet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that we are attaching and the only thing we can send a ^C, so no need for anything else here really. |
||
waitfor_process_name); | ||
break; | ||
} | ||
if (set_events & RNBContext::event_read_thread_exiting) { | ||
// The packet thread is shutting down, get out of here... | ||
DNBLogError("Interrupted by packet thread shutdown while waiting for " | ||
"%s to appear.\n", waitfor_process_name); | ||
break; | ||
} | ||
|
||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we log the return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll add that.