[PATCH v2 39/47] Windows gdb: Add non-stop support

Pedro Alves
2025-05-19T13:39:40Z

This patch adds non-stop support to the native Windows target.

This is made possible by the ContinueDebugEvent DBG_REPLY_LATER flag:

https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent

Supported in Windows 10, version 1507 or above, this flag causes
dwThreadId to replay the existing breaking event after the target
continues. By calling the SuspendThread API against dwThreadId, a
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
debugger can resume other threads in the process and later return to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
the breaking.
^^^^^^^^^^^^

The patch adds a new comment section in gdb/windows-nat.c providing an
overall picture of how all-stop / non-stop work.

Without DBG_REPLY_LATER, if we SuspendThread the thread, and then
immediately ContinueDebugThread(DBG_CONTINUE) before getting back to
the prompt, we could still have non-stop mode working, however, then
users wouldn't have a chance to decide whether to pass the signal to
the inferior the next time they resume the program, as that is done by
passing DBG_EXCEPTION_NOT_HANDLED to ContinueDebugEvent, and that has
already been called.

The patch teaches the Windows native backend to use that
DBG_REPLY_LATER flag, and also adds support for target_stop, so the
core can pause threads at its discretion. This pausing does not use
the same mechanisms used in windows_nat_target::interrupt, as that
injects a new thread in the inferior. Instead, for each thread the
core wants paused, it uses SuspendThread, and enqueues a pending
GDB_SIGNAL_0 stop on the thread.

Since DBG_REPLY_LATER only exists on Windows 10 and later, we only
enable non-stop mode on Windows 10 and later.

There is no displaced stepping support, but that's "just" a missed
optimization to be done later.

Cygwin signals handling was a major headache, but I managed to get it
working. See the "Cygwin signals" description section I added at the
top of windows-nat.c.

Another interesting bit, is that the use DBG_REPLY_LATER caused one
problem with detach. The Windows kernel re-raises any exception
previously intercepted and deferred with DBG_REPLY_LATER in the
inferior after we detach. We need to flush those events, and suppress
those which aren't meant to be seen by the inferior (e.g.,
breakpoints, single-steps, any with matching "handle SIG nopass",
etc.), otherwise the inferior dies immediately after the detach, due
to an unhandled exception.

Change-Id: Id71aef461c43c244120635b5bedc638fe77c31fb
---
gdb/nat/windows-nat.c | 17 +-
gdb/nat/windows-nat.h | 51 +-
gdb/windows-nat.c | 1134 +++++++++++++++++++++++++++++++++++-----
gdbserver/win32-low.cc | 15 +-
gdbserver/win32-low.h | 4 +-
5 files changed, 1079 insertions(+), 142 deletions(-)

diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 7e683fd8629..e1c2cd79c20 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -721,17 +721,20 @@ get_last_debug_event_ptid ()
/* See nat/windows-nat.h. */

BOOL
-continue_last_debug_event (DWORD continue_status, bool debug_events)
+continue_last_debug_event (DWORD cont_status, bool debug_events)
{
- DEBUG_EVENTS ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
- (unsigned) last_wait_event.dwProcessId,
- (unsigned) last_wait_event.dwThreadId,
- continue_status == DBG_CONTINUE ?
- "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED");
+ DEBUG_EVENTS
+ ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
+ (unsigned) last_wait_event.dwProcessId,
+ (unsigned) last_wait_event.dwThreadId,
+ cont_status == DBG_CONTINUE ? "DBG_CONTINUE" :
+ cont_status == DBG_EXCEPTION_NOT_HANDLED ? "DBG_EXCEPTION_NOT_HANDLED" :
+ cont_status == DBG_REPLY_LATER ? "DBG_REPLY_LATER" :
+ "DBG_???");

return ContinueDebugEvent (last_wait_event.dwProcessId,
last_wait_event.dwThreadId,
- continue_status);
+ cont_status);
}

/* See nat/windows-nat.h. */
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 19403d0e623..fe377fcde34 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -87,12 +87,55 @@ struct windows_thread_info
/* Thread Information Block address. */
CORE_ADDR thread_local_base;

+#ifdef __CYGWIN__
+ /* These two fields are used to handle Cygwin signals. When a
+ thread is signaled, the "sig" thread inside the Cygwin runtime
+ reports the fact to us via a special OutputDebugString message.
+ In order to make stepping into a signal handler work, we can only
+ resume the "sig" thread when we also resume the target signaled
+ thread. When we intercept a Cygwin signal, we set up a cross
+ link between the two threads using the two fields below, so we
+ can always identify one from the other. See the "Cygwin signals"
+ description in gdb/windows-nat.c for more. */
+
+ /* If this thread received a signal, then 'cygwin_sig_thread' points
+ to the "sig" thread within the Cygwin runtime. */
+ windows_thread_info *cygwin_sig_thread = nullptr;
+
+ /* If this thread is the Cygwin runtime's "sig" thread, then
+ 'signaled_thread' points at the thread that received a
+ signal. */
+ windows_thread_info *signaled_thread = nullptr;
+#endif
+
+ /* If the thread had its event postponed with DBG_REPLY_LATER, when
+ we later ResumeThread this thread, WaitForDebugEvent will
+ re-report the postponed event. This field holds the continue
+ status value to be automatically passed to ContinueDebugEvent
+ when we encounter this re-reported event. 0 if the thread has
+ not had its event postponed with DBG_REPLY_LATER. */
+ DWORD reply_later = 0;
+
/* This keeps track of whether SuspendThread was called on this
thread. -1 means there was a failure or that the thread was
explicitly not suspended, 1 means it was called, and 0 means it
was not. */
int suspended = 0;

+ /* This flag indicates whether we are explicitly stopping this
+ thread in response to a target_stop request. This allows
+ distinguishing between threads that are explicitly stopped by the
+ debugger and threads that are stopped due to other reasons.
+
+ Typically, when we want to stop a thread, we suspend it, enqueue
+ a pending GDB_SIGNAL_0 stop status on the thread, and then set
+ this flag to true. However, if the thread has had its event
+ previously postponed with DBG_REPLY_LATER, it means that it
+ already has an event to report. In such case, we simply set the
+ 'stopping' flag without suspending the thread or enqueueing a
+ pending stop. See stop_one_thread. */
+ bool stopping = false;
+
/* Info about a potential pending stop.

Sometimes, Windows will report a stop on a thread that has been
@@ -173,15 +216,15 @@ struct windows_process_info
virtual windows_thread_info *find_thread (ptid_t ptid) = 0;

/* Handle OUTPUT_DEBUG_STRING_EVENT from child process. Updates
- OURSTATUS and returns the thread id if this represents a thread
- change (this is specific to Cygwin), otherwise 0.
+ OURSTATUS and returns true if this represents a Cygwin signal,
+ otherwise false.

Cygwin prepends its messages with a "cygwin:". Interpret this as
a Cygwin signal. Otherwise just print the string as a warning.

This function must be supplied by the embedding application. */
- virtual DWORD handle_output_debug_string (const DEBUG_EVENT &current_event,
- struct target_waitstatus *ourstatus) = 0;
+ virtual bool handle_output_debug_string (const DEBUG_EVENT &current_event,
+ struct target_waitstatus *ourstatus) = 0;

/* Handle a DLL load event.

diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 73ddfcb3b6a..408df2acd78 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -77,6 +77,202 @@
#include "ser-event.h"
#include "inf-loop.h"

+/* This comment documents high-level logic of this file.
+
+all-stop
+========
+
+In all-stop mode ("maint set target-non-stop off"), there is only ever
+one Windows debug event in flight. When we receive an event from
+WaitForDebugEvent, the kernel has already implicitly suspended all the
+threads of the process. We report the breaking event to the core.
+When the core decides to resume the inferior, it calls
+windows_nat_target:resume, which triggers a ContinueDebugEvent call.
+This call makes all unsuspended threads schedulable again, and we go
+back to waiting for the next event in WaitForDebugEvent.
+
+non-stop
+========
+
+For non-stop mode, we utilize the DBG_REPLY_LATER flag in the
+ContinueDebugEvent function. According to Microsoft:
+
+ "This flag causes dwThreadId to replay the existing breaking event
+ after the target continues. By calling the SuspendThread API against
+ dwThreadId, a debugger can resume other threads in the process and
+ later return to the breaking."
+
+To enable non-stop mode, windows_nat_target::wait suspends the thread,
+calls 'ContinueForDebugEvent(..., DBG_REPLY_LATER)', and sets the
+process_thread thread to wait for the next event using
+WaitForDebugEvent, all before returning the original breaking event to
+the core.
+
+When the user/core finally decides to resume the inferior thread that
+reported the event, we unsuspend it using ResumeThread. Unlike in
+all-stop mode, we don't call ContinueDebugEvent then, as it has
+already been called when the event was first encountered. By making
+the inferior thread schedulable again (by unsuspending it),
+WaitForDebugEvent re-reports the same event (due to the earlier
+DBG_REPLY_LATER). In windows_nat_target::wait, we detect this delayed
+re-report and call ContinueDebugEvent on the thread, instructing the
+"process_thread" thread (the GDB thread responsible for calling
+WaitForDebugEvents) to continue waiting for the next event.
+
+During the initial thread resumption in windows_nat_target::resume, we
+recorded the dwContinueStatus argument to be passed to the last
+ContinueDebugEvent (called when the reply-later event is re-reported).
+See windows_thread_info::reply_later for details.
+
+Note that with this setup, in non-stop mode, every stopped thread has
+its own independent last-reported Windows debug event. Therefore, we
+can decide on a per-thread basis whether to pass the thread's
+exception (DBG_EXCEPTION_NOT_HANDLED / DBG_CONTINUE) to the inferior.
+This per-thread decision is not possible in all-stop mode, where we
+only call ContinueDebugEvent for the thread that last reported a stop,
+at windows_nat_target::resume time.
+
+Thread and process exits
+========================
+
+When a process exits, Windows reports one EXIT_THREAD_DEBUG_EVENT
+event for each thread, except for the last thread that exits. That
+last thread reports a EXIT_PROCESS_DEBUG_EVENT event instead.
+
+The last thread that exits is not guaranteed to be the main thread of
+the process. In fact, it seldom is. E.g., if the main thread calls
+ExitProcess (or returns from main, which ends up calling ExitProcess),
+then we typically see a EXIT_THREAD_DEBUG_EVENT event for the main
+thread first, followed by more EXIT_THREAD_DEBUG_EVENT events for
+other threads, and then finaly the EXIT_PROCESS_DEBUG_EVENT for
+whatever thread happened to be the last one to exit.
+
+When a thread reports EXIT_THREAD_DEBUG_EVENT /
+EXIT_PROCESS_DEBUG_EVENT, our handle to the thread is still valid, and
+we can still read its registers. Windows only destroys the handle
+after ContinueDebugEvent.
+
+A thread that has exited CANNOT be suspended. So if a thread was
+previously suspended, and then something kills the whole process
+(which force-kills all threads), that suspended thread will
+automatically "unsuspend", and report a EXIT_THREAD_DEBUG_EVENT event.
+However, if we had previously used DBG_REPLY_LATER on the thread,
+Windows will first re-report the kernel-side-queued "reply-later"
+event, and only after that one is ContinueDebugEvent'ed, will we see
+the EXIT_THREAD_DEBUG_EVENT event.
+
+Detaching and DBG_REPLY_LATER
+=============================
+
+After we detach from a process that has threads that we had previously
+used DBG_REPLY_LATER on, the kernel re-raises the "reply-later"
+exceptions for those threads. This would most often kill the
+just-detached process, if we let it happen. To prevent it, we flush
+all the "reply-later" events from the kernel before detaching.
+
+Cygwin signals
+==============
+
+The Cygwin runtime always spawns a "sig" thread, which is responsible
+for receiving signal delivery requests, and hijacking the signaled
+thread's execution to make it run the signal handler. This is all
+explained here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/DevDocs/how-signals-work.txt
+
+There's a custom debug api protocol between GDB and Cygwin to be able
+to intercept Cygwin signals before they're seen by the signaled
+thread, just like the debugger intercepts signals with ptrace on
+Linux. This Cygwin debugger protocol isn't well documented, though.
+Here's what happens: when the special "sig" thread in the Cygwin
+runtime is about to deliver a signal to the target thread, it calls
+OutputDebugString with a special message:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1866
+
+OutputDebugString is a function that is part of the Windows debug API.
+It generates an OUTPUT_DEBUG_STRING_EVENT event out of
+WaitForDebugEvent in the debugger, which freezes the inferior, like
+any other event.
+
+GDB recognizes the special Cygwin signal marker string, and is able to
+report the intercepted Cygwin signal to the user.
+
+With the windows-nat backend in all-stop mode, if the user decides to
+single-step the signaled thread, GDB will set the trace flag in the
+signaled thread to force it to single-step, and then re-resume the
+program with ContinueDebugEvent. This resumes both the signaled
+thread, and the special "sig" thread. The special "sig" thread
+decides to make the signaled thread run the signal handler, so it
+suspends it with SuspendThread, does a read-modify-write operation
+with GetThreadContext/SetThreadContext, and then re-resumes it with
+ResumeThread. This is all done here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1011
+
+That resulting register context will still have its trace flag set, so
+the signaled thread ends up single-stepping the signal handler and
+reporting the trace stop to GDB, which reports the stop where the
+thread is now stopped, inside the signal handler.
+
+That is the intended behavior; stepping into a signal handler is a
+feature that works on other ports as well, including x86 GNU/Linux,
+for example. This is exercised by the gdb.base/sigstep.exp testcase.
+
+Now, making that work with the backend in non-stop mode (the default
+on Windows 10 and above) is tricker. In that case, when GDB sees the
+magic OUTPUT_DEBUG_STRING_EVENT event mentioned above, reported for
+the "sig" thread, GDB reports the signal stop for the target signaled
+thread to the user (leaving that thread stopped), but, unlike with an
+all-stop backend, in non-stop, only the evented/signaled thread should
+be stopped, so the backend would normally want to re-resume the Cygwin
+runtime's "sig" thread after handling the OUTPUT_DEBUG_STRING_EVENT
+event, like it does with any other event out of WaitForDebugEvent that
+is not reported to the core. If it did that (resume the "sig" thread)
+however, at that point, the signaled thread would be stopped,
+suspended with SuspendThread by GDB (while the user is inspecting it),
+but, unlike in all-stop, the "sig" thread would be set running free.
+The "sig" thread would reach the code that wants to redirect the
+signaled thread's execution to the signal handler (by hacking the
+registers context, as described above), but unlike in the all-stop
+case, the "sig" thread would notice that the signaled thread is
+suspended, and so would decide to defer the signal handler until a
+later time. It's the same code as described above for the all-stop
+case, except it would take the "then" branch:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1019
+
+ // Just set pending if thread is already suspended
+ if (res)
+ {
+ tls->unlock ();
+ ResumeThread (hth);
+ goto out;
+ }
+
+The result would be that when the GDB user later finally decides to
+step the signaled thread, the signaled thread would just single step
+the mainline code, instead of stepping into the signal handler.
+
+To avoid this difference of behavior in non-stop mode compared to
+all-stop mode, we use a trick -- whenever we see that magic
+OUTPUT_DEBUG_STRING_EVENT event reported for the "sig" thread, we
+report a stop for the target signaled thread, _and_ leave the "sig"
+thread suspended as well, for as long as the target signaled thread is
+suspended. I.e., we don't let the "sig" thread run before the user
+decides what to do with the signaled thread's signal. Only when the
+user re-resumes the signaled thread, will we resume the "sig" thread
+as well. The trick is that all this is done here in the Windows
+backend, while providing the illusion to the core of GDB (and the
+user) that the "sig" thread is "running", for as long as the core
+wants the "sig" thread to be running.
+
+This isn't ideal, since this means that with user-visible non-stop,
+the inferior will only be able to process and report one signal at a
+time (as the "sig" thread is responsible for that), but that seems
+like an acceptible compromise, better than not being able to have the
+target work in non-stop by default on Cygwin. */
+
using namespace windows_nat;

/* Maintain a linked list of "so" information. */
@@ -103,6 +299,11 @@ enum windows_continue_flag
call to continue the inferior -- we are either mourning it or
detaching. */
WCONT_LAST_CALL = 2,
+
+ /* By default, windows_continue only calls ContinueDebugEvent in
+ all-stop mode. This flag indicates that windows_continue
+ should call ContinueDebugEvent even in non-stop mode. */
+ WCONT_CONTINUE_DEBUG_EVENT = 4,
};

DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
@@ -130,8 +331,8 @@ struct windows_private_thread_info : private_thread_info, windows_thread_info
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT &current_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT &current_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT &current_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
@@ -280,7 +481,11 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void attach (const char *, int) override;

bool attach_no_wait () override
- { return true; }
+ {
+ /* In non-stop, after attach, we leave all threads running, like
+ other targets. */
+ return !target_is_non_stop_p ();
+ }

void detach (inferior *, int) override;

@@ -323,8 +528,13 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
std::string pid_to_str (ptid_t) override;

void interrupt () override;
+ void stop (ptid_t) override;
void pass_ctrlc () override;

+ void thread_events (bool enable) override;
+
+ bool any_resumed_thread ();
+
const char *pid_to_exec_file (int pid) override;

ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override;
@@ -354,6 +564,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
return m_is_async;
}

+ bool supports_non_stop () override;
+
void async (bool enable) override;

int async_wait_fd () override
@@ -368,6 +580,15 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT &current_event);

+ void stop_one_thread (windows_thread_info *th);
+
+ DWORD continue_status_for_event_detaching
+ (const DEBUG_EVENT &event, size_t *reply_later_events_left = nullptr);
+
+ DWORD prepare_resume (windows_thread_info *wth,
+ thread_info *tp,
+ int step, gdb_signal sig);
+
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);

@@ -431,6 +652,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
already returned an event, and we need to ContinueDebugEvent
again to restart the inferior. */
bool m_continued = false;
+
+ /* Whether target_thread_events is in effect. */
+ bool m_report_thread_events = false;
};

/* Get the windows_thread_info object associated with THR. */
@@ -718,6 +942,13 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
registers. */
th->debug_registers_changed = true;

+ /* Even if we're stopping the thread for some reason internal to
+ this module, from the perspective of infrun and the
+ user/frontend, this new thread is running until it next reports a
+ stop. */
+ set_state (this, ptid, THREAD_RUNNING);
+ set_internal_state (this, ptid, THREAD_INT_RUNNING);
+
return th;
}

@@ -1089,12 +1320,17 @@ signal_event_command (const char *args, int from_tty)

/* See nat/windows-nat.h. */

-DWORD
+bool
windows_per_inferior::handle_output_debug_string
(const DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus)
{
- DWORD thread_id = 0;
+ windows_thread_info *event_thr
+ = windows_process.find_thread (ptid_t (current_event.dwProcessId,
+ current_event.dwThreadId));
+ if (event_thr->reply_later != 0)
+ internal_error ("OutputDebugString thread 0x%x has reply-later set",
+ event_thr->tid);

gdb::unique_xmalloc_ptr<char> s
= (target_read_string
@@ -1131,15 +1367,37 @@ windows_per_inferior::handle_output_debug_string
int sig = strtol (s.get () + sizeof (_CYGWIN_SIGNAL_STRING) - 1, &p, 0);
gdb_signal gotasig = gdb_signal_from_host (sig);
LPCVOID x = 0;
+ DWORD thread_id = 0;

- if (gotasig)
+ if (gotasig != GDB_SIGNAL_0)
{
- ourstatus->set_stopped (gotasig);
thread_id = strtoul (p, &p, 0);
- if (thread_id == 0)
- thread_id = current_event.dwThreadId;
- else
- x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+ if (thread_id != 0)
+ {
+ x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+
+ ptid_t ptid (current_event.dwProcessId, thread_id, 0);
+ windows_thread_info *th = find_thread (ptid);
+
+ /* Suspend the signaled thread, and leave the signal as
+ a pending event. It will be picked up by
+ windows_nat_target::wait. */
+ th->suspend ();
+ th->stopping = true;
+ th->last_event = {};
+ th->pending_status.set_stopped (gotasig);
+
+ /* Link the "sig" thread and the signaled threads, so we
+ can keep the "sig" thread suspended until we resume
+ the signaled thread. See "Cygwin signals" at the
+ top. */
+ event_thr->signaled_thread = th;
+ th->cygwin_sig_thread = event_thr;
+
+ /* Leave the "sig" thread suspended. */
+ event_thr->suspend ();
+ return true;
+ }
}

DEBUG_EVENTS ("gdb: cygwin signal %d, thread 0x%x, CONTEXT @ %p",
@@ -1147,7 +1405,7 @@ windows_per_inferior::handle_output_debug_string
}
#endif

- return thread_id;
+ return false;
}

static int
@@ -1303,6 +1561,15 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
{
struct x86_debug_reg_state *state = x86_debug_reg_state (process_id);

+ /* If this thread is already gone, but the core doesn't know about
+ it yet, there's really nothing to resume. Such a thread will
+ have a pending exit status, so we won't try to resume it in the
+ normal resume path. But, we can still end up here in the
+ kill/detach/mourn paths, trying to resume the whole process to
+ collect the last debug event. */
+ if (th->h == nullptr)
+ return;
+
windows_process.with_context (th, [&] (auto *context)
{
if (th->debug_registers_changed)
@@ -1376,6 +1643,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
});

th->resume ();
+ th->stopping = false;
th->last_sig = GDB_SIGNAL_0;
}

@@ -1387,31 +1655,77 @@ BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- for (auto *th : all_windows_threads ())
- {
- if ((id == -1 || id == (int) th->tid)
- && !th->suspended
- && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
- {
- DEBUG_EVENTS ("got matching pending stop event "
- "for 0x%x, not resuming",
- th->tid);
-
- /* There's no need to really continue, because there's already
- another event pending. However, we do need to inform the
- event loop of this. */
- serial_event_set (m_wait_event);
- return TRUE;
- }
- }
+ if ((cont_flags & (WCONT_LAST_CALL | WCONT_KILLED)) == 0)
+ for (auto *th : all_windows_threads ())
+ {
+ if ((id == -1 || id == (int) th->tid)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("got matching pending stop event "
+ "for 0x%x, not resuming",
+ th->tid);
+
+ /* There's no need to really continue, because there's already
+ another event pending. However, we do need to inform the
+ event loop of this. */
+ serial_event_set (m_wait_event);
+ return TRUE;
+ }
+ }

+ /* Resume any suspended thread whose ID matches "ID". Skip the
+ Cygwin "sig" thread in the main iteration, though. That one is
+ only resumed when the target signaled thread is resumed. See
+ "Cygwin signals" in the intro section. */
for (auto *th : all_windows_threads ())
- if (id == -1 || id == (int) th->tid)
- windows_process.continue_one_thread (th, cont_flags);
+ if (th->suspended
+#ifdef __CYGWIN__
+ && th->signaled_thread == nullptr
+#endif
+ && (id == -1 || id == (int) th->tid))
+ {
+ windows_process.continue_one_thread (th, cont_flags);
+
+#ifdef __CYGWIN__
+ /* See if we're resuming a thread that caught a Cygwin signal.
+ If so, also resume the Cygwin runtime's "sig" thread. */
+ if (th->cygwin_sig_thread != nullptr)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) blocked by "
+ "just-resumed thread %d (0x%x)",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid,
+ th->tid, th->tid);
+
+ inferior *inf = find_inferior_pid (this,
+ windows_process.process_id);
+ thread_info *sig_thr
+ = inf->find_thread (ptid_t (windows_process.process_id,
+ th->cygwin_sig_thread->tid));
+ if (sig_thr->internal_state () == THREAD_INT_RUNNING)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) meant to be running, "
+ "continuing it now",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid);
+ windows_process.continue_one_thread (th->cygwin_sig_thread,
+ cont_flags);
+ }
+ /* Break the chain. */
+ th->cygwin_sig_thread->signaled_thread = nullptr;
+ th->cygwin_sig_thread = nullptr;
+ }
+#endif
+ }

- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status,
- cont_flags & WCONT_LAST_CALL);
+ if (!target_is_non_stop_p ()
+ || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ {
+ DEBUG_EVENTS ("windows_continue -> continue_last_debug_event");
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status,
+ cont_flags & WCONT_LAST_CALL);
+ }

return TRUE;
}
@@ -1439,36 +1753,46 @@ windows_nat_target::fake_create_process (const DEBUG_EVENT &current_event)
return current_event.dwThreadId;
}

-void
-windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
-{
- windows_thread_info *th;
- DWORD continue_status = DBG_CONTINUE;
-
- /* A specific PTID means `step only this thread id'. */
- int resume_all = ptid == minus_one_ptid;
-
- /* If we're continuing all threads, it's the current inferior that
- should be handled specially. */
- if (resume_all)
- ptid = inferior_ptid;
+/* Prepare TH to be resumed. TH and TP must point at the same thread.
+ Records the right dwContinueStatus for SIG in th->reply_later if we
+ used DBG_REPLY_LATER before on this thread, and sets of clears the
+ trace flag according to STEP. Also returns the dwContinueStatus
+ argument to pass to ContinueDebugEvent. The thread is still left
+ suspended -- a subsequent windows_continue/continue_one_thread call
+ is needed to flush the thread's register context and unsuspend. */

- DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
- ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+DWORD
+windows_nat_target::prepare_resume (windows_thread_info *th,
+ thread_info *tp,
+ int step, gdb_signal sig)
+{
+ gdb_assert (th->tid == tp->ptid.lwp ());

- /* Get currently selected thread. */
- th = windows_process.find_thread (inferior_ptid);
- gdb_assert (th != nullptr);
+ DWORD continue_status = DBG_CONTINUE;

if (sig != GDB_SIGNAL_0)
{
+ /* Allow continuing with the same signal that interrupted us.
+ Otherwise complain. */
+
/* Note it is OK to call get_last_debug_event_ptid() from the
- main thread here, because we know the process_thread thread
- isn't waiting for an event at this point, so there's no data
- race. */
- if (inferior_ptid != get_last_debug_event_ptid ())
+ main thread here in all-stop, because we know the
+ process_thread thread is not waiting for an event at this
+ point, so there is no data race. We cannot call it in
+ non-stop mode, as the process_thread thread _is_ waiting for
+ events right now in that case. However, the restriction does
+ not exist in non-stop mode, so we don't even call it in that
+ mode. */
+ if (!target_is_non_stop_p ()
+ && tp->ptid != get_last_debug_event_ptid ())
{
- /* ContinueDebugEvent will be for a different thread. */
+ /* In all-stop, ContinueDebugEvent will be for a different
+ thread. For non-stop, we've called ContinueDebugEvent
+ with DBG_REPLY_LATER for this thread, so we just set the
+ intended continue status in 'reply_later', which is later
+ passed to ContinueDebugEvent in windows_nat_target::wait
+ after we resume the thread and we get the replied-later
+ (repeated) event out of WaitForDebugEvent. */
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not last-event thread", sig);
}
@@ -1504,20 +1828,58 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
th->last_sig);
}

+ /* If DBG_REPLY_LATER was used on the thread, we override the
+ continue status that will be passed to ContinueDebugEvent later
+ with the continue status we've just determined fulfils the
+ caller's resumption request. Note that DBG_REPLY_LATER is only
+ used in non-stop mode, and in that mode, windows_continue (called
+ below) does not call ContinueDebugEvent. */
+ if (th->reply_later != 0)
+ th->reply_later = continue_status;
+
+ /* Single step by setting t bit (trap flag). The trap flag is
+ automatically reset as soon as the single-step exception arrives,
+ however, it's possible to suspend/stop a thread before it
+ executes any instruction, leaving the trace flag set. If we
+ subsequently decide to continue such a thread instead of stepping
+ it, and we didn't clear the trap flag, the thread would step, and
+ we'd end up reporting a SIGTRAP to the core which the core
+ couldn't explain (because the thread wasn't supposed to be
+ stepping), and end up reporting a spurious SIGTRAP to the
+ user. */
+ regcache *regcache = get_thread_regcache (tp);
+ fetch_registers (regcache, gdbarch_ps_regnum (regcache->arch ()));
+
windows_process.with_context (th, [&] (auto *context)
{
if (step)
- {
- /* Single step by setting t bit. */
- regcache *regcache = get_thread_regcache (inferior_thread ());
- struct gdbarch *gdbarch = regcache->arch ();
- fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
- context->EFlags |= FLAG_TRACE_BIT;
- }
+ context->EFlags |= FLAG_TRACE_BIT;
+ else
+ context->EFlags &= ~FLAG_TRACE_BIT;
});

- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ return continue_status;
+}
+
+void
+windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
+{
+ /* A specific PTID means `step only this thread id'. */
+ int resume_all = ptid == minus_one_ptid;
+
+ /* If we're continuing all threads, it's the current inferior that
+ should be handled specially. */
+ if (resume_all)
+ ptid = inferior_ptid;
+
+ DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
+ ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+
+ /* Get currently selected thread. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+ gdb_assert (th != nullptr);
+
+ DWORD continue_status = prepare_resume (th, inferior_thread (), step, sig);

if (resume_all)
windows_continue (continue_status, -1);
@@ -1566,12 +1928,134 @@ windows_nat_target::interrupt ()
"Press Ctrl-c in the program console."));
}

+/* Stop thread TH. This leaves a GDB_SIGNAL_0 pending in the thread,
+ which is later consumed by windows_nat_target::wait. */
+
+void
+windows_nat_target::stop_one_thread (windows_thread_info *th)
+{
+ ptid_t thr_ptid (windows_process.process_id, th->tid);
+
+ if (th->suspended == -1)
+ {
+ /* Already known to be stopped; and suspension failed, most
+ probably because the thread is exiting. Do nothing, and let
+ the thread exit event be reported. */
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+ }
+#ifdef __CYGWIN__
+ else if (th->suspended
+ && th->signaled_thread != nullptr
+ && th->pending_status.kind () == TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("explict stop for \"sig\" thread %s held for signal",
+ thr_ptid.to_string ().c_str ());
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+#endif
+ else if (th->suspended)
+ {
+ /* Already known to be stopped; do nothing. */
+
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+
+ th->stopping = true;
+ }
+ else
+ {
+ DEBUG_EVENTS ("stop request for %s", thr_ptid.to_string ().c_str ());
+
+ th->suspend ();
+
+ /* If suspension failed, it means the thread is exiting. Let
+ the thread exit event be reported instead of faking our own
+ stop. */
+ if (th->suspended == -1)
+ {
+ DEBUG_EVENTS ("suspension of %s failed, expect thread exit event",
+ thr_ptid.to_string ().c_str ());
+ return;
+ }
+
+ gdb_assert (th->suspended == 1);
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+}
+
+/* Implementation of target_ops::stop. */
+
+void
+windows_nat_target::stop (ptid_t ptid)
+{
+ for (thread_info *thr : all_non_exited_threads (this))
+ {
+ if (thr->ptid.matches (ptid))
+ stop_one_thread (as_windows_thread_info (thr));
+ }
+}
+
void
windows_nat_target::pass_ctrlc ()
{
interrupt ();
}

+/* Implementation of the target_ops::thread_events method. */
+
+void
+windows_nat_target::thread_events (bool enable)
+{
+ DEBUG_EVENTS ("windows_nat_target::thread_events(%d)", enable);
+ m_report_thread_events = enable;
+}
+
+/* True if there is any resumed thread. */
+
+bool
+windows_nat_target::any_resumed_thread ()
+{
+ for (thread_info *thread : all_non_exited_threads (this))
+ if (thread->internal_state () == THREAD_INT_RUNNING)
+ return true;
+ return false;
+}
+
+/* Called for both EXIT_THREAD_DEBUG_EVENT and
+ EXIT_PROCESS_DEBUG_EVENT to handle the fact that the event thread
+ has exited. */
+
+static void
+handle_thread_exit (const DEBUG_EVENT &current_event)
+{
+ ptid_t ptid (current_event.dwProcessId, current_event.dwThreadId);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+ gdb_assert (th != nullptr);
+
+ /* The handle is still valid, but it is going to be automatically
+ closed by Windows when we next call ContinueDebugEvent. Fetch
+ the thread's registers while we still can. For EXIT_PROCESS,
+ ContinueDebugEvent only happens at target_mourn_inferior time,
+ but do this not too, for consistency with EXIT_THREAD time. */
+ windows_process.fill_thread_context (th);
+ th->h = nullptr;
+
+ /* The thread is gone, so no longer suspended from Windows's
+ perspective. */
+ th->suspended = -1;
+}
+
/* Get the next event from the child. Returns the thread ptid. */

ptid_t
@@ -1585,24 +2069,32 @@ windows_nat_target::get_windows_debug_event
/* If there is a relevant pending stop, report it now. See the
comment by the definition of "windows_thread_info::pending_status"
for details on why this is needed. */
- for (auto *th : all_windows_threads ())
+ for (thread_info *thread : all_threads_safe ())
{
- if (!th->suspended
+ if (thread->inf->process_target () != this)
+ continue;
+
+ auto *th = as_windows_thread_info (thread);
+ if (thread->internal_state () == THREAD_INT_RUNNING
+ && th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
- DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
-
- thread_id = th->tid;
*ourstatus = th->pending_status;
th->pending_status.set_ignore ();
*current_event = th->last_event;
-
- ptid_t ptid (windows_process.process_id, thread_id);
- windows_process.invalidate_context (th);
- return ptid;
+ DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
+ return thread->ptid;
}
}

+ /* If there are no resumed threads left, bail. */
+ if (windows_process.windows_initialization_done
+ && !any_resumed_thread ())
+ {
+ ourstatus->set_no_resumed ();
+ return minus_one_ptid;
+ }
+
if ((options & TARGET_WNOHANG) != 0 && !m_debug_event_pending)
{
ourstatus->set_ignore ();
@@ -1616,6 +2108,78 @@ windows_nat_target::get_windows_debug_event
event_code = current_event->dwDebugEventCode;
ourstatus->set_spurious ();

+ ptid_t result_ptid (current_event->dwProcessId,
+ current_event->dwThreadId, 0);
+ windows_thread_info *result_th = windows_process.find_thread (result_ptid);
+
+ /* If we previously used DBG_REPLY_LATER on this thread, and we're
+ seeing an event for it, it means we've already processed the
+ event, and then subsequently resumed the thread [1], intending to
+ pass REPLY_LATER to ContinueDebugEvent. Do that now, before the
+ switch table below, which may have side effects that don't make
+ sense for a delayed event.
+
+ [1] - with the caveat that sometimes Windows reports an event for
+ a suspended thread. Also handled below. */
+ if (result_th != nullptr && result_th->reply_later != 0)
+ {
+ DEBUG_EVENTS ("reply-later thread 0x%x, suspended=%d, dwDebugEventCode=%s",
+ result_th->tid, result_th->suspended,
+ event_code_to_string (event_code).c_str ());
+
+ gdb_assert (dbg_reply_later_available ());
+
+ /* We never ask to DBG_REPLY_LATER these two, so we shouldn't
+ see them here. If a thread is forced-exited when a
+ DBG_REPLY_LATER is in effect, then we will still see the
+ DBG_REPLY_LATER-ed event before the thread/process exit
+ event. */
+ gdb_assert (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT);
+
+ if (result_th->suspended == 1)
+ {
+ /* Pending stop. See the comment by the definition of
+ "pending_status" for details on why this is needed. */
+ DEBUG_EVENTS ("unexpected reply-later stop in suspended thread 0x%x",
+ result_th->tid);
+
+ /* Put the event back in the kernel queue. We haven't yet
+ decided which reply to use. */
+ continue_status = DBG_REPLY_LATER;
+ }
+ else if (result_th->suspended == -1)
+ {
+ /* We resumed the thread expecting to get back a reply-later
+ event. Before we saw that event, we tried to suspend the
+ thread, but that failed, because the thread exited
+ (likely because the whole process has been killed). We
+ should get back an EXIT_THREAD_DEBUG_EVENT for this
+ thread, but only after getting past this reply-later
+ event. */
+ DEBUG_EVENTS ("reply-later stop in suspend-failed "
+ "thread 0x%x, ignoring",
+ result_th->tid);
+
+ /* Continue normally, and expect a
+ EXIT_THREAD_DEBUG_EVENT. */
+ continue_status = DBG_CONTINUE;
+ result_th->reply_later = 0;
+ }
+ else
+ {
+ continue_status = result_th->reply_later;
+ result_th->reply_later = 0;
+ }
+
+ /* Go back to waiting for the next event. */
+ continue_last_debug_event_main_thread
+ (_("Failed to continue reply-later event"), continue_status);
+
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+
DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId,
@@ -1649,17 +2213,29 @@ windows_nat_target::get_windows_debug_event
current_event->u.CreateThread.lpThreadLocalBase,
false /* main_thread_p */));

- /* This updates debug registers if necessary. */
- windows_process.continue_one_thread (th, 0);
+ /* Update the debug registers if we're not reporting the stop.
+ If we are (reporting the stop), the debug registers will be
+ updated when the thread is eventually re-resumed. */
+ if (m_report_thread_events)
+ ourstatus->set_thread_created ();
+ else
+ windows_process.continue_one_thread (th, 0);
}
break;

case EXIT_THREAD_DEBUG_EVENT:
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- current_event->u.ExitThread.dwExitCode,
- false /* main_thread_p */);
- thread_id = 0;
+ {
+ ourstatus->set_thread_exited
+ (current_event->u.ExitThread.dwExitCode);
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
+
+ /* Don't decide yet whether to report the event, or delete the
+ thread immediately, because we still need to check whether
+ the event should be left pending, depending on whether the
+ thread was running or not from the core's perspective. */
+ }
break;

case CREATE_PROCESS_DEBUG_EVENT:
@@ -1688,9 +2264,6 @@ windows_nat_target::get_windows_debug_event
}
else if (windows_process.saw_create == 1)
{
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- 0, true /* main_thread_p */);
DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
/* If the exit status looks like a fatal exception, but we
don't recognize the exception's code, make the original
@@ -1702,7 +2275,10 @@ windows_nat_target::get_windows_debug_event
ourstatus->set_exited (exit_status);
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
- return ptid_t (current_event->dwProcessId);
+
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
}
break;

@@ -1761,8 +2337,24 @@ windows_nat_target::get_windows_debug_event
case OUTPUT_DEBUG_STRING_EVENT: /* Message from the kernel. */
if (windows_process.saw_create != 1)
break;
- thread_id = windows_process.handle_output_debug_string (*current_event,
- ourstatus);
+ if (windows_process.handle_output_debug_string (*current_event,
+ ourstatus))
+ {
+ /* We caught a Cygwin signal for a thread. That thread now
+ has a pending event, and the "sig" thread is
+ suspended. */
+ serial_event_set (m_wait_event);
+
+ /* In all-stop, return now to avoid reaching
+ ContinueDebugEvent further below. In all-stop, it's
+ always windows_nat_target::resume that does the
+ ContinueDebugEvent call. */
+ if (!target_is_non_stop_p ())
+ {
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+ }
break;

default:
@@ -1784,35 +2376,79 @@ windows_nat_target::get_windows_debug_event
return null_ptid;
}

- const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
- windows_thread_info *th = windows_process.find_thread (ptid);
+ const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id);
+ thread_info *thread = this->find_thread (ptid);
+ auto *th = as_windows_thread_info (thread);

th->last_event = *current_event;

- if (th->suspended)
+ if (thread->internal_state () == THREAD_INT_STOPPED)
{
+ gdb_assert (th->suspended != 0);
+
/* Pending stop. See the comment by the definition of
"pending_status" for details on why this is needed. */
DEBUG_EVENTS ("get_windows_debug_event - "
"unexpected stop in suspended thread 0x%x",
thread_id);

- if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
- && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
- == EXCEPTION_BREAKPOINT)
- || (current_event->u.Exception.ExceptionRecord.ExceptionCode
- == STATUS_WX86_BREAKPOINT))
- && windows_process.windows_initialization_done)
+ /* Use DBG_REPLY_LATER to put the event back in the kernel queue
+ if possible. Don't do that with exit-thread or exit-process
+ events, because when a thread is dead, it can't be suspended
+ anymore, so the kernel would immediately re-report the
+ event. */
+ if (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT
+ && dbg_reply_later_available ())
{
- th->stopped_at_software_breakpoint = true;
- th->pc_adjusted = false;
+ /* Thankfully, the Windows kernel doesn't immediately
+ re-report the unexpected event for a suspended thread
+ when we defer it with DBG_REPLY_LATER, otherwise this
+ would get us stuck in an infinite loop re-processing the
+ same unexpected event over and over. (Which is what
+ would happen if we used DBG_REPLY_LATER on an exit-thread
+ or exit-process event. See comment above.) */
+ continue_status = DBG_REPLY_LATER;
+ }
+ else
+ {
+ if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
+ && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == EXCEPTION_BREAKPOINT)
+ || (current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == STATUS_WX86_BREAKPOINT))
+ && windows_process.windows_initialization_done)
+ {
+ th->stopped_at_software_breakpoint = true;
+ th->pc_adjusted = false;
+ }
+
+ th->pending_status = *ourstatus;
+ th->last_event = {};
}

- th->pending_status = *ourstatus;
+ /* For exit-process, the debug event is continued later, at
+ mourn time. */
+ if (event_code != EXIT_PROCESS_DEBUG_EVENT)
+ {
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status);
+ }
ourstatus->set_ignore ();
+ return null_ptid;
+ }

- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status);
+ gdb_assert (thread->internal_state () == THREAD_INT_RUNNING);
+
+ /* Now that we've handled exit events for suspended threads (above),
+ we can finally decide whether to report the thread exit event or
+ just delete the thread without bothering the core. */
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED
+ && !m_report_thread_events)
+ {
+ delete_thread (ptid, ourstatus->exit_status (),
+ false /* main_thread_p */);
+ ourstatus->set_spurious ();
return null_ptid;
}

@@ -1839,15 +2475,26 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,

while (1)
{
- DEBUG_EVENT current_event;
+ DEBUG_EVENT current_event {};

ptid_t result = get_windows_debug_event (pid, ourstatus, options,
&current_event);
+ /* True if this is a pending event that we injected ourselves,
+ instead of a real event out of WaitForDebugEvent. */
+ bool fake = current_event.dwDebugEventCode == 0;
+
+ DEBUG_EVENTS ("get_windows_debug_event returned [%s : %s, fake=%d]",
+ result.to_string ().c_str (),
+ ourstatus->to_string ().c_str(),
+ fake);

if ((options & TARGET_WNOHANG) != 0
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE)
return result;

+ if (ourstatus->kind () == TARGET_WAITKIND_NO_RESUMED)
+ return result;
+
if (ourstatus->kind () == TARGET_WAITKIND_SPURIOUS)
{
continue_last_debug_event_main_thread
@@ -1873,10 +2520,40 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
th->pc_adjusted = false;
}

+ /* If non-stop, suspend the event thread, and continue
+ it with DBG_REPLY_LATER, so the other threads go back
+ to running as soon as possible. Don't do this if
+ stopping the thread, as in that case the thread was
+ already suspended, and also there's no real Windows
+ debug event to continue in that case. */
+ if (windows_process.windows_initialization_done
+ && target_is_non_stop_p ()
+ && !fake)
+ {
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
+ {
+ gdb_assert (th->suspended == -1);
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to DBG_CONTINUE after thread exit"),
+ DBG_CONTINUE);
+ }
+ else
+ {
+ th->suspend ();
+ th->reply_later = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
+ }
+
/* All-stop, suspend all threads until they are
explicitly resumed. */
- for (auto *thr : all_windows_threads ())
- thr->suspend ();
+ if (!target_is_non_stop_p ())
+ for (auto *thr : all_windows_threads ())
+ thr->suspend ();
+
+ th->stopping = false;
}

/* If something came out, assume there may be more. This is
@@ -1942,22 +2619,31 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)

ptid_t last_ptid;

+ /* Keep fetching events until we see the initial breakpoint (which
+ is planted by Windows itself) being reported. */
+
while (1)
{
struct target_waitstatus status;

last_ptid = this->wait (minus_one_ptid, &status, 0);

- /* Note windows_wait returns TARGET_WAITKIND_SPURIOUS for thread
- events. */
- if (status.kind () != TARGET_WAITKIND_LOADED
- && status.kind () != TARGET_WAITKIND_SPURIOUS)
+ /* These result in an error being thrown before we get here. */
+ gdb_assert (status.kind () != TARGET_WAITKIND_EXITED
+ && status.kind () != TARGET_WAITKIND_SIGNALLED);
+
+ /* We may also see TARGET_WAITKIND_THREAD_EXITED if
+ target_thread_events is active (because another thread was
+ stepping earlier, for example). Ignore such events until we
+ see the initial breakpoint. */
+
+ if (status.kind () == TARGET_WAITKIND_STOPPED)
break;

/* Don't use windows_nat_target::resume here because that
assumes that inferior_ptid points at a valid thread, and we
haven't switched to any thread yet. */
- windows_continue (DBG_CONTINUE, -1);
+ windows_continue (DBG_CONTINUE, -1, WCONT_CONTINUE_DEBUG_EVENT);
}

switch_to_thread (this->find_thread (last_ptid));
@@ -2101,7 +2787,29 @@ windows_nat_target::attach (const char *args, int from_tty)
#endif

do_initial_windows_stuff (pid, 1);
- target_terminal::ours ();
+
+ if (target_is_non_stop_p ())
+ {
+ /* Leave all threads running. */
+
+ continue_last_debug_event_main_thread
+ (_("Failed to DBG_CONTINUE after attach"),
+ DBG_CONTINUE);
+
+ /* The thread that reports the initial breakpoint, and thus ends
+ up as selected thread here, was injected by Windows into the
+ program for the attach, and it exits as soon as we resume it.
+ Switch to the first thread in the inferior, otherwise the
+ user will be left with an exited thread selected. */
+ switch_to_thread (first_thread_of_inferior (current_inferior ()));
+ }
+ else
+ {
+ set_state (this, minus_one_ptid, THREAD_STOPPED);
+ set_internal_state (this, minus_one_ptid, THREAD_INT_STOPPED);
+
+ target_terminal::ours ();
+ }
}

void
@@ -2202,16 +2910,77 @@ windows_nat_target::break_out_process_thread (bool &process_alive)

DEBUG_EVENTS ("got unrelated event, code %u",
current_event.dwDebugEventCode);
- windows_continue (DBG_CONTINUE, -1, 0);
+
+ DWORD continue_status
+ = continue_status_for_event_detaching (current_event);
+ windows_continue (continue_status, -1, WCONT_CONTINUE_DEBUG_EVENT);
}

if (injected_thread_handle != NULL)
CHECK (CloseHandle (injected_thread_handle));
}

+
+/* Used while detaching. Decide whether to pass the exception or not.
+ Returns the dwContinueStatus argument to pass to
+ ContinueDebugEvent. */
+
+DWORD
+windows_nat_target::continue_status_for_event_detaching
+ (const DEBUG_EVENT &event, size_t *reply_later_events_left)
+{
+ ptid_t ptid (event.dwProcessId, event.dwThreadId, 0);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+
+ /* This can be a thread that we don't know about, as we're not
+ tracking thread creation events at this point. */
+ if (th != nullptr && th->reply_later != 0)
+ {
+ DWORD res = th->reply_later;
+ th->reply_later = 0;
+ if (reply_later_events_left != nullptr)
+ (*reply_later_events_left)--;
+ return res;
+ }
+ else if (event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ /* As the user asked to detach already, any new exception not
+ seen by infrun before, is passed down to the inferior without
+ considering "handle SIG pass/nopass". We can just pretend
+ the exception was raised after the inferior was detached. */
+ return DBG_EXCEPTION_NOT_HANDLED;
+ }
+ else
+ return DBG_CONTINUE;
+}
+
void
windows_nat_target::detach (inferior *inf, int from_tty)
{
+ DWORD continue_status = DBG_CONTINUE;
+
+ /* For any thread the core hasn't resumed, call prepare_resume with
+ the signal that the thread would be resumed with, so that we set
+ the right reply_later value, and also, so that we clear the trace
+ flag. */
+ for (thread_info *tp : inf->non_exited_threads ())
+ {
+ if (tp->internal_state () != THREAD_INT_RUNNING)
+ {
+ windows_thread_info *wth = windows_process.find_thread (tp->ptid);
+ gdb_signal signo = get_detach_signal (this, tp->ptid);
+
+ if (signo != wth->last_sig
+ || (signo != GDB_SIGNAL_0 && !signal_pass_state (signo)))
+ signo = GDB_SIGNAL_0;
+
+ DWORD cstatus = prepare_resume (wth, tp, 0, signo);
+
+ if (!m_continued && tp->ptid == get_last_debug_event_ptid ())
+ continue_status = cstatus;
+ }
+ }
+
/* If we see the process exit while unblocking the process_thread
helper thread, then we should skip the actual
DebugActiveProcessStop call. But don't report an error. Just
@@ -2219,20 +2988,76 @@ windows_nat_target::detach (inferior *inf, int from_tty)
bool process_alive = true;

/* The process_thread helper thread will be blocked in
- WaitForDebugEvent waiting for events if we've resumed the target
- before we get here, e.g., with "attach&" or "c&". We need to
- unblock it so that we can have it call DebugActiveProcessStop
- below, in the do_synchronously block. */
+ WaitForDebugEvent waiting for events if we're in non-stop mode,
+ or if in all-stop and we've resumed the target before we get
+ here, e.g., with "attach&" or "c&". We need to unblock it so
+ that we can have it call DebugActiveProcessStop below, in the
+ do_synchronously block. */
if (m_continued)
- break_out_process_thread (process_alive);
+ {
+ break_out_process_thread (process_alive);
+
+ /* We're now either stopped at a thread exit event, or a process
+ exit event. */
+ continue_status = DBG_CONTINUE;
+ }

- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (continue_status, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);

std::optional<unsigned> err;
if (process_alive)
do_synchronously ([&] ()
{
- if (!DebugActiveProcessStop (windows_process.process_id))
+ /* The kernel re-raises any exception previously intercepted
+ and deferred with DBG_REPLY_LATER in the inferior after we
+ detach. We need to flush those, and suppress those which
+ aren't meant to be seen by the inferior (e.g., breakpoints,
+ single-steps, any with matching "handle SIG nopass", etc.),
+ otherwise the inferior dies immediately after the detach,
+ due to an unhandled exception. */
+ DEBUG_EVENT event;
+
+ /* Count how many threads have pending reply-later events. */
+ size_t reply_later_events_left = 0;
+ for (auto *th : all_windows_threads ())
+ if (th->reply_later != 0)
+ reply_later_events_left++;
+
+ DEBUG_EVENTS ("flushing %zu reply-later events",
+ reply_later_events_left);
+
+ /* Note we have to use a blocking wait (hence the need for the
+ counter). Just polling (timeout=0) until WaitForDebugEvent
+ returns false would be racy -- the kernel may take a little
+ bit to put the events in the pending queue. That has been
+ observed on Windows 11, where detaching would still very
+ occasionally result in the inferior dying after the detach
+ due to a reply-later event. */
+ while (reply_later_events_left > 0
+ && wait_for_debug_event (&event, INFINITE))
+ {
+ DEBUG_EVENTS ("flushed kernel event code %u",
+ event.dwDebugEventCode);
+
+ DWORD cstatus = (continue_status_for_event_detaching
+ (event, &reply_later_events_left));
+ if (!continue_last_debug_event (cstatus, debug_events))
+ {
+ err = (unsigned) GetLastError ();
+ return false;
+ }
+
+ if (event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ DEBUG_EVENTS ("got EXIT_PROCESS_DEBUG_EVENT, skipping detach");
+ process_alive = false;
+ break;
+ }
+ }
+
+ if (process_alive
+ && !DebugActiveProcessStop (windows_process.process_id))
err = (unsigned) GetLastError ();
else
DebugSetProcessKillOnExit (FALSE);
@@ -2972,13 +3797,32 @@ windows_nat_target::create_inferior (const char *exec_file,

do_initial_windows_stuff (pi.dwProcessId, 0);

- /* windows_continue (DBG_CONTINUE, -1); */
+ /* Present the initial thread as stopped to the core. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+
+ th->suspend ();
+ set_state (this, inferior_ptid, THREAD_STOPPED);
+ set_internal_state (this, inferior_ptid, THREAD_INT_STOPPED);
+
+ if (target_is_non_stop_p ())
+ {
+ /* In non-stop mode, we always immediately use DBG_REPLY_LATER
+ on threads as soon as they report an event. However, during
+ the initial startup, windows_nat_target::wait does not do
+ this, so we need to handle it here for the initial
+ thread. */
+ th->reply_later = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
}

void
windows_nat_target::mourn_inferior ()
{
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (DBG_CONTINUE, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);
x86_cleanup_dregs();
if (windows_process.open_process_used)
{
@@ -3028,19 +3872,55 @@ windows_xfer_memory (gdb_byte *readbuf, const gdb_byte *writebuf,
return success ? TARGET_XFER_OK : TARGET_XFER_E_IO;
}

+/* Return true if all the threads of the process have already
+ exited. */
+
+static bool
+already_dead ()
+{
+ for (windows_thread_info *th : all_windows_threads ())
+ if (th->h != nullptr)
+ return false;
+ return true;
+}
+
void
windows_nat_target::kill ()
{
+ /* If all the threads of the process have already exited, there is
+ really nothing to kill. This can happen with e.g., scheduler
+ locking, where the thread exit events for all threads are still
+ pending to be processed by the core. */
+ if (already_dead ())
+ {
+ target_mourn_inferior (inferior_ptid);
+ return;
+ }
+
CHECK (TerminateProcess (windows_process.handle, 0));

+ /* In non-stop mode, windows_continue does not call
+ ContinueDebugEvent by default. This behavior is appropriate for
+ the first call to windows_continue because any thread that is
+ stopped has already been ContinueDebugEvent'ed with
+ DBG_REPLY_LATER. However, after the first
+ wait_for_debug_event_main_thread call in the loop, this will no
+ longer be true.
+
+ In all-stop mode, the WCONT_CONTINUE_DEBUG_EVENT flag has no
+ effect, so writing the code in this way ensures that the code is
+ the same for both modes. */
+ windows_continue_flags flags = WCONT_KILLED;
+
for (;;)
{
- if (!windows_continue (DBG_CONTINUE, -1, WCONT_KILLED))
+ if (!windows_continue (DBG_CONTINUE, -1, flags))
break;
DEBUG_EVENT current_event;
wait_for_debug_event_main_thread (&current_event);
if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
+ flags |= WCONT_CONTINUE_DEBUG_EVENT;
}

target_mourn_inferior (inferior_ptid); /* Or just windows_mourn_inferior? */
@@ -3175,6 +4055,16 @@ windows_nat_target::thread_name (struct thread_info *thr)
}

+/* Implementation of the target_ops::supports_non_stop method. */
+
+bool
+windows_nat_target::supports_non_stop ()
+{
+ /* Non-stop support requires DBG_REPLY_LATER, which only exists on
+ Windows 10 and later. */
+ return dbg_reply_later_available ();
+}
+
void _initialize_windows_nat ();
void
_initialize_windows_nat ()
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 1d3b24540d6..7f5641bd265 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -344,8 +344,9 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)

the_target->wait (minus_one_ptid, &status, 0);

- /* Note win32_wait doesn't return thread events. */
- if (status.kind () != TARGET_WAITKIND_LOADED)
+ if (status.kind () == TARGET_WAITKIND_EXITED
+ || status.kind () == TARGET_WAITKIND_SIGNALLED
+ || status.kind () == TARGET_WAITKIND_STOPPED)
{
windows_process.cached_status = status;
break;
@@ -604,7 +605,7 @@ win32_process_target::attach (unsigned long pid)

/* See nat/windows-nat.h. */

-DWORD
+bool
gdbserver_windows_process::handle_output_debug_string
(const DEBUG_EVENT &current_event,
struct target_waitstatus *ourstatus)
@@ -615,7 +616,7 @@ gdbserver_windows_process::handle_output_debug_string
DWORD nbytes = current_event.u.DebugString.nDebugStringLength;

if (nbytes == 0)
- return 0;
+ return false;

if (nbytes > READ_BUFFER_LEN)
nbytes = READ_BUFFER_LEN;
@@ -634,7 +635,7 @@ gdbserver_windows_process::handle_output_debug_string
else
{
if (read_inferior_memory (addr, (unsigned char *) s, nbytes) != 0)
- return 0;
+ return false;
}

if (!startswith (s, "cYg"))
@@ -642,14 +643,14 @@ gdbserver_windows_process::handle_output_debug_string
if (!server_waiting)
{
OUTMSG2(("%s", s));
- return 0;
+ return false;
}

monitor_output (s);
}
#undef READ_BUFFER_LEN

- return 0;
+ return false;
}

static void
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index 123fbc76f57..60ad00b9c0a 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -182,8 +182,8 @@ class win32_process_target : public process_stratum_target
struct gdbserver_windows_process : public windows_nat::windows_process_info
{
windows_nat::windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT &current_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT &current_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT &current_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
--
2.49.0