* [PATCH v3 00/11] Windows non-stop mode
@ 2026-04-29 20:14 Pedro Alves
2026-04-29 20:14 ` [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
` (13 more replies)
0 siblings, 14 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:14 UTC (permalink / raw)
To: gdb-patches
Here is the third iteration of the Windows non-stop work.
I've pushed the series to the users/palves/windows-non-stop-v3 branch
on sourceware.org, for your convenience.
Compared to the previous version, here:
https://inbox.sourceware.org/gdb-patches/20250519132308.3553663-1-pedro@palves.net
... this one is much smaller, as most of the preparatory changes have
been merged already. For the patches that still remain, not much has
changed, other than rebasing and resolving conflicts, and addressing a
couple minor review comments.
I've added "Approved-By:" tags from last year's review.
Here's the original intro blurb, still accurate:
This series adds non-stop mode support to the Windows native backend,
on Windows 10 and above. Earlier Windows versions lack the necessary
feature, so those keep working in all-stop mode, only.
After the series, the Windows target backend defaults to working in
non-stop mode (as in, "maint set target-non-stop"), even if
user-visible mode is all-stop ("set non-stop off"). This is the same
as the Linux backend.
I've been testing this on Cygwin native with the GDB testsuite as I've
been developing this. Running the testsuite on Cygwin is a pain, and
many testcases run into cascading timeouts still, and some even hang
the test run forever until you kill them manually. I've got another
series of patches to improve such tests and skip others, and that's
what I've been testing with. I've also tested the series with the
windows-nat backend forced to all-stop mode.
Pedro Alves (11):
Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available
linux-nat: Factor out get_detach_signal code to common code
Windows GDB: make windows_thread_info be private thread_info data
Introduce windows_nat::event_code_to_string
Windows gdb: Add non-stop support
Windows gdb: Watchpoints while running (internal vs external stops)
Windows gdb: extra thread info => show exiting
Add gdb.threads/leader-exit-schedlock.exp
infrun: with AS+NS, prefer process exit over thread exit
Windows gdb: Always non-stop (default to "maint set target-non-stop
on")
Mention Windows non-stop support in NEWS
gdb/NEWS | 3 +
gdb/aarch64-windows-nat.c | 13 +-
gdb/infrun.c | 246 +++-
gdb/infrun.h | 6 +
gdb/linux-nat.c | 41 +-
gdb/nat/windows-nat.c | 62 +-
gdb/nat/windows-nat.h | 83 +-
gdb/target/waitstatus.h | 4 +-
.../gdb.threads/leader-exit-schedlock.c | 56 +
.../gdb.threads/leader-exit-schedlock.exp | 215 +++
.../gdb.threads/step-over-process-exit.c | 49 +
.../gdb.threads/step-over-process-exit.exp | 66 +
gdb/windows-nat.c | 1285 ++++++++++++++---
gdb/windows-nat.h | 142 +-
gdb/x86-windows-nat.c | 16 +-
gdbserver/win32-low.cc | 60 +-
gdbserver/win32-low.h | 4 +-
17 files changed, 1973 insertions(+), 378 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/leader-exit-schedlock.c
create mode 100644 gdb/testsuite/gdb.threads/leader-exit-schedlock.exp
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.c
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.exp
base-commit: 5ed455efd8476ce4f70266d5797ef9f3eb1e8919
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
@ 2026-04-29 20:14 ` Pedro Alves
2026-04-29 20:14 ` [PATCH v3 02/11] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
` (12 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:14 UTC (permalink / raw)
To: gdb-patches; +Cc: Hannes Domani, Eli Zaretskii, Tom Tromey
Per
<https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent>,
DBG_REPLY_LATER is "Supported in Windows 10, version 1507 or above, ..."
Since we support versions of Windows older than 10, we need to know
whether DBG_REPLY_LATER is available. And we need to know this before
starting any inferior.
This adds a function that probes for support (and caches the result),
by trying to call ContinueDebugEvent on pid=0,tid=0 with
DBG_REPLY_LATER, and inspecting the resulting error.
Suggested-by: Hannes Domani <ssbssa@yahoo.de>
Suggested-by: Eli Zaretskii <eliz@gnu.org>
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ia27b981aeecaeef430ec90cebc5b3abdce00449d
commit-id:9098a060
---
gdb/nat/windows-nat.c | 20 ++++++++++++++++++++
gdb/nat/windows-nat.h | 9 +++++++++
2 files changed, 29 insertions(+)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index c6434348607..cf30a66a0c1 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -917,6 +917,26 @@ disable_randomization_available ()
/* See windows-nat.h. */
+bool
+dbg_reply_later_available ()
+{
+ static int available = -1;
+ if (available == -1)
+ {
+ /* DBG_REPLY_LATER is supported since Windows 10, Version 1507.
+ If supported, this fails with ERROR_INVALID_HANDLE (tested on
+ Win10 and Win11). If not supported, it fails with
+ ERROR_INVALID_PARAMETER (tested on Win7). */
+ if (ContinueDebugEvent (0, 0, DBG_REPLY_LATER))
+ internal_error (_("ContinueDebugEvent call should not "
+ "have succeeded"));
+ available = (GetLastError () != ERROR_INVALID_PARAMETER);
+ }
+ return available;
+}
+
+/* See windows-nat.h. */
+
bool
initialize_loadable ()
{
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 45d186f771a..0a503232718 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -535,6 +535,15 @@ enum_process_modules (WOW64_CONTEXT *, HANDLE process,
}
#endif
+/* This is available starting with Windows 10. */
+#ifndef DBG_REPLY_LATER
+# define DBG_REPLY_LATER 0x40010001L
+#endif
+
+/* Return true if it's possible to use DBG_REPLY_LATER with
+ ContinueDebugEvent on this host. */
+extern bool dbg_reply_later_available ();
+
/* Load any functions which may not be available in ancient versions
of Windows. */
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 02/11] linux-nat: Factor out get_detach_signal code to common code
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-04-29 20:14 ` [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
@ 2026-04-29 20:14 ` Pedro Alves
2026-04-29 20:14 ` [PATCH v3 03/11] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
` (11 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:14 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
The Windows target backend will want to do most of what the
get_detach_signal function in gdb/linux-nat.c does, except for the
Linux-specific bits. This commit moves the code that is shareable to
infrun.c, so that other targets can use it too.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ifaa96b4a41bb83d868079af4d47633715c0e1940
commit-id:dac5b3f8
---
gdb/infrun.c | 37 +++++++++++++++++++++++++++++++++++++
gdb/infrun.h | 6 ++++++
gdb/linux-nat.c | 41 +++++------------------------------------
3 files changed, 48 insertions(+), 36 deletions(-)
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 0e359f0ed74..aed66bf844e 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -366,6 +366,43 @@ update_signals_program_target (void)
target_program_signals (signal_program);
}
+/* See infrun.h. */
+
+gdb_signal
+get_detach_signal (process_stratum_target *proc_target, ptid_t ptid)
+{
+ thread_info *tp = proc_target->find_thread (ptid);
+ gdb_signal signo = GDB_SIGNAL_0;
+
+ if (target_is_non_stop_p ()
+ && tp->internal_state () != THREAD_INT_RUNNING)
+ {
+ if (tp->has_pending_waitstatus ())
+ {
+ /* If the thread has a pending event, and it was stopped
+ with a signal, use that signal to resume it. If it has a
+ pending event of another kind, it was not stopped with a
+ signal, so resume it without a signal. */
+ if (tp->pending_waitstatus ().kind () == TARGET_WAITKIND_STOPPED)
+ signo = tp->pending_waitstatus ().sig ();
+ }
+ else
+ signo = tp->stop_signal ();
+ }
+ else if (!target_is_non_stop_p ())
+ {
+ ptid_t last_ptid;
+ process_stratum_target *last_target;
+
+ get_last_target_status (&last_target, &last_ptid, nullptr);
+
+ if (last_target == proc_target && ptid == last_ptid)
+ signo = tp->stop_signal ();
+ }
+
+ return signo;
+}
+
/* Value to pass to target_resume() to cause all threads to resume. */
#define RESUME_ALL minus_one_ptid
diff --git a/gdb/infrun.h b/gdb/infrun.h
index f15662d5bc9..42c867ce303 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -320,6 +320,12 @@ extern void all_uis_on_sync_execution_starting (void);
detach. */
extern void restart_after_all_stop_detach (process_stratum_target *proc_target);
+/* While detaching, return the signal PTID was supposed to be resumed
+ with, if it were resumed, so we can pass it down to PTID while
+ detaching. */
+extern gdb_signal get_detach_signal (process_stratum_target *proc_target,
+ ptid_t ptid);
+
/* RAII object to temporarily disable the requirement for target
stacks to commit their resumed threads.
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index d7d5e010748..868f08e18fb 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1320,13 +1320,13 @@ detach_one_pid (int pid, int signo)
pid, strsignal (signo));
}
-/* Get pending signal of THREAD as a host signal number, for detaching
+/* Get pending signal of LP as a host signal number, for detaching
purposes. This is the signal the thread last stopped for, which we
need to deliver to the thread when detaching, otherwise, it'd be
suppressed/lost. */
static int
-get_detach_signal (struct lwp_info *lp)
+get_lwp_detach_signal (struct lwp_info *lp)
{
enum gdb_signal signo = GDB_SIGNAL_0;
@@ -1356,38 +1356,7 @@ get_detach_signal (struct lwp_info *lp)
else if (lp->status)
signo = gdb_signal_from_host (WSTOPSIG (lp->status));
else
- {
- thread_info *tp = linux_target->find_thread (lp->ptid);
-
- if (target_is_non_stop_p ()
- && tp->internal_state () != THREAD_INT_RUNNING)
- {
- if (tp->has_pending_waitstatus ())
- {
- /* If the thread has a pending event, and it was stopped with a
- signal, use that signal to resume it. If it has a pending
- event of another kind, it was not stopped with a signal, so
- resume it without a signal. */
- if (tp->pending_waitstatus ().kind () == TARGET_WAITKIND_STOPPED)
- signo = tp->pending_waitstatus ().sig ();
- else
- signo = GDB_SIGNAL_0;
- }
- else
- signo = tp->stop_signal ();
- }
- else if (!target_is_non_stop_p ())
- {
- ptid_t last_ptid;
- process_stratum_target *last_target;
-
- get_last_target_status (&last_target, &last_ptid, nullptr);
-
- if (last_target == linux_target
- && lp->ptid.lwp () == last_ptid.lwp ())
- signo = tp->stop_signal ();
- }
- }
+ signo = get_detach_signal (linux_target, lp->ptid);
if (signo == GDB_SIGNAL_0)
{
@@ -1517,7 +1486,7 @@ detach_one_lwp (struct lwp_info *lp, int *signo_p)
if (signo_p == NULL)
{
/* Pass on any pending signal for this LWP. */
- signo = get_detach_signal (lp);
+ signo = get_lwp_detach_signal (lp);
}
else
signo = *signo_p;
@@ -1604,7 +1573,7 @@ linux_nat_target::detach (inferior *inf, int from_tty)
if (main_lwp != nullptr)
{
/* Pass on any pending signal for the last LWP. */
- int signo = get_detach_signal (main_lwp);
+ int signo = get_lwp_detach_signal (main_lwp);
detach_one_lwp (main_lwp, &signo);
}
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 03/11] Windows GDB: make windows_thread_info be private thread_info data
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-04-29 20:14 ` [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
2026-04-29 20:14 ` [PATCH v3 02/11] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
@ 2026-04-29 20:14 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 04/11] Introduce windows_nat::event_code_to_string Pedro Alves
` (10 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:14 UTC (permalink / raw)
To: gdb-patches
With Windows non-stop support, we'll add support for
target_thread_events to the Windows target.
When that is supported, and the core wants to be notified of thread
exit events, the target backend does not delete the thread for which
the event is being reported. Instead, infrun takes care of that.
That causes one problem on Windows, which is that Windows maintains
its own separate Windows threads list, in parallel with the struct
thread_info thread list maintained by the core. In the
target_thread_events events scenario, when infrun deletes the thread,
the corresponding object in the Windows backend thread list is left
dangling, causing problems.
Fix this by eliminating the parallel thread list from the Windows
backend, instead making the windows_thread_info data by registered as
the private data associated with thread_info, like other targets do.
It also adds a all_windows_threads walker function, and associated
range and iterator classes, so that most of the Windows target code
can iterate over Windows threads without having to worry about
fetching the Windows thread data out of thread_info's private data.
Change-Id: I5058969b46e0dd238c89b6c28403c1848f083683
---
gdb/target/waitstatus.h | 4 +-
gdb/windows-nat.c | 75 +++++++++++--------------------
gdb/windows-nat.h | 99 ++++++++++++++++++++++++++++++++++++++++-
gdb/x86-windows-nat.c | 4 +-
4 files changed, 129 insertions(+), 53 deletions(-)
diff --git a/gdb/target/waitstatus.h b/gdb/target/waitstatus.h
index cbdaa026218..3a1adcc6256 100644
--- a/gdb/target/waitstatus.h
+++ b/gdb/target/waitstatus.h
@@ -105,7 +105,9 @@ enum target_waitkind
/* The thread was created. */
TARGET_WAITKIND_THREAD_CREATED,
- /* The thread has exited. The exit status is in value.integer. */
+ /* The thread has exited. The exit status is in value.integer. The
+ thread should still exist in the thread list when infrun receives
+ this event. */
TARGET_WAITKIND_THREAD_EXITED,
};
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 1dfe2df389d..4029e7dad00 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -272,15 +272,23 @@ windows_nat_target::continue_last_debug_event_main_thread
m_continued = !last_call;
}
+/* Return a pointer to the windows-nat target instance. */
+
+static windows_nat_target *
+get_windows_nat_target ()
+{
+ return gdb::checked_static_cast<windows_nat_target *> (get_native_target ());
+}
+
/* See nat/windows-nat.h. */
windows_thread_info *
windows_per_inferior::find_thread (ptid_t ptid)
{
- for (auto &th : thread_list)
- if (th->tid == ptid.lwp ())
- return th.get ();
- return nullptr;
+ thread_info *thr = get_windows_nat_target ()->find_thread (ptid);
+ if (thr == nullptr)
+ return nullptr;
+ return as_windows_thread_info (thr);
}
/* See nat/windows-nat.h. */
@@ -297,12 +305,11 @@ windows_thread_info *
windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
bool main_thread_p)
{
- windows_thread_info *th;
-
gdb_assert (ptid.lwp () != 0);
- if ((th = windows_process->find_thread (ptid)))
- return th;
+ windows_thread_info *existing = windows_process->find_thread (ptid);
+ if (existing != nullptr)
+ return existing;
CORE_ADDR base = (CORE_ADDR) (uintptr_t) tlb;
#ifdef __x86_64__
@@ -311,18 +318,18 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
if (windows_process->wow64_process)
base += 0x2000;
#endif
- th = new windows_thread_info (windows_process, ptid.lwp (), h, base);
- windows_process->thread_list.emplace_back (th);
+ windows_private_thread_info *th
+ = new windows_private_thread_info (windows_process, ptid.lwp (), h, base);
/* Add this new thread to the list of threads.
To be consistent with what's done on other platforms, we add
the main thread silently (in reality, this thread is really
more of a process to the user than a thread). */
- if (main_thread_p)
- add_thread_silent (this, ptid);
- else
- ::add_thread (this, ptid);
+ thread_info *gth = (main_thread_p
+ ? ::add_thread_silent (this, ptid)
+ : ::add_thread (this, ptid));
+ gth->priv.reset (th);
/* It's simplest to always set this and update the debug
registers. */
@@ -331,15 +338,6 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
return th;
}
-/* Clear out any old thread list and reinitialize it to a
- pristine state. */
-static void
-windows_init_thread_list (void)
-{
- DEBUG_EVENTS ("called");
- windows_process->thread_list.clear ();
-}
-
/* Delete a thread from the list of threads.
PTID is the ptid of the thread to be deleted.
@@ -351,12 +349,6 @@ void
windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
bool main_thread_p)
{
- DWORD id;
-
- gdb_assert (ptid.lwp () != 0);
-
- id = ptid.lwp ();
-
/* Note that no notification was printed when the main thread was
created, and thus, unless in verbose mode, we should be symmetrical,
and avoid an exit notification for the main thread here as well. */
@@ -364,16 +356,6 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
bool silent = (main_thread_p && !info_verbose);
thread_info *to_del = this->find_thread (ptid);
delete_thread_with_exit_code (to_del, exit_code, silent);
-
- auto iter = std::find_if (windows_process->thread_list.begin (),
- windows_process->thread_list.end (),
- [=] (std::unique_ptr<windows_thread_info> &th)
- {
- return th->tid == id;
- });
-
- if (iter != windows_process->thread_list.end ())
- windows_process->thread_list.erase (iter);
}
void
@@ -712,7 +694,7 @@ BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- for (auto &th : windows_process->thread_list)
+ for (auto *th : all_windows_threads ())
{
if ((id == -1 || id == (int) th->tid)
&& !th->suspended
@@ -730,9 +712,9 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
}
}
- for (auto &th : windows_process->thread_list)
+ for (auto *th : all_windows_threads ())
if (id == -1 || id == (int) th->tid)
- continue_one_thread (th.get (), cont_flags);
+ continue_one_thread (th, cont_flags);
continue_last_debug_event_main_thread
(_("Failed to resume program execution"), continue_status,
@@ -914,7 +896,7 @@ 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 : windows_process->thread_list)
+ for (auto *th : all_windows_threads ())
{
if (!th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
@@ -927,7 +909,7 @@ windows_nat_target::get_windows_debug_event
*current_event = th->last_event;
ptid_t ptid (windows_process->process_id, thread_id);
- windows_process->invalidate_thread_context (th.get ());
+ windows_process->invalidate_thread_context (th);
return ptid;
}
}
@@ -1226,7 +1208,7 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
/* All-stop, suspend all threads until they are
explicitly resumed. */
- for (auto &thr : windows_process->thread_list)
+ for (auto *thr : all_windows_threads ())
thr->suspend ();
}
@@ -1382,7 +1364,6 @@ windows_nat_target::attach (const char *args, int from_tty)
warning ("Failed to get SE_DEBUG_NAME privilege\n"
"This can cause attach to fail on Windows NT/2K/XP");
- windows_init_thread_list ();
windows_process->saw_create = 0;
std::optional<unsigned> err;
@@ -2142,7 +2123,6 @@ windows_nat_target::create_inferior (const char *exec_file,
}
}
- windows_init_thread_list ();
do_synchronously ([&] ()
{
BOOL ok = create_process (nullptr, args, flags, w32_env,
@@ -2271,7 +2251,6 @@ windows_nat_target::create_inferior (const char *exec_file,
/* Final nil string to terminate new env. */
*temp = 0;
- windows_init_thread_list ();
do_synchronously ([&] ()
{
BOOL ok = create_process (nullptr, /* image */
diff --git a/gdb/windows-nat.h b/gdb/windows-nat.h
index 235844a7c95..d63ef6e7b33 100644
--- a/gdb/windows-nat.h
+++ b/gdb/windows-nat.h
@@ -77,6 +77,38 @@ enum windows_continue_flag
DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
+/* We want to register windows_thread_info as struct thread_info
+ private data. thread_info::priv must point to a class that
+ inherits from private_thread_info. But we can't make
+ windows_thread_info inherit private_thread_info, because
+ windows_thread_info is shared with GDBserver. So we make a new
+ class that inherits from both private_thread_info,
+ windows_thread_info, and register that one as thread_info::private.
+ This multiple inheritance is benign, because private_thread_info is
+ a java-style interface class with no data. */
+struct windows_private_thread_info : private_thread_info, windows_thread_info
+{
+ windows_private_thread_info (windows_nat::windows_process_info *proc,
+ DWORD tid, HANDLE h, CORE_ADDR tlb)
+ : windows_thread_info (proc, tid, h, tlb)
+ {}
+
+ ~windows_private_thread_info () override
+ {}
+};
+
+/* Get the windows_thread_info object associated with THR. */
+
+static inline windows_thread_info *
+as_windows_thread_info (thread_info *thr)
+{
+ /* Cast to windows_private_thread_info, which inherits from
+ private_thread_info, and is implicitly convertible to
+ windows_thread_info, the return type. */
+ private_thread_info *priv = thr->priv.get ();
+ return gdb::checked_static_cast<windows_private_thread_info *> (priv);
+}
+
struct windows_per_inferior : public windows_nat::windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
@@ -91,8 +123,6 @@ struct windows_per_inferior : public windows_nat::windows_process_info
int windows_initialization_done = 0;
- std::vector<std::unique_ptr<windows_thread_info>> thread_list;
-
/* Counts of things. */
int saw_create = 0;
int open_process_used = 0;
@@ -352,4 +382,69 @@ int amd64_windows_segment_register_p (int regnum);
extern const int amd64_mappings[];
#endif
+/* Creates an iterator that works like all_matching_threads_iterator,
+ but that returns windows_thread_info pointers instead of
+ thread_info. This could be replaced with a std::range::transform
+ when we require C++20. */
+class all_windows_threads_iterator
+{
+public:
+ typedef all_windows_threads_iterator self_type;
+ typedef windows_thread_info value_type;
+ typedef windows_thread_info *&reference;
+ typedef windows_thread_info **pointer;
+ typedef std::forward_iterator_tag iterator_category;
+ typedef int difference_type;
+
+ explicit all_windows_threads_iterator (all_non_exited_threads_iterator base_iter)
+ : m_base_iter (base_iter)
+ {}
+
+ windows_thread_info * operator* () const { return as_windows_thread_info (&*m_base_iter); }
+
+ all_windows_threads_iterator &operator++ ()
+ {
+ ++m_base_iter;
+ return *this;
+ }
+
+ bool operator== (const all_windows_threads_iterator &other) const
+ { return m_base_iter == other.m_base_iter; }
+
+ bool operator!= (const all_windows_threads_iterator &other) const
+ { return !(*this == other); }
+
+private:
+ all_non_exited_threads_iterator m_base_iter;
+};
+
+/* The range for all_windows_threads, below. */
+
+class all_windows_threads_range : public all_non_exited_threads_range
+{
+public:
+ all_windows_threads_range (all_non_exited_threads_range base_range)
+ : m_base_range (base_range)
+ {}
+
+ all_windows_threads_iterator begin () const
+ { return all_windows_threads_iterator (m_base_range.begin ()); }
+ all_windows_threads_iterator end () const
+ { return all_windows_threads_iterator (m_base_range.end ()); }
+
+private:
+ all_non_exited_threads_range m_base_range;
+};
+
+/* Return a range that can be used to walk over all non-exited Windows
+ threads of all inferiors, with range-for. */
+
+static inline all_windows_threads_range
+all_windows_threads ()
+{
+ auto *win_tgt = static_cast<windows_nat_target *> (get_native_target ());
+ return (all_windows_threads_range
+ (all_non_exited_threads_range (win_tgt, minus_one_ptid)));
+}
+
#endif /* GDB_WINDOWS_NAT_H */
diff --git a/gdb/x86-windows-nat.c b/gdb/x86-windows-nat.c
index 5a4876b7fbe..ccabe80f9b4 100644
--- a/gdb/x86-windows-nat.c
+++ b/gdb/x86-windows-nat.c
@@ -346,7 +346,7 @@ windows_set_dr (int i, CORE_ADDR addr)
if (i < 0 || i > 3)
internal_error (_("Invalid register %d in windows_set_dr.\n"), i);
- for (auto &th : x86_windows_process.thread_list)
+ for (auto *th : all_windows_threads ())
th->debug_registers_changed = true;
}
@@ -356,7 +356,7 @@ windows_set_dr (int i, CORE_ADDR addr)
static void
windows_set_dr7 (unsigned long val)
{
- for (auto &th : x86_windows_process.thread_list)
+ for (auto *th : all_windows_threads ())
th->debug_registers_changed = true;
}
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 04/11] Introduce windows_nat::event_code_to_string
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (2 preceding siblings ...)
2026-04-29 20:14 ` [PATCH v3 03/11] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 05/11] Windows gdb: Add non-stop support Pedro Alves
` (9 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Instead of:
switch (event_code)
{
case FOO_DEBUG_EVENT:
DEBUG_EVENTS (..., "FOO_DEBUG_EVENT");
...
case BAR_DEBUG_EVENT:
DEBUG_EVENTS (..., "BAR_DEBUG_EVENT");
...
... with one DEBUG_EVENTS call per event type, log the event just once
before the switch, and introduce a new event_code_to_string function
to handle the event code to string conversion.
Do the same on GDB's and gdbserver's Windows backends.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Id38b7e30df182e4742f3179538de3c643cf42668
commit-id:a8abf6a6
---
gdb/nat/windows-nat.c | 25 +++++++++++++++++++++++
gdb/nat/windows-nat.h | 4 ++++
gdb/windows-nat.c | 37 +++++-----------------------------
gdbserver/win32-low.cc | 45 +++++-------------------------------------
4 files changed, 39 insertions(+), 72 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index cf30a66a0c1..72576222b8d 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -694,6 +694,31 @@ windows_process_info::add_all_dlls ()
/* See nat/windows-nat.h. */
+std::string
+event_code_to_string (DWORD event_code)
+{
+#define CASE(X) \
+ case X: return #X
+
+ switch (event_code)
+ {
+ CASE (CREATE_THREAD_DEBUG_EVENT);
+ CASE (EXIT_THREAD_DEBUG_EVENT);
+ CASE (CREATE_PROCESS_DEBUG_EVENT);
+ CASE (EXIT_PROCESS_DEBUG_EVENT);
+ CASE (LOAD_DLL_DEBUG_EVENT);
+ CASE (UNLOAD_DLL_DEBUG_EVENT);
+ CASE (EXCEPTION_DEBUG_EVENT);
+ CASE (OUTPUT_DEBUG_STRING_EVENT);
+ default:
+ return string_printf ("unknown event code %u", (unsigned) event_code);
+ }
+
+#undef CASE
+}
+
+/* See nat/windows-nat.h. */
+
ptid_t
get_last_debug_event_ptid ()
{
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 0a503232718..364a01c5724 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -285,6 +285,10 @@ struct windows_process_info
int get_exec_module_filename (char *exe_name_ret, size_t exe_name_max_len);
};
+/* Return a string version of EVENT_CODE. */
+
+extern std::string event_code_to_string (DWORD event_code);
+
/* A simple wrapper for ContinueDebugEvent that continues the last
waited-for event. If DEBUG_EVENTS is true, logging will be
enabled. */
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 4029e7dad00..67e4d35f96f 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -927,13 +927,14 @@ windows_nat_target::get_windows_debug_event
event_code = current_event->dwDebugEventCode;
ourstatus->set_spurious ();
+ DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
+ (unsigned) current_event->dwProcessId,
+ (unsigned) current_event->dwThreadId,
+ event_code_to_string (event_code).c_str ());
+
switch (event_code)
{
case CREATE_THREAD_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "CREATE_THREAD_DEBUG_EVENT");
if (windows_process->saw_create != 1)
{
inferior *inf = find_inferior_pid (this, current_event->dwProcessId);
@@ -965,10 +966,6 @@ windows_nat_target::get_windows_debug_event
break;
case EXIT_THREAD_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "EXIT_THREAD_DEBUG_EVENT");
delete_thread (ptid_t (current_event->dwProcessId,
current_event->dwThreadId, 0),
current_event->u.ExitThread.dwExitCode,
@@ -977,10 +974,6 @@ windows_nat_target::get_windows_debug_event
break;
case CREATE_PROCESS_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "CREATE_PROCESS_DEBUG_EVENT");
CloseHandle (current_event->u.CreateProcessInfo.hFile);
if (++windows_process->saw_create != 1)
break;
@@ -997,10 +990,6 @@ windows_nat_target::get_windows_debug_event
break;
case EXIT_PROCESS_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "EXIT_PROCESS_DEBUG_EVENT");
if (!windows_process->windows_initialization_done)
{
target_terminal::ours ();
@@ -1029,10 +1018,6 @@ windows_nat_target::get_windows_debug_event
break;
case LOAD_DLL_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "LOAD_DLL_DEBUG_EVENT");
CloseHandle (current_event->u.LoadDll.hFile);
if (windows_process->saw_create != 1
|| ! windows_process->windows_initialization_done)
@@ -1050,10 +1035,6 @@ windows_nat_target::get_windows_debug_event
break;
case UNLOAD_DLL_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "UNLOAD_DLL_DEBUG_EVENT");
if (windows_process->saw_create != 1
|| ! windows_process->windows_initialization_done)
break;
@@ -1070,10 +1051,6 @@ windows_nat_target::get_windows_debug_event
break;
case EXCEPTION_DEBUG_EVENT:
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "EXCEPTION_DEBUG_EVENT");
if (windows_process->saw_create != 1)
break;
switch (windows_process->handle_exception (*current_event,
@@ -1093,10 +1070,6 @@ windows_nat_target::get_windows_debug_event
break;
case OUTPUT_DEBUG_STRING_EVENT: /* Message from the kernel. */
- DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- "OUTPUT_DEBUG_STRING_EVENT");
if (windows_process->saw_create != 1)
break;
thread_id = windows_process->handle_output_debug_string (*current_event,
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 826d21867ae..8d24826d05c 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -997,13 +997,14 @@ get_child_debug_event (DWORD *continue_status,
}
}
+ OUTMSG2 (("gdbserver: kernel event %s for pid=%u tid=%x)\n",
+ event_code_to_string (current_event->dwDebugEventCode).c_str (),
+ (unsigned) current_event->dwProcessId,
+ (unsigned) current_event->dwThreadId));
+
switch (current_event->dwDebugEventCode)
{
case CREATE_THREAD_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event CREATE_THREAD_DEBUG_EVENT "
- "for pid=%u tid=%x)\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
/* Record the existence of this thread. */
child_add_thread (current_event->dwProcessId,
@@ -1013,10 +1014,6 @@ get_child_debug_event (DWORD *continue_status,
break;
case EXIT_THREAD_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event EXIT_THREAD_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
child_delete_thread (current_event->dwProcessId,
current_event->dwThreadId);
@@ -1024,10 +1021,6 @@ get_child_debug_event (DWORD *continue_status,
return 1;
case CREATE_PROCESS_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event CREATE_PROCESS_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
CloseHandle (current_event->u.CreateProcessInfo.hFile);
if (windows_process.open_process_used)
@@ -1047,10 +1040,6 @@ get_child_debug_event (DWORD *continue_status,
break;
case EXIT_PROCESS_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event EXIT_PROCESS_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
{
DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
/* If the exit status looks like a fatal exception, but we
@@ -1067,10 +1056,6 @@ get_child_debug_event (DWORD *continue_status,
break;
case LOAD_DLL_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event LOAD_DLL_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
CloseHandle (current_event->u.LoadDll.hFile);
if (! windows_process.child_initialization_done)
break;
@@ -1080,10 +1065,6 @@ get_child_debug_event (DWORD *continue_status,
break;
case UNLOAD_DLL_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event UNLOAD_DLL_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
if (! windows_process.child_initialization_done)
break;
windows_process.handle_unload_dll (*current_event);
@@ -1091,10 +1072,6 @@ get_child_debug_event (DWORD *continue_status,
break;
case EXCEPTION_DEBUG_EVENT:
- OUTMSG2 (("gdbserver: kernel event EXCEPTION_DEBUG_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
if (windows_process.handle_exception (*current_event,
ourstatus, debug_threads)
== HANDLE_EXCEPTION_UNHANDLED)
@@ -1103,20 +1080,8 @@ get_child_debug_event (DWORD *continue_status,
case OUTPUT_DEBUG_STRING_EVENT:
/* A message from the kernel (or Cygwin). */
- OUTMSG2 (("gdbserver: kernel event OUTPUT_DEBUG_STRING_EVENT "
- "for pid=%u tid=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId));
windows_process.handle_output_debug_string (*current_event, nullptr);
break;
-
- default:
- OUTMSG2 (("gdbserver: kernel event unknown "
- "for pid=%u tid=%x code=%x\n",
- (unsigned) current_event->dwProcessId,
- (unsigned) current_event->dwThreadId,
- (unsigned) current_event->dwDebugEventCode));
- break;
}
ptid = debug_event_ptid (current_event);
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 05/11] Windows gdb: Add non-stop support
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (3 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 04/11] Introduce windows_nat::event_code_to_string Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 06/11] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
` (8 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
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.
Acked-By: Tom Tromey <tom@tromey.com>
Change-Id: Id71aef461c43c244120635b5bedc638fe77c31fb
commit-id:bbf38a26
---
gdb/aarch64-windows-nat.c | 10 +-
gdb/nat/windows-nat.c | 17 +-
gdb/nat/windows-nat.h | 51 +-
gdb/windows-nat.c | 1083 +++++++++++++++++++++++++++++++++----
gdb/windows-nat.h | 31 +-
gdb/x86-windows-nat.c | 10 +-
gdbserver/win32-low.cc | 15 +-
gdbserver/win32-low.h | 4 +-
8 files changed, 1071 insertions(+), 150 deletions(-)
diff --git a/gdb/aarch64-windows-nat.c b/gdb/aarch64-windows-nat.c
index 2b082805708..e6b596a38cb 100644
--- a/gdb/aarch64-windows-nat.c
+++ b/gdb/aarch64-windows-nat.c
@@ -44,7 +44,7 @@ struct aarch64_windows_nat_target final
void cleanup_windows_arch () override;
void thread_context_continue (windows_thread_info *th, int killed) override;
- void thread_context_step (windows_thread_info *th) override;
+ void thread_context_step (windows_thread_info *th, bool step) override;
void fetch_one_register (struct regcache *regcache,
windows_thread_info *th, int r) override;
@@ -241,9 +241,13 @@ aarch64_windows_nat_target::thread_context_continue (windows_thread_info *th,
/* See windows-nat.h. */
void
-aarch64_windows_nat_target::thread_context_step (windows_thread_info *th)
+aarch64_windows_nat_target::thread_context_step (windows_thread_info *th,
+ bool enable)
{
- th->context.Cpsr |= 0x200000;
+ if (enable)
+ th->context.Cpsr |= 0x200000;
+ else
+ th->context.Cpsr &= ~0x200000;
}
/* See windows-nat.h. */
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 72576222b8d..b093acda342 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -728,17 +728,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 364a01c5724..f61eab766ac 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
@@ -181,15 +224,15 @@ struct windows_process_info
virtual void fill_thread_context (windows_thread_info *th) = 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 ¤t_event,
- struct target_waitstatus *ourstatus) = 0;
+ virtual bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) = 0;
/* Handle a DLL load event.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 67e4d35f96f..d56510c23f6 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -66,6 +66,202 @@
#include "gdbsupport/symbol.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;
/* The current process. */
@@ -335,6 +531,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;
}
@@ -589,12 +792,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 ¤t_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
@@ -631,15 +839,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",
@@ -647,7 +877,7 @@ windows_per_inferior::handle_output_debug_string
}
#endif
- return thread_id;
+ return false;
}
/* See nat/windows-nat.h. */
@@ -680,9 +910,20 @@ void
windows_nat_target::continue_one_thread (windows_thread_info *th,
windows_continue_flags cont_flags)
{
+ /* 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;
+
bool killed = (cont_flags & WCONT_KILLED) != 0;
thread_context_continue (th, killed);
+
th->resume ();
+ th->stopping = false;
th->last_sig = GDB_SIGNAL_0;
}
@@ -694,31 +935,76 @@ 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)
- continue_one_thread (th, cont_flags);
+ if (th->suspended
+#ifdef __CYGWIN__
+ && th->signaled_thread == nullptr
+#endif
+ && (id == -1 || id == (int) th->tid))
+ {
+ continue_one_thread (th, cont_flags);
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status,
- cont_flags & WCONT_LAST_CALL);
+#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);
+ 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
+ }
+
+ 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;
}
@@ -746,36 +1032,46 @@ windows_nat_target::fake_create_process (const DEBUG_EVENT ¤t_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;
+/* 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. */
- /* 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);
+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);
}
@@ -811,18 +1107,52 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
th->last_sig);
}
- /* Get context for currently selected thread. */
- 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));
- thread_context_step (th);
- }
+ /* 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 (inferior_thread ());
+ struct gdbarch *gdbarch = regcache->arch ();
+ fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
+ thread_context_step (th, step);
+
+ 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;
- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ 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);
@@ -871,18 +1201,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::pass_ctrlc ()
+windows_nat_target::stop_one_thread (windows_thread_info *th)
{
- interrupt ();
+ 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 ¤t_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
@@ -896,24 +1342,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_thread_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 ();
@@ -927,6 +1381,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,
@@ -960,17 +1486,29 @@ windows_nat_target::get_windows_debug_event
current_event->u.CreateThread.lpThreadLocalBase,
false /* main_thread_p */));
- /* This updates debug registers if necessary. */
- 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
+ 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:
@@ -999,9 +1537,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
@@ -1013,7 +1548,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;
@@ -1072,8 +1610,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:
@@ -1095,32 +1649,76 @@ 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
- && is_sw_breakpoint (¤t_event->u.Exception.ExceptionRecord)
- && 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
+ && is_sw_breakpoint (¤t_event->u.Exception.ExceptionRecord)
+ && 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;
}
@@ -1147,15 +1745,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,
¤t_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
@@ -1179,10 +1788,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
@@ -1234,22 +1873,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));
@@ -1400,7 +2048,21 @@ windows_nat_target::attach (const char *args, int from_tty)
is normally more useful to the user than the injected thread. */
switch_to_thread (first_thread_of_inferior (current_inferior ()));
- 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);
+ }
+ else
+ {
+ set_state (this, minus_one_ptid, THREAD_STOPPED);
+ set_internal_state (this, minus_one_ptid, THREAD_INT_STOPPED);
+
+ target_terminal::ours ();
+ }
}
void
@@ -1501,16 +2163,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 &thr : inf->non_exited_threads ())
+ {
+ if (thr.internal_state () != THREAD_INT_RUNNING)
+ {
+ windows_thread_info *wth = windows_process->find_thread (thr.ptid);
+ gdb_signal signo = get_detach_signal (this, thr.ptid);
+
+ if (signo != wth->last_sig
+ || (signo != GDB_SIGNAL_0 && !signal_pass_state (signo)))
+ signo = GDB_SIGNAL_0;
+
+ DWORD cstatus = prepare_resume (wth, &thr, 0, signo);
+
+ if (!m_continued && thr.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
@@ -1518,20 +2241,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);
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ /* We're now either stopped at a thread exit event, or a process
+ exit event. */
+ continue_status = DBG_CONTINUE;
+ }
+
+ 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);
@@ -2271,13 +3050,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);
cleanup_windows_arch ();
if (windows_process->open_process_used)
{
@@ -2327,19 +3125,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, 137));
+ /* 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 (¤t_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? */
@@ -2473,6 +3307,15 @@ windows_nat_target::thread_name (struct thread_info *thr)
return th->thread_name ();
}
+/* 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 ();
+}
INIT_GDB_FILE (windows_nat)
{
diff --git a/gdb/windows-nat.h b/gdb/windows-nat.h
index d63ef6e7b33..7ce71690e20 100644
--- a/gdb/windows-nat.h
+++ b/gdb/windows-nat.h
@@ -73,6 +73,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);
@@ -112,8 +117,8 @@ as_windows_thread_info (thread_info *thr)
struct windows_per_inferior : public windows_nat::windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT ¤t_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
@@ -211,6 +216,10 @@ struct windows_nat_target : public inf_child_target
void pass_ctrlc () override;
void stop (ptid_t ptid) 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;
@@ -240,6 +249,8 @@ struct windows_nat_target : public inf_child_target
return m_is_async;
}
+ bool supports_non_stop () override;
+
void async (bool enable) override;
int async_wait_fd () override
@@ -259,8 +270,8 @@ struct windows_nat_target : public inf_child_target
/* Prepare the thread context for continuing. */
virtual void thread_context_continue (windows_thread_info *th,
int killed) = 0;
- /* Set the stepping bit in the thread context. */
- virtual void thread_context_step (windows_thread_info *th) = 0;
+ /* Set or clear the stepping bit in the thread context. */
+ virtual void thread_context_step (windows_thread_info *th, bool enable) = 0;
/* Fetches register number R from the given windows_thread_info,
and supplies its value to the given regcache.
@@ -292,6 +303,15 @@ struct windows_nat_target : public inf_child_target
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT ¤t_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);
+
void continue_one_thread (windows_thread_info *th,
windows_continue_flags cont_flags);
@@ -358,6 +378,9 @@ struct windows_nat_target : public 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;
};
/* Check if Windows API call succeeds, and otherwise print error code
diff --git a/gdb/x86-windows-nat.c b/gdb/x86-windows-nat.c
index ccabe80f9b4..8ad9d3bedea 100644
--- a/gdb/x86-windows-nat.c
+++ b/gdb/x86-windows-nat.c
@@ -58,7 +58,7 @@ struct x86_windows_nat_target final : public x86_nat_target<windows_nat_target>
void cleanup_windows_arch () override;
void thread_context_continue (windows_thread_info *th, int killed) override;
- void thread_context_step (windows_thread_info *th) override;
+ void thread_context_step (windows_thread_info *th, bool step) override;
void fetch_one_register (struct regcache *regcache,
windows_thread_info *th, int r) override;
@@ -213,11 +213,15 @@ x86_windows_nat_target::thread_context_continue (windows_thread_info *th,
/* See windows-nat.h. */
void
-x86_windows_nat_target::thread_context_step (windows_thread_info *th)
+x86_windows_nat_target::thread_context_step (windows_thread_info *th,
+ bool enable)
{
x86_windows_process.with_context (th, [&] (auto *context)
{
- context->EFlags |= FLAG_TRACE_BIT;
+ if (enable)
+ context->EFlags |= FLAG_TRACE_BIT;
+ else
+ context->EFlags &= ~FLAG_TRACE_BIT;
});
}
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 8d24826d05c..469ff32f070 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -333,8 +333,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;
@@ -593,7 +594,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 ¤t_event,
struct target_waitstatus *ourstatus)
@@ -604,7 +605,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;
@@ -623,7 +624,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"))
@@ -631,14 +632,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 ff680492756..439adb84bc2 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -179,8 +179,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 ¤t_event,
- struct target_waitstatus *ourstatus) override;
+ bool handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 06/11] Windows gdb: Watchpoints while running (internal vs external stops)
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (4 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 05/11] Windows gdb: Add non-stop support Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 07/11] Windows gdb: extra thread info => show exiting Pedro Alves
` (7 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Teach the Windows target to temporarily pause all threads when we
change the debug registers for a watchpoint. Implements the same
logic as Linux uses:
~~~
/* (...) if threads are running when the
mirror changes, a temporary and transparent stop on all threads
is forced so they can get their copy of the debug registers
updated on re-resume. (...) */
~~~
On Linux, we send each thread a SIGSTOP to step them. On Windows,
SuspendThread itself doesn't cause any asynchronous debug event to be
reported. However, we've implemented windows_nat_target::stop such
that it uses SuspendThread, and then queues a pending GDB_SIGNAL_0
stop on the thread. That results in a user-visible stop, while here
we want a non-user-visible stop. So what we do is re-use that
windows_nat_target::stop stopping mechanism, but add an external vs
internal stopping kind distinction. An internal stop results in
windows_nat_target::wait immediately re-resuming the thread.
Note we don't make the debug registers poking code SuspendThread ->
write debug registers -> ContinueThread itself, because SuspendThread
is actually asynchronous and may take a bit to stop the thread (a
following GetThreadContext blocks until the thread is actually
suspended), and, there will be several debug register writes when a
watchpoint is set, because we have to set all of DR0, DR1, DR2, DR3,
and DR7. Defering the actual writes to ::wait avoids a bunch of
SuspendThread/ResumeThread sequences, so in principle should be
faster.
Reviewed-By: Tom Tromey <tom@tromey.com>
Change-Id: I39c2492c7aac06d23ef8f287f4afe3747b7bc53f
commit-id:22d7a7e0
---
gdb/aarch64-windows-nat.c | 3 +-
gdb/nat/windows-nat.h | 27 ++++++++--
gdb/windows-nat.c | 102 ++++++++++++++++++++++++++++++++------
gdb/windows-nat.h | 11 +++-
gdb/x86-windows-nat.c | 6 +--
5 files changed, 123 insertions(+), 26 deletions(-)
diff --git a/gdb/aarch64-windows-nat.c b/gdb/aarch64-windows-nat.c
index e6b596a38cb..29a6c95d485 100644
--- a/gdb/aarch64-windows-nat.c
+++ b/gdb/aarch64-windows-nat.c
@@ -317,8 +317,7 @@ aarch64_notify_debug_reg_change (ptid_t ptid,
= aarch64_get_debug_reg_state (inferior_ptid.pid ());
aarch64_windows_process.dr_state = *state;
- for (auto &th : aarch64_windows_process.thread_list)
- th->debug_registers_changed = true;
+ windows_debug_registers_changed_all_threads ();
}
INIT_GDB_FILE (aarch64_windows_nat)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index f61eab766ac..52378765438 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -40,6 +40,24 @@ namespace windows_nat
struct windows_process_info;
+/* The reason for explicitly stopping a thread. Note the enumerators
+ are ordered such that when comparing two stopping_kind's numerical
+ value, the highest should prevail. */
+enum stopping_kind
+ {
+ /* Not really stopping the thread. */
+ SK_NOT_STOPPING = 0,
+
+ /* We're stopping the thread for internal reasons, the stop should
+ not be reported as an event to the core. */
+ SK_INTERNAL = 1,
+
+ /* We're stopping the thread for external reasons, meaning, the
+ core/user asked us to stop the thread, so we must report a stop
+ event to the core. */
+ SK_EXTERNAL = 2,
+ };
+
/* Thread information structure used to track extra information about
each thread. */
struct windows_thread_info
@@ -123,9 +141,10 @@ struct windows_thread_info
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.
+ thread in response to a target_stop request or for
+ backend-internal reasons. 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
@@ -134,7 +153,7 @@ struct windows_thread_info
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;
+ stopping_kind stopping = SK_NOT_STOPPING;
/* Info about a potential pending stop.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index d56510c23f6..6f3803c887b 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -855,7 +855,7 @@ windows_per_inferior::handle_output_debug_string
a pending event. It will be picked up by
windows_nat_target::wait. */
th->suspend ();
- th->stopping = true;
+ th->stopping = SK_EXTERNAL;
th->last_event = {};
th->pending_status.set_stopped (gotasig);
@@ -923,7 +923,7 @@ windows_nat_target::continue_one_thread (windows_thread_info *th,
thread_context_continue (th, killed);
th->resume ();
- th->stopping = false;
+ th->stopping = SK_NOT_STOPPING;
th->last_sig = GDB_SIGNAL_0;
}
@@ -997,8 +997,19 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
#endif
}
- if (!target_is_non_stop_p ()
- || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ /* WCONT_DONT_CONTINUE_DEBUG_EVENT and WCONT_CONTINUE_DEBUG_EVENT
+ can't both be enabled at the same time. */
+ gdb_assert ((cont_flags & WCONT_DONT_CONTINUE_DEBUG_EVENT) == 0
+ || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) == 0);
+
+ bool continue_debug_event;
+ if ((cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ continue_debug_event = true;
+ else if ((cont_flags & WCONT_DONT_CONTINUE_DEBUG_EVENT) != 0)
+ continue_debug_event = false;
+ else
+ continue_debug_event = !target_is_non_stop_p ();
+ if (continue_debug_event)
{
DEBUG_EVENTS ("windows_continue -> continue_last_debug_event");
continue_last_debug_event_main_thread
@@ -1201,11 +1212,13 @@ 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. */
+/* Stop thread TH, for STOPPING_KIND reason. 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)
+windows_nat_target::stop_one_thread (windows_thread_info *th,
+ enum stopping_kind stopping_kind)
{
ptid_t thr_ptid (windows_process->process_id, th->tid);
@@ -1221,12 +1234,18 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
#ifdef __CYGWIN__
else if (th->suspended
&& th->signaled_thread != nullptr
- && th->pending_status.kind () == TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () == TARGET_WAITKIND_IGNORE
+ /* If doing an internal stop to update debug registers,
+ then just leave the "sig" thread suspended. Otherwise
+ windows_nat_target::wait would incorrectly break the
+ signaled_thread lock when it later processes the pending
+ stop and calls windows_continue on this thread. */
+ && stopping_kind == SK_EXTERNAL)
{
DEBUG_EVENTS ("explict stop for \"sig\" thread %s held for signal",
thr_ptid.to_string ().c_str ());
- th->stopping = true;
+ th->stopping = stopping_kind;
th->pending_status.set_stopped (GDB_SIGNAL_0);
th->last_event = {};
serial_event_set (m_wait_event);
@@ -1240,7 +1259,9 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
thr_ptid.to_string ().c_str (),
th->suspended, th->stopping);
- th->stopping = true;
+ /* Upgrade stopping. */
+ if (stopping_kind > th->stopping)
+ th->stopping = stopping_kind;
}
else
{
@@ -1255,14 +1276,20 @@ windows_nat_target::stop_one_thread (windows_thread_info *th)
{
DEBUG_EVENTS ("suspension of %s failed, expect thread exit event",
thr_ptid.to_string ().c_str ());
+ if (stopping_kind > th->stopping)
+ th->stopping = stopping_kind;
return;
}
gdb_assert (th->suspended == 1);
- th->stopping = true;
- th->pending_status.set_stopped (GDB_SIGNAL_0);
- th->last_event = {};
+ if (stopping_kind > th->stopping)
+ {
+ th->stopping = stopping_kind;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ }
+
serial_event_set (m_wait_event);
}
}
@@ -1275,7 +1302,7 @@ 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));
+ stop_one_thread (as_windows_thread_info (&thr), SK_EXTERNAL);
}
}
@@ -1777,6 +1804,17 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
{
windows_thread_info *th = windows_process->find_thread (result);
+ /* If this thread was temporarily stopped just so we
+ could update its debug registers on the next
+ resumption, do it now. */
+ if (th->stopping == SK_INTERNAL)
+ {
+ gdb_assert (fake);
+ windows_continue (DBG_CONTINUE, th->tid,
+ WCONT_DONT_CONTINUE_DEBUG_EVENT);
+ continue;
+ }
+
th->stopped_at_software_breakpoint = false;
if (current_event.dwDebugEventCode
== EXCEPTION_DEBUG_EVENT
@@ -1821,7 +1859,7 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
for (auto *thr : all_windows_threads ())
thr->suspend ();
- th->stopping = false;
+ th->stopping = SK_NOT_STOPPING;
}
/* If something came out, assume there may be more. This is
@@ -3404,6 +3442,40 @@ Use \"%ps\" or \"%ps\" command to load executable/libraries directly."),
}
}
+/* For each thread, set the debug_registers_changed flag, and
+ temporarily stop it so we can update its debug registers. */
+
+void
+windows_nat_target::debug_registers_changed_all_threads ()
+{
+ for (auto *th : all_windows_threads ())
+ {
+ th->debug_registers_changed = true;
+
+ /* Note we don't SuspendThread => update debug regs =>
+ ResumeThread, because SuspendThread is actually asynchronous
+ (and GetThreadContext blocks until the thread really
+ suspends), and doing that for all threads may take a bit.
+ Also, the core does one call per DR register update, so that
+ would result in a lot of suspend-resumes. So instead, we
+ suspend the thread if it wasn't already suspended, and queue
+ a pending stop to be handled by windows_nat_target::wait.
+ This means we only stop each thread once, and, we don't block
+ waiting for each individual thread stop. */
+ stop_one_thread (th, SK_INTERNAL);
+ }
+}
+
+/* Trampoline helper to get at the
+ windows_nat_target::debug_registers_changed_all_threads method in
+ the native target. */
+
+void
+windows_debug_registers_changed_all_threads ()
+{
+ get_windows_nat_target ()->debug_registers_changed_all_threads ();
+}
+
/* Determine if the thread referenced by "ptid" is alive
by "polling" it. If WaitForSingleObject returns WAIT_OBJECT_0
it means that the thread has died. Otherwise it is assumed to be alive. */
diff --git a/gdb/windows-nat.h b/gdb/windows-nat.h
index 7ce71690e20..5ef68dff274 100644
--- a/gdb/windows-nat.h
+++ b/gdb/windows-nat.h
@@ -78,6 +78,10 @@ enum windows_continue_flag
all-stop mode. This flag indicates that windows_continue
should call ContinueDebugEvent even in non-stop mode. */
WCONT_CONTINUE_DEBUG_EVENT = 4,
+
+ /* Skip calling ContinueDebugEvent even in all-stop mode. This is
+ the default in non-stop mode. */
+ WCONT_DONT_CONTINUE_DEBUG_EVENT = 8,
};
DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
@@ -258,6 +262,8 @@ struct windows_nat_target : public inf_child_target
return serial_event_fd (m_wait_event);
}
+ void debug_registers_changed_all_threads ();
+
protected:
/* Initialize arch-specific data for a new inferior (debug registers,
@@ -303,7 +309,8 @@ struct windows_nat_target : public inf_child_target
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT ¤t_event);
- void stop_one_thread (windows_thread_info *th);
+ void stop_one_thread (windows_thread_info *th,
+ enum windows_nat::stopping_kind stopping_kind);
DWORD continue_status_for_event_detaching
(const DEBUG_EVENT &event, size_t *reply_later_events_left = nullptr);
@@ -470,4 +477,6 @@ all_windows_threads ()
(all_non_exited_threads_range (win_tgt, minus_one_ptid)));
}
+extern void windows_debug_registers_changed_all_threads ();
+
#endif /* GDB_WINDOWS_NAT_H */
diff --git a/gdb/x86-windows-nat.c b/gdb/x86-windows-nat.c
index 8ad9d3bedea..61764da8deb 100644
--- a/gdb/x86-windows-nat.c
+++ b/gdb/x86-windows-nat.c
@@ -350,8 +350,7 @@ windows_set_dr (int i, CORE_ADDR addr)
if (i < 0 || i > 3)
internal_error (_("Invalid register %d in windows_set_dr.\n"), i);
- for (auto *th : all_windows_threads ())
- th->debug_registers_changed = true;
+ windows_debug_registers_changed_all_threads ();
}
/* Pass the value VAL to the inferior in the DR7 debug control
@@ -360,8 +359,7 @@ windows_set_dr (int i, CORE_ADDR addr)
static void
windows_set_dr7 (unsigned long val)
{
- for (auto *th : all_windows_threads ())
- th->debug_registers_changed = true;
+ windows_debug_registers_changed_all_threads ();
}
/* Get the value of debug register I from the inferior. */
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 07/11] Windows gdb: extra thread info => show exiting
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (5 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 06/11] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 08/11] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
` (6 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Now that we have easy access to each thread's last event, we can
easily include some extra info in "info threads" output related to
each thread's last event.
This patch makes us show whether the thread is exiting, or causing a
whole-process exit. This is useful when multiple threads hit events
at the same time, and the thread/process exit events are still pending
until the user re-resumes the program.
This is similar to how linux-thread-db.c also shows "Exiting" in its
target_extra_thread_info implementation.
This will be relied on by the testcase added by the following patch.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I493b7ea3e14574dc972b1341eb5062fbbfda1521
commit-id:51b6d728
---
gdb/windows-nat.c | 21 +++++++++++++++++++++
gdb/windows-nat.h | 2 ++
2 files changed, 23 insertions(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 6f3803c887b..638e87f6f60 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -3345,6 +3345,27 @@ windows_nat_target::thread_name (struct thread_info *thr)
return th->thread_name ();
}
+/* Implementation of the target_ops::extra_thread_info method. */
+
+const char *
+windows_nat_target::extra_thread_info (thread_info *info)
+{
+ windows_thread_info *th = windows_process->find_thread (info->ptid);
+
+ if (!th->suspended)
+ return nullptr;
+
+ if (th->pending_status.kind () == TARGET_WAITKIND_THREAD_EXITED
+ || th->last_event.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT)
+ return "exiting";
+ else if (th->pending_status.kind () == TARGET_WAITKIND_EXITED
+ || th->pending_status.kind () == TARGET_WAITKIND_SIGNALLED
+ || th->last_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ return "exiting process";
+
+ return nullptr;
+}
+
/* Implementation of the target_ops::supports_non_stop method. */
bool
diff --git a/gdb/windows-nat.h b/gdb/windows-nat.h
index 5ef68dff274..2d6981ada0b 100644
--- a/gdb/windows-nat.h
+++ b/gdb/windows-nat.h
@@ -214,6 +214,8 @@ struct windows_nat_target : public inf_child_target
bool thread_alive (ptid_t ptid) override;
+ const char *extra_thread_info (thread_info *info) override;
+
std::string pid_to_str (ptid_t) override;
void interrupt () override;
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 08/11] Add gdb.threads/leader-exit-schedlock.exp
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (6 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 07/11] Windows gdb: extra thread info => show exiting Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
` (5 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This adds a new test for letting the main thread exit the process with
scheduler-locking on, while there are other threads live.
On Linux, when the main thread exits without causing a whole-process
exit (e.g., via the main thread doing pthread_exit), the main thread
becomes zombie but does not report a thread exit event. When
eventually all other threads of the process exit, the main thread is
unblocked out of its zombie state and reports its exit which we
interpret as the whole-process exit.
If the main-thread-exit causes a whole-process exit (e.g., via the
exit syscall), the process is the same, except that the exit syscall
makes the kernel force-close all threads immediately.
Importantly, the main thread on Linux is always the last thread that
reports the exit event.
On Windows, the main thread exiting is not special at all. When the
main thread causes a process exit (e.g., for ExitProcess or by
returning from main), the debugger sees a normal thread exit event for
the main thread. All other threads will follow up with a thread-exit
event too, except whichever thread happens to be the last one. That
last one is the one that reports a whole-process-exit event instead of
an exit-thread event. So, since programs are typically multi-threaded
on Windows (because the OS/runtime spawns some threads), when the main
thread just returns from main(), it is very typically _not_ the main
thread that reports the whole-process exit.
As a result, stepping the main thread with schedlock on Windows
results in the main thread exiting and the continue aborting due to
no-resumed-threads left instead of a whole-process exit as seen on
Linux:
(gdb) info threads
Id Target Id Frame
* 1 Thread 11768.0x1bc "leader-exit-schedlock" main () at .../gdb.threads/leader-exit-schedlock.c:55
2 Thread 11768.0x31e0 (in kernel) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
3 Thread 11768.0x2dec "sig" (in kernel) 0x00007ffbb23dc087 in ntdll!ZwReadFile () from C:/WINDOWS/SYSTEM32/ntdll.dll
4 Thread 11768.0x2530 (in kernel) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
5 Thread 11768.0x3384 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
6 Thread 11768.0x3198 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
7 Thread 11768.0x1ab8 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
8 Thread 11768.0x3fe4 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
9 Thread 11768.0x3b5c "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
10 Thread 11768.0x45c "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
11 Thread 11768.0x3724 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
12 Thread 11768.0x1e44 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
13 Thread 11768.0x23f0 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
14 Thread 11768.0x3b80 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
(gdb) set scheduler-locking on
(gdb) c
Continuing.
[Thread 11768.0x1bc exited]
No unwaited-for children left.
(gdb) info threads
Id Target Id Frame
2 Thread 11768.0x31e0 (exiting) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
3 Thread 11768.0x2dec "sig" (exiting) 0x00007ffbb23dc087 in ntdll!ZwReadFile () from C:/WINDOWS/SYSTEM32/ntdll.dll
4 Thread 11768.0x2530 (exiting) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
5 Thread 11768.0x3384 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
6 Thread 11768.0x3198 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
7 Thread 11768.0x1ab8 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
8 Thread 11768.0x3fe4 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
9 Thread 11768.0x3b5c "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
10 Thread 11768.0x45c "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
11 Thread 11768.0x3724 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
12 Thread 11768.0x1e44 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
13 Thread 11768.0x23f0 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
14 Thread 11768.0x3b80 "leader-exit-schedlock" (exiting process) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
The current thread <Thread ID 1> has terminated. See `help thread'.
(gdb)
The "(exiting)" and "(exiting process)" threads are threads for which
the kernel already reported their exit to GDB's Windows backend (via
WaitForDebugEvent), but the Windows backend hasn't yet reported the
event to infrun. The events are still pending in windows-nat.c.
The "(exiting process)" thread above (thread 14) is the one that won
the process-exit event lottery on the Windows kernel side (because it
was the last to exit). Continuing the (exiting) threads with
schedlock enabled should result in the Windows backend reporting that
thread's pending exit to infrun. While continuing thread 14 should
result in the inferior exiting. Vis:
(gdb) c
Continuing.
[Thread 11768.0x31e0 exited]
No unwaited-for children left.
(gdb) t 14
[Switching to thread 14 (Thread 11768.0x3b80)]
#0 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
(gdb) c
Continuing.
[Inferior 1 (process 11768) exited normally]
The testcase continues all the (exiting) threads, one by one, and then
finally continues the (exiting process) one, expecting an inferior
exit.
The testcase also tries a similar scenario: instead immediately
continue the (exiting process) thread without continuing the others.
That should result in the inferior exiting immediately.
It is actually not guaranteed that the Windows backend will consume
all the thread and process exit events out of the kernel before the
first thread exit event is processed by infrun. So often we will see
for example, instead:
(gdb) info threads
Id Target Id Frame
2 Thread 11768.0x31e0 (exiting) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
3 Thread 11768.0x2dec "sig" (exiting) 0x00007ffbb23dc087 in ntdll!ZwReadFile () from C:/WINDOWS/SYSTEM32/ntdll.dll
4 Thread 11768.0x2530 (exiting) 0x00007ffbb23dfc77 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/WINDOWS/SYSTEM32/ntdll.dll
5 Thread 11768.0x3384 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
6 Thread 11768.0x3198 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
7 Thread 11768.0x1ab8 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
8 Thread 11768.0x3fe4 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
9 Thread 11768.0x3b5c "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
10 Thread 11768.0x45c "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
11 Thread 11768.0x3724 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
12 Thread 11768.0x1e44 "leader-exit-schedlock" 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
13 Thread 11768.0x23f0 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
14 Thread 11768.0x3b80 "leader-exit-schedlock" (exiting) 0x00007ffbb23dcb17 in ntdll!ZwWaitForMultipleObjects () from C:/WINDOWS/SYSTEM32/ntdll.dll
Above, we can't tell which thread will get the exit-process event,
there is no "(exiting process)" thread. We do know it'll be one of
threads 10, 11, and 12, because those do not have "(exiting)". The
Windows kernel has already decided which one it is at this point, we
just haven't seen the exit-process event yet.
This is actually what we _always_ see with "maint set target-non-stop
off" too, because in all-stop, the Windows backend only processes one
Windows debug event at a time.
So when the the test first continues all the (exiting) threads, one by
one, and then when there are no more "(exiting)" threads, if there is
no "(exiting process)" thread, it tries to exit the remaining threads,
(in the above case threads 10, 11 and 12), expecting that one of those
continues may cause an inferior exit.
On systems other than Windows, the testcase expects that continuing
the main thread results in an inferior exit. If we find out that
isn't correct for some system, we can adjust the testcase then.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I52fb8de5e72bc12195ffb8bedd1d8070464332d3
commit-id:5f751d8e
---
.../gdb.threads/leader-exit-schedlock.c | 56 +++++
.../gdb.threads/leader-exit-schedlock.exp | 215 ++++++++++++++++++
2 files changed, 271 insertions(+)
create mode 100644 gdb/testsuite/gdb.threads/leader-exit-schedlock.c
create mode 100644 gdb/testsuite/gdb.threads/leader-exit-schedlock.exp
diff --git a/gdb/testsuite/gdb.threads/leader-exit-schedlock.c b/gdb/testsuite/gdb.threads/leader-exit-schedlock.c
new file mode 100644
index 00000000000..cdd8780878f
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/leader-exit-schedlock.c
@@ -0,0 +1,56 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025-2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <pthread.h>
+#include <assert.h>
+#include <unistd.h>
+
+static pthread_barrier_t threads_started_barrier;
+
+static void *
+start (void *arg)
+{
+ pthread_barrier_wait (&threads_started_barrier);
+
+ while (1)
+ sleep (1);
+
+ return NULL;
+}
+
+#define NTHREADS 10
+
+int
+main (void)
+{
+ int i;
+
+ pthread_barrier_init (&threads_started_barrier, NULL, NTHREADS + 1);
+
+ for (i = 0; i < NTHREADS; i++)
+ {
+ pthread_t thread;
+ int res;
+
+ res = pthread_create (&thread, NULL, start, NULL);
+ assert (res == 0);
+ }
+
+ pthread_barrier_wait (&threads_started_barrier);
+
+ return 0; /* break-here */
+}
diff --git a/gdb/testsuite/gdb.threads/leader-exit-schedlock.exp b/gdb/testsuite/gdb.threads/leader-exit-schedlock.exp
new file mode 100644
index 00000000000..56e1634c42f
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/leader-exit-schedlock.exp
@@ -0,0 +1,215 @@
+# Copyright (C) 2025-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# On Linux, exiting the main thread with scheduler locking on results
+# in an inferior exit immediately. On Windows, however, it results in
+# a normal thread exit of the main thread, with the other threads
+# staying listed. Test this, and then test iterating over all the
+# other threads and continuing then too, one by one, with
+# scheduler-locking on. Also test schedlock off, for completeness.
+
+standard_testfile
+
+if {[build_executable "failed to prepare" $testfile $srcfile {debug pthreads}]} {
+ return -1
+}
+
+# Run "info threads" and return a list of various information:
+#
+# Element 0 - the highest numbered thread, zero if no thread is found.
+# Element 1 - the "(exiting process)" thread, zero if not found.
+# Element 2 - a list of "(exiting)" threads.
+# Element 3 - a list of the other threads.
+
+proc info_threads {} {
+ set highest_thread 0
+ set exit_process_thread 0
+ set exit_thread_threads {}
+ set other_threads {}
+ set any "\[^\r\n\]*"
+ set ws "\[ \t\]*"
+ set eol "(?=\r\n)"
+ set common_prefix "^\r\n(?:\\*)?${ws}($::decimal)\[ \t\]*${::tdlabel_re}${any}"
+ gdb_test_multiple "info threads" "" -lbl {
+ -re "${common_prefix}\\(exiting process\\)${any}${eol}" {
+ set highest_thread $expect_out(1,string)
+ verbose -log "\nhighest_thread=$highest_thread, exiting process\n"
+ set exit_process_thread $highest_thread
+ exp_continue
+ }
+ -re "${common_prefix}\\(exiting\\)${any}${eol}" {
+ set highest_thread $expect_out(1,string)
+ verbose -log "\nhighest_thread=$highest_thread, exiting thread\n"
+ lappend exit_thread_threads $highest_thread
+ exp_continue
+ }
+ -re "${common_prefix}${eol}" {
+ set highest_thread $expect_out(1,string)
+ verbose -log "\nhighest_thread=$highest_thread, other thread\n"
+ lappend other_threads $highest_thread
+ exp_continue
+ }
+ -re "^\r\n$::gdb_prompt $" {
+ verbose -log "\nhighest_thread=$highest_thread, prompt\n"
+ gdb_assert {$highest_thread > 0} $gdb_test_name
+ }
+ }
+ verbose -log "info_threads: highest_thread=$highest_thread"
+ verbose -log "info_threads: exit_thread_threads=$exit_thread_threads"
+ verbose -log "info_threads: other_threads=$other_threads"
+ verbose -log "info_threads: exit_process_thread=$exit_process_thread"
+
+ return [list \
+ $highest_thread \
+ $exit_process_thread \
+ $exit_thread_threads \
+ $other_threads]
+}
+
+# If EXIT-THREADS-FIRST is true, continues all threads which have a
+# pending exit-thread event first, before continuing the thread with
+# the pending exit-process event.
+proc test {target-non-stop exit-threads-first schedlock} {
+ save_vars ::GDBFLAGS {
+ append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+ clean_restart $::testfile
+ }
+
+ set is_windows [expr {[istarget *-*-mingw*] || [istarget *-*-cygwin*]}]
+ set is_linux [istarget *-*-linux*]
+
+ if {!$is_windows && ${exit-threads-first}} {
+ # No point in exercising this combination because we will
+ # return before we reach the point where it is tested.
+ return
+ }
+
+ if {![runto_main]} {
+ return
+ }
+
+ gdb_breakpoint [gdb_get_line_number "break-here"]
+ gdb_continue_to_breakpoint "break-here" ".* break-here .*"
+
+ gdb_test_no_output "set scheduler-locking $schedlock"
+
+ # Continuing the main thread on Linux makes the whole process
+ # exit. This makes GDB report all threads exits immediately, and
+ # then the inferior exit. The thread exits don't stay pending
+ # because Linux supports per-thread thread-exit control, while
+ # Windows is per-target.
+ if {!$is_windows || $schedlock == off} {
+ gdb_test_multiple "c" "continue exit-process thread to exit" {
+ -re -wrap "Inferior.*exited normally.*" {
+ pass $gdb_test_name
+ }
+ -re -wrap "No unwaited-for children left.*" {
+ # On Linux, GDB may briefly see the main thread turn
+ # zombie before seeing its exit event.
+ gdb_assert $is_linux $gdb_test_name
+ }
+ }
+
+ return
+ }
+
+ # On Windows, continuing the thread that calls TerminateProcess
+ # (the main thread when it returns from main in our case) with
+ # scheduler-locking enabled exits the whole process, but core of
+ # GDB won't see the exit process event right away. Windows only
+ # reports it to the last thread that exits, whichever that is.
+ # Due to scheduler locking, that won't happen until we resume all
+ # other threads. The TerminateProcess-caller thread gets a plain
+ # thread exit event.
+ gdb_test "c" "No unwaited-for children left\\." "continue main thread"
+
+ if {${target-non-stop} == "on"} {
+ # With non-stop. GDB issues ContinueDebugEvent as soon as it
+ # seens a debug event, so after a bit, the windows backend
+ # will have seen all the thread and process exit events, even
+ # while the user has the prompt. Give it a bit of time for
+ # that to happen, so we can tell which threads have exited by
+ # looking for (exiting) and "(exiting process) in "info
+ # threads" output.
+ sleep 2
+ }
+
+ with_test_prefix "initial threads info" {
+ lassign [info_threads] \
+ highest_thread \
+ exit_process_thread \
+ exit_thread_threads \
+ other_threads
+
+ gdb_assert {$highest_thread > 0}
+ }
+
+ # Continue one thread at a time, collecting the exit status.
+ set thread_count $highest_thread
+ for {set i 2} {$i <= $thread_count} {incr i} {
+ with_test_prefix "thread $i" {
+ lassign [info_threads] \
+ highest_thread \
+ exit_process_thread \
+ exit_thread_threads \
+ other_threads
+
+ # Default to a value that forces FAILs below.
+ set thr 0
+ # Whether we expect to find a thread with "(exiting process)":
+ # 0 - not expected - it's a failure if we see one.
+ # 1 - possible - we may or may not see one.
+ # 2 - required - it's a failure if we don't see one.
+ set process_exit_expected 0
+
+ if {$i == $thread_count} {
+ set thr $highest_thread
+ set process_exit_expected 2
+ gdb_test "p/d \$_inferior_thread_count == 1" " = 1" "one thread left"
+ } else {
+ if {${exit-threads-first} && [llength $exit_thread_threads] != 0} {
+ set thr [lindex $exit_thread_threads 0]
+ } elseif {$exit_process_thread > 0} {
+ set thr $exit_process_thread
+ set process_exit_expected 2
+ } elseif {[llength $other_threads] != 0} {
+ set thr [lindex $other_threads 0]
+ set process_exit_expected 1
+ }
+ }
+
+ gdb_test "thread $thr" \
+ "Switching to .*" \
+ "switch to thread"
+ gdb_test_multiple "c" "continue thread to exit" {
+ -re -wrap "No unwaited-for children left\\." {
+ gdb_assert {$process_exit_expected != 2} $gdb_test_name
+ }
+ -re -wrap "Inferior.*exited normally.*" {
+ gdb_assert {$process_exit_expected != 0} $gdb_test_name
+ return
+ }
+ }
+ }
+ }
+}
+
+foreach_with_prefix target-non-stop {off on} {
+ foreach_with_prefix exit-threads-first {0 1} {
+ foreach_with_prefix schedlock {off on} {
+ test ${target-non-stop} ${exit-threads-first} $schedlock
+ }
+ }
+}
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (7 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 08/11] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-05-06 10:01 ` Bouhaouel, Mohamed
2026-04-29 20:15 ` [PATCH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
` (4 subsequent siblings)
13 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches
This patch fixes gdb.base/ending-run.exp for Windows when the target
backend supports notifying infrun about thread exit events (which is
added by the Windows non-stop support, later).
Without this patch, and with the Windows target in non-stop mode
("maint set target-non-stop on"), we get, when stepping out of main:
(gdb) PASS: gdb.base/ending-run.exp: Step to return
next
32 }
(gdb) next
[Thread 7956.0x2658 exited]
[Thread 7956.0x2500 exited]
[Thread 7956.0x2798 exited]
Command aborted, thread exited.
(gdb) FAIL: gdb.base/ending-run.exp: step out of main
With the patch, we get:
(gdb) next
[Thread 9424.0x40c exited]
[Inferior 1 (process 9424) exited normally]
(gdb) PASS: gdb.base/ending-run.exp: step out of main
In the failing case, what happens is that "next" enables
target_thread_events. Then, the main thread causes the whole process
to exit. On Windows, that makes the main thread report a thread exit
event, followed by thread exit events for all other threads, except
the last thread that happens to be the one that exits last. That last
one reports an exit-process event instead.
Since "next" enabled target_thread_events, the Windows target backend
reports the main thread's exit event to infrun. And then, since the
thread that was stepping reported a thread-exit, GDB aborts the "next"
command.
Stepping out of main is a very common thing to do, and I think
reporting the thread exit in this case when the whole process is
exiting isn't very useful. I think we can do better. So instead, if
we're about to report a thread exit in all-stop mode with the backend
in non-stop mode, and while stopping all threads, we see a
whole-process-exit event, prefer processing that event instead of
reporting the original thread exit.
A similar issue can be triggered on GNU/Linux as well, if we step over
an exit syscall that is called by any thread other than main. This
scenario is exercised by the new testcase added by this patch.
Without the patch, the testcase shows:
(gdb) next
[Thread 0x7ffff7a00640 (LWP 3207243) exited]
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
Command aborted, thread exited.
Cannot remove breakpoints because program is no longer writable.
Further execution is probably impossible.
(gdb)
This is fixed for GNU/Linux by the patch, which results in:
(gdb) next
[Thread 0x7ffff7a00640 (LWP 3230550) exited]
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
[Inferior 1 (process 3230539) exited normally]
(gdb)
Pure all-stop targets (such as GNU/Linux GDBserver unless you force
non-stop with "maint set target-non-stop on") will unfortunately still
have the "Further execution is probably impossible." behavior, because
GDB can't see the process-exit event until the target is re-resumed.
That's unfortunate, but I don't think that should prevent improving
non-stop targets. (And eventually I would like remote targets to be
always "maint set target-non-stop on" by default if possible, too.)
Change-Id: I56559f13e04aeafd812d15e4b408c8337bca5294
---
gdb/infrun.c | 209 ++++++++++++------
.../gdb.threads/step-over-process-exit.c | 49 ++++
.../gdb.threads/step-over-process-exit.exp | 66 ++++++
3 files changed, 255 insertions(+), 69 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.c
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.exp
diff --git a/gdb/infrun.c b/gdb/infrun.c
index aed66bf844e..11c5d5214d6 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -109,6 +109,8 @@ static bool step_over_info_valid_p (void);
static bool schedlock_applies (struct thread_info *tp);
+static void handle_process_exited (struct execution_control_state *ecs);
+
/* Asynchronous signal handler registered as event loop source for
when we have pending events ready to be passed to the core. */
static struct async_event_handler *infrun_async_inferior_event_token;
@@ -4778,7 +4780,68 @@ fetch_inferior_event ()
don't want to stop all the other threads. */
if (ecs.event_thread == nullptr
|| !ecs.event_thread->control.in_cond_eval)
- stop_all_threads_if_all_stop_mode ();
+ {
+ stop_all_threads_if_all_stop_mode ();
+
+ /* Say the user does "next" over an exit(0) call, or
+ out of main, either of which make the whole process
+ exit. If the target supports target_thread_events,
+ then that is activated while "next" is in progress,
+ and consequently we may be processing a thread-exit
+ event (caused by the process exit) for a thread
+ that reported its exit before the
+ whole-process-exit event is reported for another
+ thread (which is normally going to be the last
+ event out of the inferior, but we don't know for
+ which thread it will be).
+
+ If we're in 'all-stop on top of non-stop' mode, and
+ indeed the inferior process is exiting, then the
+ stop_all_threads_if_all_stop_mode call above must
+ have seen the process-exit event, as it will see
+ one stop for each and every (running) thread of the
+ process. Look at the pending statuses of all
+ threads, and see if we have a process-exit status.
+ If so, prefer handling it now and report the
+ inferior exit to the user instead of reporting the
+ original thread exit.
+
+ Do not do this if are handling any other kind of
+ event, like e.g., a breakpoint hit, which the user
+ may be interested in knowing was hit before the
+ process exited. If we ever have a "catch
+ thread-exit" or something similar, we may want to
+ skip this "prefer process-exit" if such a
+ catchpoint is installed. */
+ if (ecs.ws.kind () == TARGET_WAITKIND_THREAD_EXITED
+ && !non_stop && exists_non_stop_target ())
+ {
+ for (thread_info &thread : inf->non_exited_threads ())
+ {
+ if (thread.has_pending_waitstatus ()
+ && ((thread.pending_waitstatus ().kind ()
+ == TARGET_WAITKIND_EXITED)
+ || (thread.pending_waitstatus ().kind ()
+ == TARGET_WAITKIND_SIGNALLED)))
+ {
+ /* Found a pending process-exit event.
+ Prefer handling and reporting it now
+ over the thread-exit event. */
+ infrun_debug_printf
+ ("found pending process-exit event, preferring it");
+ ecs.ws = thread.pending_waitstatus ();
+ thread.clear_pending_waitstatus ();
+ ecs.event_thread = nullptr;
+ ecs.ptid = thread.ptid;
+ /* Re-record the last target status. */
+ set_last_target_status (ecs.target, ecs.ptid,
+ ecs.ws);
+ handle_process_exited (&ecs);
+ break;
+ }
+ }
+ }
+ }
clean_up_just_stopped_threads_fsms (&ecs);
@@ -6104,6 +6167,81 @@ handle_thread_exited (execution_control_state *ecs)
return true;
}
+/* Handle a process exit event. */
+
+static void
+handle_process_exited (execution_control_state *ecs)
+{
+ /* Depending on the system, ecs->ptid may point to a thread or to a
+ process. On some targets, target_mourn_inferior may need to have
+ access to the just-exited thread. That is the case of
+ GNU/Linux's "checkpoint" support, for example. Switch context
+ appropriately. */
+ thread_info *thr = ecs->target->find_thread (ecs->ptid);
+ if (thr != nullptr)
+ switch_to_thread (thr);
+ else
+ {
+ inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
+ switch_to_inferior_no_thread (inf);
+ }
+
+ handle_vfork_child_exec_or_exit (0);
+ target_terminal::ours (); /* Must do this before mourn anyway. */
+
+ /* Clearing any previous state of convenience variables. */
+ clear_exit_convenience_vars ();
+
+ if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
+ {
+ /* Record the exit code in the convenience variable $_exitcode,
+ so that the user can inspect this again later. */
+ set_internalvar_integer (lookup_internalvar ("_exitcode"),
+ (LONGEST) ecs->ws.exit_status ());
+
+ /* Also record this in the inferior itself. */
+ current_inferior ()->has_exit_code = true;
+ current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
+
+ /* Support the --return-child-result option. */
+ return_child_result_value = ecs->ws.exit_status ();
+
+ interps_notify_exited (ecs->ws.exit_status ());
+ }
+ else
+ {
+ struct gdbarch *gdbarch = current_inferior ()->arch ();
+
+ if (gdbarch_gdb_signal_to_target_p (gdbarch))
+ {
+ /* Set the value of the internal variable $_exitsignal,
+ which holds the signal uncaught by the inferior. */
+ set_internalvar_integer (lookup_internalvar ("_exitsignal"),
+ gdbarch_gdb_signal_to_target (gdbarch,
+ ecs->ws.sig ()));
+ }
+ else
+ {
+ /* We don't have access to the target's method used for
+ converting between signal numbers (GDB's internal
+ representation <-> target's representation).
+ Therefore, we cannot do a good job at displaying this
+ information to the user. It's better to just warn
+ her about it (if infrun debugging is enabled), and
+ give up. */
+ infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
+ "signal number.");
+ }
+
+ interps_notify_signal_exited (ecs->ws.sig ());
+ }
+
+ gdb_flush (gdb_stdout);
+ target_mourn_inferior (inferior_ptid);
+ stop_print_frame = false;
+ stop_waiting (ecs);
+}
+
/* Given an execution control state that has been freshly filled in by
an event from the inferior, figure out what it means and take
appropriate action.
@@ -6313,74 +6451,7 @@ handle_inferior_event (struct execution_control_state *ecs)
case TARGET_WAITKIND_EXITED:
case TARGET_WAITKIND_SIGNALLED:
- {
- /* Depending on the system, ecs->ptid may point to a thread or
- to a process. On some targets, target_mourn_inferior may
- need to have access to the just-exited thread. That is the
- case of GNU/Linux's "checkpoint" support, for example.
- Call the switch_to_xxx routine as appropriate. */
- thread_info *thr = ecs->target->find_thread (ecs->ptid);
- if (thr != nullptr)
- switch_to_thread (thr);
- else
- {
- inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
- switch_to_inferior_no_thread (inf);
- }
- }
- handle_vfork_child_exec_or_exit (0);
- target_terminal::ours (); /* Must do this before mourn anyway. */
-
- /* Clearing any previous state of convenience variables. */
- clear_exit_convenience_vars ();
-
- if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
- {
- /* Record the exit code in the convenience variable $_exitcode, so
- that the user can inspect this again later. */
- set_internalvar_integer (lookup_internalvar ("_exitcode"),
- (LONGEST) ecs->ws.exit_status ());
-
- /* Also record this in the inferior itself. */
- current_inferior ()->has_exit_code = true;
- current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
-
- /* Support the --return-child-result option. */
- return_child_result_value = ecs->ws.exit_status ();
-
- interps_notify_exited (ecs->ws.exit_status ());
- }
- else
- {
- struct gdbarch *gdbarch = current_inferior ()->arch ();
-
- if (gdbarch_gdb_signal_to_target_p (gdbarch))
- {
- /* Set the value of the internal variable $_exitsignal,
- which holds the signal uncaught by the inferior. */
- set_internalvar_integer (lookup_internalvar ("_exitsignal"),
- gdbarch_gdb_signal_to_target (gdbarch,
- ecs->ws.sig ()));
- }
- else
- {
- /* We don't have access to the target's method used for
- converting between signal numbers (GDB's internal
- representation <-> target's representation).
- Therefore, we cannot do a good job at displaying this
- information to the user. It's better to just warn
- her about it (if infrun debugging is enabled), and
- give up. */
- infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
- "signal number.");
- }
-
- interps_notify_signal_exited (ecs->ws.sig ());
- }
-
- gdb_flush (gdb_stdout);
- target_mourn_inferior (inferior_ptid);
- stop_print_frame = false;
+ handle_process_exited (ecs);
stop_waiting (ecs);
return;
diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.c b/gdb/testsuite/gdb.threads/step-over-process-exit.c
new file mode 100644
index 00000000000..a9b86751596
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-process-exit.c
@@ -0,0 +1,49 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025-2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int other_thread_exits = 0;
+
+static void *
+thread_function (void *arg)
+{
+ if (other_thread_exits)
+ exit (0); /* break here other */
+
+ while (1)
+ sleep (1);
+}
+
+int
+main ()
+{
+ pthread_t thread;
+
+ alarm (30);
+
+ pthread_create (&thread, NULL, thread_function, NULL);
+
+ if (!other_thread_exits)
+ exit (0); /* break here main */
+
+ while (1)
+ sleep (1);
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.exp b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
new file mode 100644
index 00000000000..6c98ebe3956
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
@@ -0,0 +1,66 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2025-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test stepping over an exit syscall from both the main thread, and a
+# non-main thread.
+
+if { [target_info exists exit_is_reliable] } {
+ set exit_is_reliable [target_info exit_is_reliable]
+} else {
+ set exit_is_reliable [expr {![target_info exists use_gdb_stub]}]
+}
+require {expr {$exit_is_reliable}}
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthread}] } {
+ return -1
+}
+
+# WHICH is which thread exits the process. Can be "main" for main
+# thread, or "other" for the non-main thread.
+
+proc test {which} {
+ if {![runto_main]} {
+ return -1
+ }
+
+ set other [expr {$which == "other"}]
+ gdb_test "p other_thread_exits = $other" " = $other"
+
+ set break_line [gdb_get_line_number "break here $which"]
+ gdb_breakpoint $break_line
+ gdb_continue_to_breakpoint "exit syscall"
+
+ set target_non_stop [is_target_non_stop]
+
+ gdb_test_multiple "next" "" {
+ -re -wrap "$::inferior_exited_re normally\\\]" {
+ pass $gdb_test_name
+ }
+ -re -wrap "Further execution is probably impossible\\." {
+ # With a target in all-stop, this is the best we can do.
+ # We should not see this with a target backend in non-stop
+ # mode, however.
+ gdb_assert !$target_non_stop $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix which {main other} {
+ test $which
+}
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on")
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (8 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-29 20:15 ` [PATCH v3 11/11] Mention Windows non-stop support in NEWS Pedro Alves
` (3 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Since having the target backend work in non-stop mode adds features
compared to old all-stop mode (signal/exception passing/suppression is
truly per-thread), this switches the backend to do
all-stop-on-top-of-non-stop, by having
windows_nat_target::always_non_stop_p return true if non-stop mode is
possible.
To be clear, this just changes how the backend works in coordination
with infrun. The user-visible mode default mode is still all-stop.
The difference is that infrun is responsible for stopping all threads
when needed, instead of the backend (actually the kernel) always doing
that before reporting an event to infrun.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I83d23dbb1edc7692d5d8b37f5b9e0264c74d4940
commit-id:6f7924dc
---
gdb/windows-nat.c | 9 +++++++++
gdb/windows-nat.h | 1 +
2 files changed, 10 insertions(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 638e87f6f60..a9647e90bb8 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -3376,6 +3376,15 @@ windows_nat_target::supports_non_stop ()
return dbg_reply_later_available ();
}
+/* Implementation of the target_ops::always_non_stop_p method. */
+
+bool
+windows_nat_target::always_non_stop_p ()
+{
+ /* If we can do non-stop, prefer it. */
+ return supports_non_stop ();
+}
+
INIT_GDB_FILE (windows_nat)
{
#ifdef __CYGWIN__
diff --git a/gdb/windows-nat.h b/gdb/windows-nat.h
index 2d6981ada0b..1f7ecb07e4f 100644
--- a/gdb/windows-nat.h
+++ b/gdb/windows-nat.h
@@ -256,6 +256,7 @@ struct windows_nat_target : public inf_child_target
}
bool supports_non_stop () override;
+ bool always_non_stop_p () override;
void async (bool enable) override;
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v3 11/11] Mention Windows non-stop support in NEWS
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (9 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
@ 2026-04-29 20:15 ` Pedro Alves
2026-04-30 5:55 ` [PATCH v3 00/11] Windows non-stop mode Eli Zaretskii
` (2 subsequent siblings)
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-29 20:15 UTC (permalink / raw)
To: gdb-patches; +Cc: Eli Zaretskii
Add a note to gdb/NEWS mentioning Windows native target non-stop
support.
Approved-By: Eli Zaretskii <eliz@gnu.org>
Change-Id: Id0e28525c06e57120c47b07f978581d1627d70f3
commit-id:0f56dd46
---
gdb/NEWS | 3 +++
1 file changed, 3 insertions(+)
diff --git a/gdb/NEWS b/gdb/NEWS
index e233906153a..480e1854002 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -75,6 +75,9 @@
* The Windows native target now supports scheduler-locking. E.g.,
"set scheduler-locking on" now works. Previously it gave an error.
+* The Windows native target now supports non-stop mode. This feature
+ requires Windows 10 or later.
+
* New targets
GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (10 preceding siblings ...)
2026-04-29 20:15 ` [PATCH v3 11/11] Mention Windows non-stop support in NEWS Pedro Alves
@ 2026-04-30 5:55 ` Eli Zaretskii
2026-04-30 10:13 ` Pedro Alves
2026-04-30 17:45 ` [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-05-08 18:43 ` Tom Tromey
13 siblings, 1 reply; 29+ messages in thread
From: Eli Zaretskii @ 2026-04-30 5:55 UTC (permalink / raw)
To: Pedro Alves, Hannes Domani; +Cc: gdb-patches
> From: Pedro Alves <pedro@palves.net>
> Date: Wed, 29 Apr 2026 21:14:56 +0100
>
> This series adds non-stop mode support to the Windows native backend,
> on Windows 10 and above. Earlier Windows versions lack the necessary
> feature, so those keep working in all-stop mode, only.
>
> After the series, the Windows target backend defaults to working in
> non-stop mode (as in, "maint set target-non-stop"), even if
> user-visible mode is all-stop ("set non-stop off"). This is the same
> as the Linux backend.
I'm not sure this is necessarily a good idea. Windows is weird in
this aspect, as you know very well: the system frequently starts
additional threads for its own purposes, such as handling the Ctrl-C
and Ctrl-BREAK signals. Also, many Windows programs have a separate
UI thread which receives and dispatches the Windows GUI messages.
Letting those threads run by default when the program stops at a
breakpoint is not necessarily the best alternative, and perhaps is
best left to the GDB user in each case (they can do that in
program-specific .gdbinit, if they need). Or am I missing something?
Thanks.
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-30 5:55 ` [PATCH v3 00/11] Windows non-stop mode Eli Zaretskii
@ 2026-04-30 10:13 ` Pedro Alves
2026-04-30 11:14 ` Eli Zaretskii
0 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 10:13 UTC (permalink / raw)
To: Eli Zaretskii, Hannes Domani; +Cc: gdb-patches
Hi Eli,
On 2026-04-30 06:55, Eli Zaretskii wrote:
>> From: Pedro Alves <pedro@palves.net>
>> After the series, the Windows target backend defaults to working in
>> non-stop mode (as in, "maint set target-non-stop"), even if
>> user-visible mode is all-stop ("set non-stop off"). This is the same
>> as the Linux backend.
>
> I'm not sure this is necessarily a good idea. Windows is weird in
> this aspect, as you know very well: the system frequently starts
> additional threads for its own purposes, such as handling the Ctrl-C
> and Ctrl-BREAK signals. Also, many Windows programs have a separate
> UI thread which receives and dispatches the Windows GUI messages.
> Letting those threads run by default when the program stops at a
> breakpoint is not necessarily the best alternative, and perhaps is
> best left to the GDB user in each case (they can do that in
> program-specific .gdbinit, if they need). Or am I missing something?
What I meant by the above, is that the windows target backend code defaults to
working in non-stop mode, but the user does not see any difference. When a
breakpoint is hit, GDB still stops all threads. For the user, it still works the
same, "set non-stop off" is still the default and I don't plan to change that.
The "maint set target-non-stop" setting is just for how the backend communicates
with infrun. I call this "all-stop on top of non-stop". Short for
"(user-visible) all-stop on top of (backend working in) non-stop (mode)".
This is how the Linux backend works too.
When the backend works in non-stop mode (the "maint set target-non-stop" setting),
infrun takes responsibility for explicitly stopping all threads when necessary,
instead of the backend implicitly stopping all threads for every reported
internal debug event. It gives more flexibility to infrun, and is a requirement
for enabling supporting AMD GPU debugging on Windows. The user doesn't see
anything different.
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-30 10:13 ` Pedro Alves
@ 2026-04-30 11:14 ` Eli Zaretskii
2026-04-30 12:01 ` Pedro Alves
0 siblings, 1 reply; 29+ messages in thread
From: Eli Zaretskii @ 2026-04-30 11:14 UTC (permalink / raw)
To: Pedro Alves; +Cc: ssbssa, gdb-patches
> Date: Thu, 30 Apr 2026 11:13:55 +0100
> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
>
> Hi Eli,
>
> On 2026-04-30 06:55, Eli Zaretskii wrote:
> >> From: Pedro Alves <pedro@palves.net>
> >> After the series, the Windows target backend defaults to working in
> >> non-stop mode (as in, "maint set target-non-stop"), even if
> >> user-visible mode is all-stop ("set non-stop off"). This is the same
> >> as the Linux backend.
> >
> > I'm not sure this is necessarily a good idea. Windows is weird in
> > this aspect, as you know very well: the system frequently starts
> > additional threads for its own purposes, such as handling the Ctrl-C
> > and Ctrl-BREAK signals. Also, many Windows programs have a separate
> > UI thread which receives and dispatches the Windows GUI messages.
> > Letting those threads run by default when the program stops at a
> > breakpoint is not necessarily the best alternative, and perhaps is
> > best left to the GDB user in each case (they can do that in
> > program-specific .gdbinit, if they need). Or am I missing something?
>
> What I meant by the above, is that the windows target backend code defaults to
> working in non-stop mode, but the user does not see any difference. When a
> breakpoint is hit, GDB still stops all threads. For the user, it still works the
> same, "set non-stop off" is still the default and I don't plan to change that.
> The "maint set target-non-stop" setting is just for how the backend communicates
> with infrun. I call this "all-stop on top of non-stop". Short for
> "(user-visible) all-stop on top of (backend working in) non-stop (mode)".
> This is how the Linux backend works too.
>
> When the backend works in non-stop mode (the "maint set target-non-stop" setting),
> infrun takes responsibility for explicitly stopping all threads when necessary,
> instead of the backend implicitly stopping all threads for every reported
> internal debug event. It gives more flexibility to infrun, and is a requirement
> for enabling supporting AMD GPU debugging on Windows. The user doesn't see
> anything different.
Thanks. Now I'm even more confused than before, because not only do
your explanations contradict the literal meaning of what you say, they
seem to also contradict what's in the manual.
You said:
> After the series, the Windows target backend defaults to working in
> non-stop mode (as in, "maint set target-non-stop"), even if
> user-visible mode is all-stop ("set non-stop off"). This is the same
> as the Linux backend.
The "Non-Stop Mode" node in the manual says:
In non-stop mode, when a thread stops to report a debugging event,
_only_ that thread is stopped; GDB does not stop other threads as well,
in contrast to the all-stop mode behavior.
and
'maint set target-non-stop'
'maint show target-non-stop'
This controls whether GDB targets always operate in non-stop mode
even if 'set non-stop' is 'off' (*note Non-Stop Mode::). The
default is 'auto', meaning non-stop mode is enabled if supported by
the target.
So at the very least, there's a very confusing overloading of the
meaning of "works/operates in non-stop mode", perhaps even double
overloading. It seems that you are now saying that the maint setting
is just an implementation detail, specifying whether infrun or the
"backend" (whatever that means) stops threads as appropriate for the
all-stop vs non-stop mode. It also sounds like the "targets" part in
"GDB targets always operate in non-stop mode" is very important, as it
seems that it's specifically meant to tell something important.
Likewise for the "Windows target backend" in what you wrote above. To
a naïve reader such as myself, these all describe the behavior when
the program hits a breakpoint, because, for me, "target backend" is
basically a synonym for "inferior" or "debuggee". But you (and it
seems the manual as well) use it to specify two very different aspects
of behavior.
I think we should clarify the text in the manual, and in particular we
should not use "operate in non-stop mode" for describing both the
user-level "set non-stop on" and the maint command. I would also
welcome some wording in the description of the maint command to
clearly indicate that this setting is of no interest to users
whatsoever, only to GDB developers who work on this mode. Explaining
"backend" (not currently described anywhere in the manual AFAICT)
would also be welcome, as it seems to be relevant for this particular
issue, and could be used in the manual to clarify this.
My current, somewhat confused, understanding of the effects of this
changeset on the Windows native debugging is that users will now be
able to specify "set non-stop on" without explicit need to say "maint
set target-non-stop on" before it, because the latter will now be the
default. However, the default user-level run mode will still be
all-stop mode. Is that correct?
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-30 11:14 ` Eli Zaretskii
@ 2026-04-30 12:01 ` Pedro Alves
2026-04-30 14:15 ` [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode) Pedro Alves
0 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 12:01 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: ssbssa, gdb-patches
On 2026-04-30 12:14, Eli Zaretskii wrote:
>> Date: Thu, 30 Apr 2026 11:13:55 +0100
>> Cc: gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>>
>> Hi Eli,
>>
>> On 2026-04-30 06:55, Eli Zaretskii wrote:
>>>> From: Pedro Alves <pedro@palves.net>
>>>> After the series, the Windows target backend defaults to working in
>>>> non-stop mode (as in, "maint set target-non-stop"), even if
>>>> user-visible mode is all-stop ("set non-stop off"). This is the same
>>>> as the Linux backend.
>>>
>>> I'm not sure this is necessarily a good idea. Windows is weird in
>>> this aspect, as you know very well: the system frequently starts
>>> additional threads for its own purposes, such as handling the Ctrl-C
>>> and Ctrl-BREAK signals. Also, many Windows programs have a separate
>>> UI thread which receives and dispatches the Windows GUI messages.
>>> Letting those threads run by default when the program stops at a
>>> breakpoint is not necessarily the best alternative, and perhaps is
>>> best left to the GDB user in each case (they can do that in
>>> program-specific .gdbinit, if they need). Or am I missing something?
>>
>> What I meant by the above, is that the windows target backend code defaults to
>> working in non-stop mode, but the user does not see any difference. When a
>> breakpoint is hit, GDB still stops all threads. For the user, it still works the
>> same, "set non-stop off" is still the default and I don't plan to change that.
>> The "maint set target-non-stop" setting is just for how the backend communicates
>> with infrun. I call this "all-stop on top of non-stop". Short for
>> "(user-visible) all-stop on top of (backend working in) non-stop (mode)".
>> This is how the Linux backend works too.
>>
>> When the backend works in non-stop mode (the "maint set target-non-stop" setting),
>> infrun takes responsibility for explicitly stopping all threads when necessary,
>> instead of the backend implicitly stopping all threads for every reported
>> internal debug event. It gives more flexibility to infrun, and is a requirement
>> for enabling supporting AMD GPU debugging on Windows. The user doesn't see
>> anything different.
>
> Thanks. Now I'm even more confused than before, because not only do
> your explanations contradict the literal meaning of what you say, they
> seem to also contradict what's in the manual.
>
> You said:
>
>> After the series, the Windows target backend defaults to working in
>> non-stop mode (as in, "maint set target-non-stop"), even if
>> user-visible mode is all-stop ("set non-stop off"). This is the same
>> as the Linux backend.
>
> The "Non-Stop Mode" node in the manual says:
>
> In non-stop mode, when a thread stops to report a debugging event,
> _only_ that thread is stopped; GDB does not stop other threads as well,
> in contrast to the all-stop mode behavior.
This is a user setting, and it is talking about user-visible stops. GDB
internally handles stops that the user does not see. E.g., with
"break foo if cond", the breakpoint hit, gdb evals cond, and if it is
false, gdb re-resumes the program, without the user seeing.
>
> and
>
> 'maint set target-non-stop'
> 'maint show target-non-stop'
>
> This controls whether GDB targets always operate in non-stop mode
> even if 'set non-stop' is 'off' (*note Non-Stop Mode::). The
> default is 'auto', meaning non-stop mode is enabled if supported by
> the target.
>
> So at the very least, there's a very confusing overloading of the
> meaning of "works/operates in non-stop mode", perhaps even double
> overloading. It seems that you are now saying that the maint setting
> is just an implementation detail, specifying whether infrun or the
> "backend" (whatever that means)
The target backend, is the target_ops implementation. gdb/windows-nat.c,
gdb/linux-nat.c, etc. I thought it would be a familiar term to maintainers.
> stops threads as appropriate for the
> all-stop vs non-stop mode. It also sounds like the "targets" part in
> "GDB targets always operate in non-stop mode" is very important, as it
> seems that it's specifically meant to tell something important.
> Likewise for the "Windows target backend" in what you wrote above. To
> a naïve reader such as myself, these all describe the behavior when
> the program hits a breakpoint, because, for me, "target backend" is
> basically a synonym for "inferior" or "debuggee". But you (and it
> seems the manual as well) use it to specify two very different aspects
> of behavior.
See above. The target backend, and inferior are definitely different things.
>
> I think we should clarify the text in the manual, and in particular we
> should not use "operate in non-stop mode" for describing both the
> user-level "set non-stop on" and the maint command. I would also
> welcome some wording in the description of the maint command to
> clearly indicate that this setting is of no interest to users
> whatsoever, only to GDB developers who work on this mode. Explaining
> "backend" (not currently described anywhere in the manual AFAICT)
> would also be welcome, as it seems to be relevant for this particular
> issue, and could be used in the manual to clarify this.
OK, I can take a look at this.
> My current, somewhat confused, understanding of the effects of this
> changeset on the Windows native debugging is that users will now be
> able to specify "set non-stop on" without explicit need to say "maint
> set target-non-stop on" before it, because the latter will now be the
> default. However, the default user-level run mode will still be
> all-stop mode. Is that correct?
>
Even with all patches applied except
'[PATH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on")'
you will still be able to use "set non-stop on".
#A - "set non-stop on":
To support "set non-stop on", the target_ops backend implementation (in this case gdb/windows-nat.c), must also support working in non-stop mode. This means, being able to report stop events to infrun without stopping every thread implicitly. When you do "set non-stop on", gdb checks if the target backend supports it, with a target_supports_non_stop() call. You get that with this series, even if patch 10/11 is not merged.
#B - "set non-stop off" (aka all-stop, the default)
#1 - If the target backend does NOT advertise that it wants to ALWAYS work in non-stop mode, then with "set non-stop off" (the default), then when the inferior hits a breakpoint, finishes a step, etc., the backend stops all threads, and reports the stop to infrun. If infrun decides the stop is not supposed to be seen by the user, infrun re-resumes all threads again. All threads stop and are re-resumed for every debug event even if it's an internal event that does not cause a user-visible stop. This is hidden from the user, though.
#2 - If the target backend DOES advertise that it wants to ALWAYS work in non-stop mode, then with "set non-stop off" (the default), when the inferior hits a breakpoint, finishes a step, etc., the backend does NOT stop all other threads, it reports the stop to infrun just for that thread that got the event. infrun then does its business, and if it realizes the stop should be reported to the user, infrun explicitly stops all threads before presenting the stop to the user. This is more efficient in some scenarios. (For AMD GPU debugging, it's a requirement. And for remote debugging, it also enables gdb talking to the remote side while the remote inferior is running. Etc.)
Patch 10/11 makes the windows target (gdb/windows-nat.c) work like #2 above (for "set non-stop off"). This is what that sentence in the cover letter means.
Thanks,
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode)
2026-04-30 12:01 ` Pedro Alves
@ 2026-04-30 14:15 ` Pedro Alves
2026-04-30 15:09 ` Eli Zaretskii
0 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 14:15 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: ssbssa, gdb-patches
On 2026-04-30 13:01, Pedro Alves wrote:
> On 2026-04-30 12:14, Eli Zaretskii wrote:
>>
>> I think we should clarify the text in the manual, and in particular we
>> should not use "operate in non-stop mode" for describing both the
>> user-level "set non-stop on" and the maint command. I would also
>> welcome some wording in the description of the maint command to
>> clearly indicate that this setting is of no interest to users
>> whatsoever, only to GDB developers who work on this mode. Explaining
>> "backend" (not currently described anywhere in the manual AFAICT)
>> would also be welcome, as it seems to be relevant for this particular
>> issue, and could be used in the manual to clarify this.
>
> OK, I can take a look at this.
How about this below?
Note I added a note saying that this isn't useful to users, but note that the maint commands appendix already starts with:
"In addition to commands intended for GDB users, GDB includes a number of commands intended for GDB developers, that are not documented elsewhere in this manual.
From 897071189f4acf94b6e01e32bc10f2c036181b42 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Thu, 30 Apr 2026 13:12:10 +0100
Subject: [PATCH] Clarify "maint set target-non-stop" in GDB manual
This provides the following improvements to the GDB user manual, where
we document "maint set target-non-stop":
- Clarifies "maint set target-non-stop" vs "set non-stop" .
- Corrects the "auto" description to current reality.
- Gives a couple examples of what "GDB targets" are.
- Documents the "all-stop on top of non-stop" term.
Change-Id: Ia720e5091dd57321fb19e6a306678b834ab822df
commit-id:dbc519ee
---
gdb/doc/gdb.texinfo | 43 ++++++++++++++++++++++++++++++++++++++-----
1 file changed, 38 insertions(+), 5 deletions(-)
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 82306072e8c..f6b9436f841 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -42907,15 +42907,22 @@ to more easily debug problems occurring only in synchronous mode.
@item maint set target-non-stop
@itemx maint show target-non-stop
-This controls whether @value{GDBN} targets always operate in non-stop
-mode even if @code{set non-stop} is @code{off} (@pxref{Non-Stop
-Mode}). The default is @code{auto}, meaning non-stop mode is enabled
-if supported by the target.
+This controls whether @value{GDBN} targets (e.g., the native target,
+or a remote target) operate in non-stop mode even if @code{set
+non-stop} is @code{off} (@pxref{Non-Stop Mode}). The default is
+@code{auto}.
+
+This affects @value{GDBN} internal operation and is largely invisible
+to users. Normally users should not need to change this setting, but
+it can be changed to more easily debug problems occurring only in a
+specific mode.
@table @code
@item maint set target-non-stop auto
This is the default mode. @value{GDBN} controls the target in
-non-stop mode if the target supports it.
+non-stop mode if @code{set non-stop} is @code{on}, or the target tells
+infrun that it wants to operate in non-stop mode even with @code{set
+non-stop} is set to @code{off}.
@item maint set target-non-stop on
@value{GDBN} controls the target in non-stop mode even if the target
@@ -42926,6 +42933,32 @@ does not indicate support.
target supports it.
@end table
+The following @code{set non-stop off} combinations are valid:
+
+@table @code
+@item @code{set non-stop off}, target operating in all-stop mode
+When a @value{GDBN} target is operating in all-stop mode, then when
+a thread hits a breakpoint, finishes a step, etc., the target stops
+all threads, and reports the event to the infrun module in the core of
+@value{GDBN}. If infrun decides the stop is not to be seen by the
+user, infrun re-resumes all threads again. In other words, all
+threads stop and are re-resumed for every debug event, even for debug
+events that are internal and do not cause a user-visible stop.
+
+@item @code{set non-stop off}, target operating in non-stop mode
+When a @value{GDBN} target is operating in non-stop mode in
+combination with @code{set non-stop} set to @code{off}, it is said
+that @value{GDBN} is operating in ``all-stop on top of non-stop''. In
+this scenario, when a thread hits a breakpoint, finishes a step, etc.,
+the target does not immediately stop all other threads. If, while
+processing the event, infrun decides the stop should be reported to
+the user, it then explicitly stops all threads, just before presenting
+the stop to the user; otherwise, infrun re-resumes the stopped thread.
+@end table
+
+@code{set non-stop on} requires the target operating in non-stop mode;
+it is not compatible with the target operating in all-stop mode.
+
@kindex maint set tui-resize-message
@kindex maint show tui-resize-message
@item maint set tui-resize-message
base-commit: bc145a24033381e93bae0ee24add664386c66433
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode)
2026-04-30 14:15 ` [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode) Pedro Alves
@ 2026-04-30 15:09 ` Eli Zaretskii
2026-04-30 16:18 ` [PATCH v2] " Pedro Alves
0 siblings, 1 reply; 29+ messages in thread
From: Eli Zaretskii @ 2026-04-30 15:09 UTC (permalink / raw)
To: Pedro Alves; +Cc: ssbssa, gdb-patches
> Date: Thu, 30 Apr 2026 15:15:42 +0100
> From: Pedro Alves <pedro@palves.net>
> Cc: ssbssa@yahoo.de, gdb-patches@sourceware.org
>
> > OK, I can take a look at this.
>
> How about this below?
>
> Note I added a note saying that this isn't useful to users, but note that the maint commands appendix already starts with:
>
> "In addition to commands intended for GDB users, GDB includes a number of commands intended for GDB developers, that are not documented elsewhere in this manual.
Thanks.
> +The following @code{set non-stop off} combinations are valid:
Shouldn't it also say something about "set non-stop on"?
> +@table @code
> +@item @code{set non-stop off}, target operating in all-stop mode
> +When a @value{GDBN} target is operating in all-stop mode, then when
> +a thread hits a breakpoint, finishes a step, etc., the target stops
> +all threads, and reports the event to the infrun module in the core of
> +@value{GDBN}. If infrun decides the stop is not to be seen by the
> +user, infrun re-resumes all threads again. In other words, all
> +threads stop and are re-resumed for every debug event, even for debug
> +events that are internal and do not cause a user-visible stop.
> +
> +@item @code{set non-stop off}, target operating in non-stop mode
> +When a @value{GDBN} target is operating in non-stop mode in
> +combination with @code{set non-stop} set to @code{off}, it is said
> +that @value{GDBN} is operating in ``all-stop on top of non-stop''. In
> +this scenario, when a thread hits a breakpoint, finishes a step, etc.,
> +the target does not immediately stop all other threads. If, while
> +processing the event, infrun decides the stop should be reported to
> +the user, it then explicitly stops all threads, just before presenting
> +the stop to the user; otherwise, infrun re-resumes the stopped thread.
> +@end table
The second @item says "...in combination with @code{set non-stop} set
to @code{off}", which is a Good Thing, but the first @item doesn't say
the same about the "on" setting. I suggest to make the style more
consistent.
Also, what about the description of "set non-stop", the user-level
command? It currently doesn't even say what is the default. In
addition, the fact that its description says "Enable selection of
non-stop mode" is confusing, because it makes it sound like there's
some other command to actually "select" the non-stop mode. The text
which attempts to explain the "enable" part, viz.:
Note these commands only reflect whether non-stop mode is enabled,
not whether the currently-executing program is being run in non-stop
mode. In particular, the 'set non-stop' preference is only consulted
when GDB starts or connects to the target program, and it is generally
not possible to switch modes once debugging has started. Furthermore,
since not all targets support non-stop mode, even when you have enabled
non-stop mode, GDB may still fall back to all-stop operation by default.
doesn't really justify the "enable" part. It won't surprise anyone
that "set non-stop on" will only work if the target doesn't support
it, and the fact that this command only affects the next inferior to
be started is just a factoid to be mentioned, but it again doesn't
justify the "enable" confusion, IMO.
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode)
2026-04-30 15:09 ` Eli Zaretskii
@ 2026-04-30 16:18 ` Pedro Alves
2026-04-30 16:27 ` Eli Zaretskii
0 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 16:18 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: ssbssa, gdb-patches
On 2026-04-30 16:09, Eli Zaretskii wrote:
>> Date: Thu, 30 Apr 2026 15:15:42 +0100
>> From: Pedro Alves <pedro@palves.net>
>> Cc: ssbssa@yahoo.de, gdb-patches@sourceware.org
>>
>>> OK, I can take a look at this.
>>
>> How about this below?
>>
>> Note I added a note saying that this isn't useful to users, but note that the maint commands appendix already starts with:
>>
>> "In addition to commands intended for GDB users, GDB includes a number of commands intended for GDB developers, that are not documented elsewhere in this manual.
>
> Thanks.
>
>> +The following @code{set non-stop off} combinations are valid:
>
> Shouldn't it also say something about "set non-stop on"?
It does, just below:
+The following @code{set non-stop off} combinations are valid:
+@table @code
+@item @code{set non-stop off}, target operating in all-stop mode
...
+@item @code{set non-stop off}, target operating in non-stop mode
...
+@end table
+@code{set non-stop on} requires the target operating in non-stop mode;
+it is not compatible with the target operating in all-stop mode.
I thought it was clear, but apparently not. I've now converted this to
a 4-entry table. I hope it's clearer this way.
>
>> +@table @code
>> +@item @code{set non-stop off}, target operating in all-stop mode
>> +When a @value{GDBN} target is operating in all-stop mode, then when
>> +a thread hits a breakpoint, finishes a step, etc., the target stops
>> +all threads, and reports the event to the infrun module in the core of
>> +@value{GDBN}. If infrun decides the stop is not to be seen by the
>> +user, infrun re-resumes all threads again. In other words, all
>> +threads stop and are re-resumed for every debug event, even for debug
>> +events that are internal and do not cause a user-visible stop.
>> +
>> +@item @code{set non-stop off}, target operating in non-stop mode
>> +When a @value{GDBN} target is operating in non-stop mode in
>> +combination with @code{set non-stop} set to @code{off}, it is said
>> +that @value{GDBN} is operating in ``all-stop on top of non-stop''. In
>> +this scenario, when a thread hits a breakpoint, finishes a step, etc.,
>> +the target does not immediately stop all other threads. If, while
>> +processing the event, infrun decides the stop should be reported to
>> +the user, it then explicitly stops all threads, just before presenting
>> +the stop to the user; otherwise, infrun re-resumes the stopped thread.
>> +@end table
>
> The second @item says "...in combination with @code{set non-stop} set
> to @code{off}", which is a Good Thing, but the first @item doesn't say
> the same about the "on" setting. I suggest to make the style more
> consistent.
Done.
>
> Also, what about the description of "set non-stop", the user-level
> command? It currently doesn't even say what is the default. In
> addition, the fact that its description says "Enable selection of
> non-stop mode" is confusing, because it makes it sound like there's
> some other command to actually "select" the non-stop mode. The text
> which attempts to explain the "enable" part, viz.:
>
> Note these commands only reflect whether non-stop mode is enabled,
> not whether the currently-executing program is being run in non-stop
> mode. In particular, the 'set non-stop' preference is only consulted
> when GDB starts or connects to the target program, and it is generally
> not possible to switch modes once debugging has started. Furthermore,
> since not all targets support non-stop mode, even when you have enabled
> non-stop mode, GDB may still fall back to all-stop operation by default.
>
> doesn't really justify the "enable" part. It won't surprise anyone
> that "set non-stop on" will only work if the target doesn't support
> it, and the fact that this command only affects the next inferior to
> be started is just a factoid to be mentioned, but it again doesn't
> justify the "enable" confusion, IMO.
>
The "To enter non-stop mode" part is also unnecessary, the pagination
suggestion breaking non-stop was something that was needed early on.
Here's take 2:
From 6a34cd5c0720a103b6a005d5abfcc52b91e15759 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Thu, 30 Apr 2026 13:12:10 +0100
Subject: [PATCH] Clarify "set non-stop" and "maint set target-non-stop" in GDB
manual
This provides the following improvements to the GDB user manual, where
we document "set non-stop" and "maint set target-non-stop":
- In the "set non-stop" section:
- Names "all-stop" earlier.
- Says what mode is the default.
- Removes old pagination suggestion.
- Clarifies text.
- In the "maint set target-non-stop" section:
- Clarifies "maint set target-non-stop" vs "set non-stop" .
- Corrects the "auto" description to current reality.
- Gives a couple examples of what "GDB targets" are.
- Documents the "all-stop on top of non-stop" term.
Change-Id: Ia720e5091dd57321fb19e6a306678b834ab822df
commit-id:dbc519ee
---
gdb/doc/gdb.texinfo | 71 +++++++++++++++++++++++++++++++--------------
1 file changed, 49 insertions(+), 22 deletions(-)
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 82306072e8c..ab0216ff477 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -7528,6 +7528,10 @@ multiple processes.
@c This section is really only a place-holder, and needs to be expanded
@c with more details.
+By default, when a thread stops to report a debugging event,
+@value{GDBN} stops all other threads as well. This is called
+@dfn{all-stop} mode.
+
For some multi-threaded targets, @value{GDBN} supports an optional
mode of operation in which you can examine stopped program threads in
the debugger while other threads continue to execute freely. This
@@ -7546,34 +7550,22 @@ one thread while allowing others to run freely, stepping
one thread while holding all others stopped, or stepping several threads
independently and simultaneously.
-To enter non-stop mode, use this sequence of commands before you run
-or attach to your program:
-
-@smallexample
-# If using the CLI, pagination breaks non-stop.
-set pagination off
-
-# Finally, turn it on!
-set non-stop on
-@end smallexample
-
You can use these commands to manipulate the non-stop mode setting:
@table @code
@kindex set non-stop
@item set non-stop on
-Enable selection of non-stop mode.
+Enable non-stop mode.
@item set non-stop off
-Disable selection of non-stop mode.
+Disable non-stop mode. Also known as enabling all-stop mode. This is
+the default.
@kindex show non-stop
@item show non-stop
Show the current non-stop enablement setting.
@end table
-Note these commands only reflect whether non-stop mode is enabled,
-not whether the currently-executing program is being run in non-stop mode.
-In particular, the @code{set non-stop} preference is only consulted when
-@value{GDBN} starts or connects to the target program, and it is generally
+Note the @code{set non-stop} preference is only consulted when
+@value{GDBN} starts or connects to the target program, and it is
not possible to switch modes once debugging has started. Furthermore,
since not all targets support non-stop mode, even when you have enabled
non-stop mode, @value{GDBN} may still fall back to all-stop operation by
@@ -42907,15 +42899,22 @@ to more easily debug problems occurring only in synchronous mode.
@item maint set target-non-stop
@itemx maint show target-non-stop
-This controls whether @value{GDBN} targets always operate in non-stop
-mode even if @code{set non-stop} is @code{off} (@pxref{Non-Stop
-Mode}). The default is @code{auto}, meaning non-stop mode is enabled
-if supported by the target.
+This controls whether @value{GDBN} targets (e.g., the native target,
+or a remote target) operate in non-stop mode even if @code{set
+non-stop} is @code{off} (@pxref{Non-Stop Mode}). The default is
+@code{auto}.
+
+This affects @value{GDBN} internal operation and is largely invisible
+to users. Normally users should not need to change this setting, but
+it can be changed to more easily debug problems occurring only in a
+specific mode.
@table @code
@item maint set target-non-stop auto
This is the default mode. @value{GDBN} controls the target in
-non-stop mode if the target supports it.
+non-stop mode if @code{set non-stop} is @code{on}, or the target tells
+infrun that it wants to operate in non-stop mode even with @code{set
+non-stop} is set to @code{off}.
@item maint set target-non-stop on
@value{GDBN} controls the target in non-stop mode even if the target
@@ -42926,6 +42925,34 @@ does not indicate support.
target supports it.
@end table
+Here is how @code{set non-stop} and @code{maint set target-non-stop}
+settings combine:
+
+@table @code
+@item @code{set non-stop off}, target operating in all-stop mode
+When a thread hits a breakpoint, finishes a step, etc., the target
+stops all threads, and reports the event to the infrun module in the
+core of @value{GDBN}. If infrun decides the stop is not to be seen by
+the user, infrun re-resumes all threads again. In other words, all
+threads stop and are re-resumed for every debug event, even for debug
+events that are internal and do not cause a user-visible stop.
+
+@item @code{set non-stop off}, target operating in non-stop mode
+When a thread hits a breakpoint, finishes a step, etc., the target
+does not immediately stop all other threads. If, while processing the
+event, infrun decides the stop should be reported to the user, it then
+explicitly stops all threads, just before presenting the stop to the
+user; otherwise, infrun re-resumes the stopped thread. This scenario
+is also called ``all-stop on top of non-stop''.
+
+@item @code{set non-stop on}, target operating in all-stop mode
+This combination is invalid.
+
+@item @code{set non-stop on}, target operating in non-stop mode
+When a thread hits a breakpoint, finishes a step, etc., neither the
+target, nor infrun stop any other thread.
+@end table
+
@kindex maint set tui-resize-message
@kindex maint show tui-resize-message
@item maint set tui-resize-message
base-commit: bc145a24033381e93bae0ee24add664386c66433
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode)
2026-04-30 16:18 ` [PATCH v2] " Pedro Alves
@ 2026-04-30 16:27 ` Eli Zaretskii
2026-04-30 16:33 ` Pedro Alves
0 siblings, 1 reply; 29+ messages in thread
From: Eli Zaretskii @ 2026-04-30 16:27 UTC (permalink / raw)
To: Pedro Alves; +Cc: ssbssa, gdb-patches
> Date: Thu, 30 Apr 2026 17:18:48 +0100
> Cc: ssbssa@yahoo.de, gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
>
> Here's take 2:
Thanks, this LGTM.
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode)
2026-04-30 16:27 ` Eli Zaretskii
@ 2026-04-30 16:33 ` Pedro Alves
0 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 16:33 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: ssbssa, gdb-patches
On 2026-04-30 17:27, Eli Zaretskii wrote:
>> Date: Thu, 30 Apr 2026 17:18:48 +0100
>> Cc: ssbssa@yahoo.de, gdb-patches@sourceware.org
>> From: Pedro Alves <pedro@palves.net>
>>
>> Here's take 2:
>
> Thanks, this LGTM.
>
Thanks, now merged.
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (11 preceding siblings ...)
2026-04-30 5:55 ` [PATCH v3 00/11] Windows non-stop mode Eli Zaretskii
@ 2026-04-30 17:45 ` Pedro Alves
2026-05-08 18:43 ` Tom Tromey
13 siblings, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-04-30 17:45 UTC (permalink / raw)
To: gdb-patches
On 2026-04-29 21:14, Pedro Alves wrote:
> Here is the third iteration of the Windows non-stop work.
FYI, given:
https://inbox.sourceware.org/gdb-patches/042c6d55-fd9a-46e8-ae1b-b78536c5ce60@palves.net/T/#mf5a6d6093c86e697b3b90f4347a6a58ae4129e51
I went ahead and pushed this to master.
Thank you,
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* RE: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit
2026-04-29 20:15 ` [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
@ 2026-05-06 10:01 ` Bouhaouel, Mohamed
2026-05-08 21:37 ` Pedro Alves
0 siblings, 1 reply; 29+ messages in thread
From: Bouhaouel, Mohamed @ 2026-05-06 10:01 UTC (permalink / raw)
To: Pedro Alves, gdb-patches
Hi Pedro,
On Linux, with a remote non-stop target (AS on top of NS), the test you added
is failing! Isn't it supposed to pass in such scenario?
I see the following logs, without the "[Inferior ... exited normally]" message.
Breakpoint 1, main () at /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c:39
39 alarm (30);
(gdb) p other_thread_exits = 0
$1 = 0
(gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: p other_thread_exits = 0
break 44
Breakpoint 2 at 0x555555555216: file /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c, line 44.
(gdb) continue
Continuing.
[New Thread 18153.18154 (id 2)]
Thread 1 "step-over-proce" hit Breakpoint 2, main () at /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c:44
44 exit (0); /* break here main */
(gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: continue to breakpoint: exit syscall
maint show target-non-stop
Whether the target is always in non-stop mode is on.
(gdb) next
[Thread 18153.18153 (id 1) exited]
warning: error removing breakpoint 0 at 0x555555555220
warning: error removing breakpoint 0 at 0x555555555220
warning: error removing breakpoint 0 at 0x555555555220
Remote communication error. Target disconnected: error while reading: Connection reset by peer.
(gdb) FAIL: gdb.threads/step-over-process-exit.exp: which=main: next
To reproduce, simply run gdb.threads/step-over-process-exit.exp with:
export GDBFLAGS="-ex \"maint set target-non-stop on\""
Regards,
--Mohamed
> -----Original Message-----
> From: Pedro Alves <pedro@palves.net>
> Sent: Wednesday, 29 April 2026 22:15
> To: gdb-patches@sourceware.org
> Subject: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread
> exit
>
> This patch fixes gdb.base/ending-run.exp for Windows when the target
> backend supports notifying infrun about thread exit events (which is
> added by the Windows non-stop support, later).
>
> Without this patch, and with the Windows target in non-stop mode
> ("maint set target-non-stop on"), we get, when stepping out of main:
>
> (gdb) PASS: gdb.base/ending-run.exp: Step to return
> next
> 32 }
> (gdb) next
> [Thread 7956.0x2658 exited]
> [Thread 7956.0x2500 exited]
> [Thread 7956.0x2798 exited]
> Command aborted, thread exited.
> (gdb) FAIL: gdb.base/ending-run.exp: step out of main
>
> With the patch, we get:
>
> (gdb) next
> [Thread 9424.0x40c exited]
> [Inferior 1 (process 9424) exited normally]
> (gdb) PASS: gdb.base/ending-run.exp: step out of main
>
> In the failing case, what happens is that "next" enables
> target_thread_events. Then, the main thread causes the whole process
> to exit. On Windows, that makes the main thread report a thread exit
> event, followed by thread exit events for all other threads, except
> the last thread that happens to be the one that exits last. That last
> one reports an exit-process event instead.
>
> Since "next" enabled target_thread_events, the Windows target backend
> reports the main thread's exit event to infrun. And then, since the
> thread that was stepping reported a thread-exit, GDB aborts the "next"
> command.
>
> Stepping out of main is a very common thing to do, and I think
> reporting the thread exit in this case when the whole process is
> exiting isn't very useful. I think we can do better. So instead, if
> we're about to report a thread exit in all-stop mode with the backend
> in non-stop mode, and while stopping all threads, we see a
> whole-process-exit event, prefer processing that event instead of
> reporting the original thread exit.
>
> A similar issue can be triggered on GNU/Linux as well, if we step over
> an exit syscall that is called by any thread other than main. This
> scenario is exercised by the new testcase added by this patch.
>
> Without the patch, the testcase shows:
>
> (gdb) next
> [Thread 0x7ffff7a00640 (LWP 3207243) exited]
> warning: error removing breakpoint 0 at 0x5555555551c3
> warning: error removing breakpoint 0 at 0x5555555551c3
> warning: error removing breakpoint 0 at 0x5555555551c3
> Command aborted, thread exited.
> Cannot remove breakpoints because program is no longer writable.
> Further execution is probably impossible.
> (gdb)
>
> This is fixed for GNU/Linux by the patch, which results in:
>
> (gdb) next
> [Thread 0x7ffff7a00640 (LWP 3230550) exited]
> warning: error removing breakpoint 0 at 0x5555555551c3
> warning: error removing breakpoint 0 at 0x5555555551c3
> warning: error removing breakpoint 0 at 0x5555555551c3
> [Inferior 1 (process 3230539) exited normally]
> (gdb)
>
> Pure all-stop targets (such as GNU/Linux GDBserver unless you force
> non-stop with "maint set target-non-stop on") will unfortunately still
> have the "Further execution is probably impossible." behavior, because
> GDB can't see the process-exit event until the target is re-resumed.
> That's unfortunate, but I don't think that should prevent improving
> non-stop targets. (And eventually I would like remote targets to be
> always "maint set target-non-stop on" by default if possible, too.)
>
> Change-Id: I56559f13e04aeafd812d15e4b408c8337bca5294
> ---
> gdb/infrun.c | 209 ++++++++++++------
> .../gdb.threads/step-over-process-exit.c | 49 ++++
> .../gdb.threads/step-over-process-exit.exp | 66 ++++++
> 3 files changed, 255 insertions(+), 69 deletions(-)
> create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.c
> create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.exp
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index aed66bf844e..11c5d5214d6 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -109,6 +109,8 @@ static bool step_over_info_valid_p (void);
>
> static bool schedlock_applies (struct thread_info *tp);
>
> +static void handle_process_exited (struct execution_control_state *ecs);
> +
> /* Asynchronous signal handler registered as event loop source for
> when we have pending events ready to be passed to the core. */
> static struct async_event_handler *infrun_async_inferior_event_token;
> @@ -4778,7 +4780,68 @@ fetch_inferior_event ()
> don't want to stop all the other threads. */
> if (ecs.event_thread == nullptr
> || !ecs.event_thread->control.in_cond_eval)
> - stop_all_threads_if_all_stop_mode ();
> + {
> + stop_all_threads_if_all_stop_mode ();
> +
> + /* Say the user does "next" over an exit(0) call, or
> + out of main, either of which make the whole process
> + exit. If the target supports target_thread_events,
> + then that is activated while "next" is in progress,
> + and consequently we may be processing a thread-exit
> + event (caused by the process exit) for a thread
> + that reported its exit before the
> + whole-process-exit event is reported for another
> + thread (which is normally going to be the last
> + event out of the inferior, but we don't know for
> + which thread it will be).
> +
> + If we're in 'all-stop on top of non-stop' mode, and
> + indeed the inferior process is exiting, then the
> + stop_all_threads_if_all_stop_mode call above must
> + have seen the process-exit event, as it will see
> + one stop for each and every (running) thread of the
> + process. Look at the pending statuses of all
> + threads, and see if we have a process-exit status.
> + If so, prefer handling it now and report the
> + inferior exit to the user instead of reporting the
> + original thread exit.
> +
> + Do not do this if are handling any other kind of
> + event, like e.g., a breakpoint hit, which the user
> + may be interested in knowing was hit before the
> + process exited. If we ever have a "catch
> + thread-exit" or something similar, we may want to
> + skip this "prefer process-exit" if such a
> + catchpoint is installed. */
> + if (ecs.ws.kind () == TARGET_WAITKIND_THREAD_EXITED
> + && !non_stop && exists_non_stop_target ())
> + {
> + for (thread_info &thread : inf->non_exited_threads ())
> + {
> + if (thread.has_pending_waitstatus ()
> + && ((thread.pending_waitstatus ().kind ()
> + == TARGET_WAITKIND_EXITED)
> + || (thread.pending_waitstatus ().kind ()
> + == TARGET_WAITKIND_SIGNALLED)))
> + {
> + /* Found a pending process-exit event.
> + Prefer handling and reporting it now
> + over the thread-exit event. */
> + infrun_debug_printf
> + ("found pending process-exit event, preferring
> it");
> + ecs.ws = thread.pending_waitstatus ();
> + thread.clear_pending_waitstatus ();
> + ecs.event_thread = nullptr;
> + ecs.ptid = thread.ptid;
> + /* Re-record the last target status. */
> + set_last_target_status (ecs.target, ecs.ptid,
> + ecs.ws);
> + handle_process_exited (&ecs);
> + break;
> + }
> + }
> + }
> + }
>
> clean_up_just_stopped_threads_fsms (&ecs);
>
> @@ -6104,6 +6167,81 @@ handle_thread_exited (execution_control_state
> *ecs)
> return true;
> }
>
> +/* Handle a process exit event. */
> +
> +static void
> +handle_process_exited (execution_control_state *ecs)
> +{
> + /* Depending on the system, ecs->ptid may point to a thread or to a
> + process. On some targets, target_mourn_inferior may need to have
> + access to the just-exited thread. That is the case of
> + GNU/Linux's "checkpoint" support, for example. Switch context
> + appropriately. */
> + thread_info *thr = ecs->target->find_thread (ecs->ptid);
> + if (thr != nullptr)
> + switch_to_thread (thr);
> + else
> + {
> + inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
> + switch_to_inferior_no_thread (inf);
> + }
> +
> + handle_vfork_child_exec_or_exit (0);
> + target_terminal::ours (); /* Must do this before mourn anyway. */
> +
> + /* Clearing any previous state of convenience variables. */
> + clear_exit_convenience_vars ();
> +
> + if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
> + {
> + /* Record the exit code in the convenience variable $_exitcode,
> + so that the user can inspect this again later. */
> + set_internalvar_integer (lookup_internalvar ("_exitcode"),
> + (LONGEST) ecs->ws.exit_status ());
> +
> + /* Also record this in the inferior itself. */
> + current_inferior ()->has_exit_code = true;
> + current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
> +
> + /* Support the --return-child-result option. */
> + return_child_result_value = ecs->ws.exit_status ();
> +
> + interps_notify_exited (ecs->ws.exit_status ());
> + }
> + else
> + {
> + struct gdbarch *gdbarch = current_inferior ()->arch ();
> +
> + if (gdbarch_gdb_signal_to_target_p (gdbarch))
> + {
> + /* Set the value of the internal variable $_exitsignal,
> + which holds the signal uncaught by the inferior. */
> + set_internalvar_integer (lookup_internalvar ("_exitsignal"),
> + gdbarch_gdb_signal_to_target (gdbarch,
> + ecs->ws.sig
> ()));
> + }
> + else
> + {
> + /* We don't have access to the target's method used for
> + converting between signal numbers (GDB's internal
> + representation <-> target's representation).
> + Therefore, we cannot do a good job at displaying this
> + information to the user. It's better to just warn
> + her about it (if infrun debugging is enabled), and
> + give up. */
> + infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
> + "signal number.");
> + }
> +
> + interps_notify_signal_exited (ecs->ws.sig ());
> + }
> +
> + gdb_flush (gdb_stdout);
> + target_mourn_inferior (inferior_ptid);
> + stop_print_frame = false;
> + stop_waiting (ecs);
> +}
> +
> /* Given an execution control state that has been freshly filled in by
> an event from the inferior, figure out what it means and take
> appropriate action.
> @@ -6313,74 +6451,7 @@ handle_inferior_event (struct
> execution_control_state *ecs)
>
> case TARGET_WAITKIND_EXITED:
> case TARGET_WAITKIND_SIGNALLED:
> - {
> - /* Depending on the system, ecs->ptid may point to a thread or
> - to a process. On some targets, target_mourn_inferior may
> - need to have access to the just-exited thread. That is the
> - case of GNU/Linux's "checkpoint" support, for example.
> - Call the switch_to_xxx routine as appropriate. */
> - thread_info *thr = ecs->target->find_thread (ecs->ptid);
> - if (thr != nullptr)
> - switch_to_thread (thr);
> - else
> - {
> - inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
> - switch_to_inferior_no_thread (inf);
> - }
> - }
> - handle_vfork_child_exec_or_exit (0);
> - target_terminal::ours (); /* Must do this before mourn anyway. */
> -
> - /* Clearing any previous state of convenience variables. */
> - clear_exit_convenience_vars ();
> -
> - if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
> - {
> - /* Record the exit code in the convenience variable $_exitcode, so
> - that the user can inspect this again later. */
> - set_internalvar_integer (lookup_internalvar ("_exitcode"),
> - (LONGEST) ecs->ws.exit_status ());
> -
> - /* Also record this in the inferior itself. */
> - current_inferior ()->has_exit_code = true;
> - current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
> -
> - /* Support the --return-child-result option. */
> - return_child_result_value = ecs->ws.exit_status ();
> -
> - interps_notify_exited (ecs->ws.exit_status ());
> - }
> - else
> - {
> - struct gdbarch *gdbarch = current_inferior ()->arch ();
> -
> - if (gdbarch_gdb_signal_to_target_p (gdbarch))
> - {
> - /* Set the value of the internal variable $_exitsignal,
> - which holds the signal uncaught by the inferior. */
> - set_internalvar_integer (lookup_internalvar ("_exitsignal"),
> - gdbarch_gdb_signal_to_target (gdbarch,
> - ecs->ws.sig ()));
> - }
> - else
> - {
> - /* We don't have access to the target's method used for
> - converting between signal numbers (GDB's internal
> - representation <-> target's representation).
> - Therefore, we cannot do a good job at displaying this
> - information to the user. It's better to just warn
> - her about it (if infrun debugging is enabled), and
> - give up. */
> - infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
> - "signal number.");
> - }
> -
> - interps_notify_signal_exited (ecs->ws.sig ());
> - }
> -
> - gdb_flush (gdb_stdout);
> - target_mourn_inferior (inferior_ptid);
> - stop_print_frame = false;
> + handle_process_exited (ecs);
> stop_waiting (ecs);
> return;
>
> diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.c
> b/gdb/testsuite/gdb.threads/step-over-process-exit.c
> new file mode 100644
> index 00000000000..a9b86751596
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/step-over-process-exit.c
> @@ -0,0 +1,49 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025-2026 Free Software Foundation, Inc.
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 3 of the License, or
> + (at your option) any later version.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program. If not, see <http://www.gnu.org/licenses/>. */
> +
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <pthread.h>
> +
> +volatile int other_thread_exits = 0;
> +
> +static void *
> +thread_function (void *arg)
> +{
> + if (other_thread_exits)
> + exit (0); /* break here other */
> +
> + while (1)
> + sleep (1);
> +}
> +
> +int
> +main ()
> +{
> + pthread_t thread;
> +
> + alarm (30);
> +
> + pthread_create (&thread, NULL, thread_function, NULL);
> +
> + if (!other_thread_exits)
> + exit (0); /* break here main */
> +
> + while (1)
> + sleep (1);
> + return 0;
> +}
> diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.exp
> b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
> new file mode 100644
> index 00000000000..6c98ebe3956
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
> @@ -0,0 +1,66 @@
> +# This testcase is part of GDB, the GNU debugger.
> +
> +# Copyright 2025-2026 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program. If not, see <http://www.gnu.org/licenses/>.
> +
> +# Test stepping over an exit syscall from both the main thread, and a
> +# non-main thread.
> +
> +if { [target_info exists exit_is_reliable] } {
> + set exit_is_reliable [target_info exit_is_reliable]
> +} else {
> + set exit_is_reliable [expr {![target_info exists use_gdb_stub]}]
> +}
> +require {expr {$exit_is_reliable}}
> +
> +standard_testfile
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug
> pthread}] } {
> + return -1
> +}
> +
> +# WHICH is which thread exits the process. Can be "main" for main
> +# thread, or "other" for the non-main thread.
> +
> +proc test {which} {
> + if {![runto_main]} {
> + return -1
> + }
> +
> + set other [expr {$which == "other"}]
> + gdb_test "p other_thread_exits = $other" " = $other"
> +
> + set break_line [gdb_get_line_number "break here $which"]
> + gdb_breakpoint $break_line
> + gdb_continue_to_breakpoint "exit syscall"
> +
> + set target_non_stop [is_target_non_stop]
> +
> + gdb_test_multiple "next" "" {
> + -re -wrap "$::inferior_exited_re normally\\\]" {
> + pass $gdb_test_name
> + }
> + -re -wrap "Further execution is probably impossible\\." {
> + # With a target in all-stop, this is the best we can do.
> + # We should not see this with a target backend in non-stop
> + # mode, however.
> + gdb_assert !$target_non_stop $gdb_test_name
> + }
> + }
> +}
> +
> +foreach_with_prefix which {main other} {
> + test $which
> +}
> --
> 2.53.0
>
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
` (12 preceding siblings ...)
2026-04-30 17:45 ` [PATCH v3 00/11] Windows non-stop mode Pedro Alves
@ 2026-05-08 18:43 ` Tom Tromey
2026-05-08 21:27 ` Pedro Alves
13 siblings, 1 reply; 29+ messages in thread
From: Tom Tromey @ 2026-05-08 18:43 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Here is the third iteration of the Windows non-stop work.
As mentioned elsewhere, this caused some regressions in the internal
AdaCore test suite.
On x86 Windows 2019 we see the "unwaited-for children" message
(gdb) run my_which2
Starting program: C:\it\sbx\wave\x86-windows\gdb_version-head_test\tmp\test\gdb-TS-8qgi1zlz\K509-012__path_cmd\my_which.exe my_which2
C:\it\sbx\wave\x86-windows\gdb_version-head_test\tmp\test\gdb-TS-8qgi1zlz\K509-012__path_cmd\mybin2\my_which2.exe
No unwaited-for children left.
FAILED:K509-012__path_cmd:run my_which2
A few tests seem to fail this way but they are all pretty basic things
like the above.
The same happens on x86-64 Windows 2019.
There's another test that adds some event listeners from Python like:
gdb.events.stop.connect(signal_stop_handler)
gdb.events.stop.connect(breakpoint_stop_handler)
gdb.events.exited.connect(exit_handler)
gdb.events.cont.connect(continue_handler)
These just print what happens. The test started failing because what
exactly happens has changed:
--- Regression: K224-002__py-events:what_happened
- Could not match
event type: continue
event type: stop
event !! FAILS HERE -> type: stop
stop reason: breakpoint
breakpoint number: @NUMBER
all threads stopped
- Against input line
event type: continue
event type: continue
event type: stop
event type: stop
stop reason: breakpoint
breakpoint number: 2
all threads stopped
That is, two stop events seem to be produced?
According to the notes this test is a port of gdb.python/py-events.exp,
though I am not sure if they have diverged.
I'm not really sure that the Windows 2019 detail is completely relevant
because that seems to be the only version of the OS that we're using to
test gdb HEAD.
Anyway I am happy to produce logs or something if that would help.
Eventually I'll probably end up debugging this myself but it might be a
while.
thanks,
Tom
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-05-08 18:43 ` Tom Tromey
@ 2026-05-08 21:27 ` Pedro Alves
2026-05-22 0:22 ` Pedro Alves
2026-06-02 19:00 ` Tom Tromey
0 siblings, 2 replies; 29+ messages in thread
From: Pedro Alves @ 2026-05-08 21:27 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
On 2026-05-08 19:43, Tom Tromey wrote:
> As mentioned elsewhere, this caused some regressions in the internal
> AdaCore test suite.
Thanks.
>
>
> On x86 Windows 2019 we see the "unwaited-for children" message
>
> (gdb) run my_which2
> Starting program: C:\it\sbx\wave\x86-windows\gdb_version-head_test\tmp\test\gdb-TS-8qgi1zlz\K509-012__path_cmd\my_which.exe my_which2
> C:\it\sbx\wave\x86-windows\gdb_version-head_test\tmp\test\gdb-TS-8qgi1zlz\K509-012__path_cmd\mybin2\my_which2.exe
> No unwaited-for children left.
> FAILED:K509-012__path_cmd:run my_which2
>
> A few tests seem to fail this way but they are all pretty basic things
> like the above.
That sounds like the same symptom of the problem you saw last year, though I
think it was another test back then.
>
> The same happens on x86-64 Windows 2019.
>
>
> There's another test that adds some event listeners from Python like:
>
> gdb.events.stop.connect(signal_stop_handler)
> gdb.events.stop.connect(breakpoint_stop_handler)
> gdb.events.exited.connect(exit_handler)
> gdb.events.cont.connect(continue_handler)
>
> These just print what happens. The test started failing because what
> exactly happens has changed:
>
> --- Regression: K224-002__py-events:what_happened
> - Could not match
> event type: continue
> event type: stop
> event !! FAILS HERE -> type: stop
> stop reason: breakpoint
> breakpoint number: @NUMBER
> all threads stopped
> - Against input line
> event type: continue
> event type: continue
> event type: stop
> event type: stop
> stop reason: breakpoint
> breakpoint number: 2
> all threads stopped
>
> That is, two stop events seem to be produced?
>
I'm afraid I'm not able to parse the output above off hand.
> According to the notes this test is a port of gdb.python/py-events.exp,
> though I am not sure if they have diverged.
Hmm, I'm cross compiling Cygwin gdb and that doesn't include Python support.
I'll see if I can set things up to enable Python support and run
gdb.python/py-events.exp with Cygwin.
>
> I'm not really sure that the Windows 2019 detail is completely relevant
> because that seems to be the only version of the OS that we're using to
> test gdb HEAD.
What was the other system that you ran tests on, where they passed?
I don't have a Windows 2019 machine handy. I've been testing on Windows 11
with latest Microsoft updates.
>
> Anyway I am happy to produce logs or something if that would help.
> Eventually I'll probably end up debugging this myself but it might be a
> while.
Reducing things to something that I could reproduce myself would help a lot.
We already tried that last year, and the tests I came up with that mimicked
yours didn't trigger the issues for me, unfortunately. I don't think we ever
confirmed if my tests would trigger the problems for you, though.
Confirming that "maint show target-non-stop" says non-stop is on, and
checking whether "maint set target-non-stop off" makes a difference might help.
"set debug infrun 1" + the usual "set debugfoo 1" Windows target logs are probably
helpful.
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit
2026-05-06 10:01 ` Bouhaouel, Mohamed
@ 2026-05-08 21:37 ` Pedro Alves
2026-05-11 11:58 ` Bouhaouel, Mohamed
0 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2026-05-08 21:37 UTC (permalink / raw)
To: Bouhaouel, Mohamed, gdb-patches
Hi!
On 2026-05-06 11:01, Bouhaouel, Mohamed wrote:
> Hi Pedro,
>
> On Linux, with a remote non-stop target (AS on top of NS), the test you added
> is failing! Isn't it supposed to pass in such scenario?
It is, like _all_ tests are supposed to pass, old and new. :-) Sometimes new tests expose
preexisting problems on some targets/ports, though.
>
> I see the following logs, without the "[Inferior ... exited normally]" message.
>
> Breakpoint 1, main () at /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c:39
> 39 alarm (30);
> (gdb) p other_thread_exits = 0
> $1 = 0
> (gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: p other_thread_exits = 0
> break 44
> Breakpoint 2 at 0x555555555216: file /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c, line 44.
> (gdb) continue
> Continuing.
> [New Thread 18153.18154 (id 2)]
>
> Thread 1 "step-over-proce" hit Breakpoint 2, main () at /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c:44
> 44 exit (0); /* break here main */
> (gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: continue to breakpoint: exit syscall
> maint show target-non-stop
> Whether the target is always in non-stop mode is on.
> (gdb) next
> [Thread 18153.18153 (id 1) exited]
> warning: error removing breakpoint 0 at 0x555555555220
> warning: error removing breakpoint 0 at 0x555555555220
> warning: error removing breakpoint 0 at 0x555555555220
> Remote communication error. Target disconnected: error while reading: Connection reset by peer.
> (gdb) FAIL: gdb.threads/step-over-process-exit.exp: which=main: next
>
> To reproduce, simply run gdb.threads/step-over-process-exit.exp with:
>
> export GDBFLAGS="-ex \"maint set target-non-stop on\""
>
Could you file a bug about this, please?
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* RE: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit
2026-05-08 21:37 ` Pedro Alves
@ 2026-05-11 11:58 ` Bouhaouel, Mohamed
0 siblings, 0 replies; 29+ messages in thread
From: Bouhaouel, Mohamed @ 2026-05-11 11:58 UTC (permalink / raw)
To: Pedro Alves, gdb-patches
Hi,
Thanks, Pedro for your feedback! This is the bug report I filed:
https://sourceware.org/bugzilla/show_bug.cgi?id=34142
--Mohamed
> -----Original Message-----
> From: Pedro Alves <pedro@palves.net>
> Sent: Friday, 8 May 2026 23:38
> To: Bouhaouel, Mohamed <mohamed.bouhaouel@intel.com>; gdb-
> patches@sourceware.org
> Subject: Re: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over
> thread exit
>
> Hi!
>
> On 2026-05-06 11:01, Bouhaouel, Mohamed wrote:
> > Hi Pedro,
> >
> > On Linux, with a remote non-stop target (AS on top of NS), the test you
> added
> > is failing! Isn't it supposed to pass in such scenario?
>
> It is, like _all_ tests are supposed to pass, old and new. :-) Sometimes new
> tests expose
> preexisting problems on some targets/ports, though.
>
> >
> > I see the following logs, without the "[Inferior ... exited normally]" message.
> >
> > Breakpoint 1, main () at /home/gta/sources/gdb-
> worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c:39
> > 39 alarm (30);
> > (gdb) p other_thread_exits = 0
> > $1 = 0
> > (gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: p
> other_thread_exits = 0
> > break 44
> > Breakpoint 2 at 0x555555555216: file /home/gta/sources/gdb-
> worktrees/upstream/gdb/testsuite/gdb.threads/step-over-process-exit.c, line
> 44.
> > (gdb) continue
> > Continuing.
> > [New Thread 18153.18154 (id 2)]
> >
> > Thread 1 "step-over-proce" hit Breakpoint 2, main () at
> /home/gta/sources/gdb-worktrees/upstream/gdb/testsuite/gdb.threads/step-
> over-process-exit.c:44
> > 44 exit (0); /* break here main */
> > (gdb) PASS: gdb.threads/step-over-process-exit.exp: which=main: continue
> to breakpoint: exit syscall
> > maint show target-non-stop
> > Whether the target is always in non-stop mode is on.
> > (gdb) next
> > [Thread 18153.18153 (id 1) exited]
> > warning: error removing breakpoint 0 at 0x555555555220
> > warning: error removing breakpoint 0 at 0x555555555220
> > warning: error removing breakpoint 0 at 0x555555555220
> > Remote communication error. Target disconnected: error while reading:
> Connection reset by peer.
> > (gdb) FAIL: gdb.threads/step-over-process-exit.exp: which=main: next
> >
> > To reproduce, simply run gdb.threads/step-over-process-exit.exp with:
> >
> > export GDBFLAGS="-ex \"maint set target-non-stop on\""
> >
>
> Could you file a bug about this, please?
>
> Pedro Alves
Intel Deutschland GmbH
Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-05-08 21:27 ` Pedro Alves
@ 2026-05-22 0:22 ` Pedro Alves
2026-06-02 19:00 ` Tom Tromey
1 sibling, 0 replies; 29+ messages in thread
From: Pedro Alves @ 2026-05-22 0:22 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
Hi!
On 2026-05-08 22:27, Pedro Alves wrote:
> On 2026-05-08 19:43, Tom Tromey wrote:
>> There's another test that adds some event listeners from Python like:
>>
>> gdb.events.stop.connect(signal_stop_handler)
>> gdb.events.stop.connect(breakpoint_stop_handler)
>> gdb.events.exited.connect(exit_handler)
>> gdb.events.cont.connect(continue_handler)
>>
>> These just print what happens. The test started failing because what
>> exactly happens has changed:
>>
>> --- Regression: K224-002__py-events:what_happened
>> - Could not match
>> event type: continue
>> event type: stop
>> event !! FAILS HERE -> type: stop
>> stop reason: breakpoint
>> breakpoint number: @NUMBER
>> all threads stopped
>> - Against input line
>> event type: continue
>> event type: continue
>> event type: stop
>> event type: stop
>> stop reason: breakpoint
>> breakpoint number: 2
>> all threads stopped
>>
>> That is, two stop events seem to be produced?
>>
>
> I'm afraid I'm not able to parse the output above off hand.
>
>> According to the notes this test is a port of gdb.python/py-events.exp,
>> though I am not sure if they have diverged.
>
I was looking at the output of gdb.python/py-events.exp on Linux, and I see two stop events there:
Breakpoint 2, first () at /home/pedro/rocm/gdb/src/gdb/testsuite/gdb.python/py-events.c:30
30 for (i = 0; i < 2; i++)
event type: stop
event type: stop
stop reason: breakpoint
first breakpoint number: 2
breakpoint number: 2
breakpoint number: 3
all threads stopped
(gdb) PASS: gdb.python/py-events.exp: continue
Two stops seems to be what happens for every test in that testcase. I noticed that the .exp file
matches with:
# Test continue event and breakpoint stop event
gdb_test "continue" ".*event type: continue.*
.*event type: stop.*
.*stop reason: breakpoint.*
.*first breakpoint number: 2.*
.*breakpoint number: 2.*
.*breakpoint number: 3.*
all threads stopped.*"
So if gdb prints two stop events by mistake, the testcase will still pass.
I went as further back as commit f1cc4f02cb558d513cb4575211dbbb690391618f (May 21 2023), and I saw
the same there, though.
Looking at gdb.python/-events.py, we have:
def invoke(self, arg, from_tty):
gdb.events.stop.connect(signal_stop_handler)
gdb.events.stop.connect(breakpoint_stop_handler)
So we register two callbacks for the same event, so it's really expected. With:
diff --git i/gdb/testsuite/gdb.python/py-events.py w/gdb/testsuite/gdb.python/py-events.py
index 2cb48e4eccf..a5ac083bfd4 100644
--- i/gdb/testsuite/gdb.python/py-events.py
+++ w/gdb/testsuite/gdb.python/py-events.py
@@ -20,7 +20,7 @@ import gdb
def signal_stop_handler(event):
if isinstance(event, gdb.StopEvent):
- print("event type: stop")
+ print("event type: stop (1)")
if isinstance(event, gdb.SignalEvent):
print("stop reason: signal")
print("stop signal: %s" % (event.stop_signal))
@@ -30,7 +30,7 @@ def signal_stop_handler(event):
def breakpoint_stop_handler(event):
if isinstance(event, gdb.StopEvent):
- print("event type: stop")
+ print("event type: stop (2)")
I see:
Breakpoint 2, first () at /home/pedro/rocm/gdb/src/gdb/testsuite/gdb.python/py-events.c:30
30 for (i = 0; i < 2; i++)
event type: stop (1)
event type: stop (2)
stop reason: breakpoint
So that seems totally expected, at least for gdb.python/py-events.exp.
I don't suppose that somehow your internal testcase does the same and somehow that
wasn't noticed before?
> Hmm, I'm cross compiling Cygwin gdb and that doesn't include Python support.
> I'll see if I can set things up to enable Python support and run
> gdb.python/py-events.exp with Cygwin.
>
I managed to do this. That exposed a number of pre-existing problems, as usual...
All fixed with this series:
https://inbox.sourceware.org/gdb-patches/20260522001626.393908-1-pedro@palves.net/T/
Nothing related to the non-stop support, though.
Pedro Alves
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v3 00/11] Windows non-stop mode
2026-05-08 21:27 ` Pedro Alves
2026-05-22 0:22 ` Pedro Alves
@ 2026-06-02 19:00 ` Tom Tromey
1 sibling, 0 replies; 29+ messages in thread
From: Tom Tromey @ 2026-06-02 19:00 UTC (permalink / raw)
To: Pedro Alves; +Cc: Tom Tromey, gdb-patches
>> Anyway I am happy to produce logs or something if that would help.
Pedro> Confirming that "maint show target-non-stop" says non-stop is on, and
Pedro> checking whether "maint set target-non-stop off" makes a difference might help.
Ok I forgot to do this.
Pedro> "set debug infrun 1" + the usual "set debugfoo 1" Windows target
Pedro> logs are probably helpful.
I filed a bug that has some info.
https://sourceware.org/bugzilla/show_bug.cgi?id=34195
This has a log that was made with "set debug infrun 1" and "set
debugevents 1".
Tom
^ permalink raw reply [flat|nested] 29+ messages in thread
end of thread, other threads:[~2026-06-02 19:01 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-04-29 20:14 ` [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
2026-04-29 20:14 ` [PATCH v3 02/11] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
2026-04-29 20:14 ` [PATCH v3 03/11] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
2026-04-29 20:15 ` [PATCH v3 04/11] Introduce windows_nat::event_code_to_string Pedro Alves
2026-04-29 20:15 ` [PATCH v3 05/11] Windows gdb: Add non-stop support Pedro Alves
2026-04-29 20:15 ` [PATCH v3 06/11] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
2026-04-29 20:15 ` [PATCH v3 07/11] Windows gdb: extra thread info => show exiting Pedro Alves
2026-04-29 20:15 ` [PATCH v3 08/11] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
2026-04-29 20:15 ` [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
2026-05-06 10:01 ` Bouhaouel, Mohamed
2026-05-08 21:37 ` Pedro Alves
2026-05-11 11:58 ` Bouhaouel, Mohamed
2026-04-29 20:15 ` [PATCH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
2026-04-29 20:15 ` [PATCH v3 11/11] Mention Windows non-stop support in NEWS Pedro Alves
2026-04-30 5:55 ` [PATCH v3 00/11] Windows non-stop mode Eli Zaretskii
2026-04-30 10:13 ` Pedro Alves
2026-04-30 11:14 ` Eli Zaretskii
2026-04-30 12:01 ` Pedro Alves
2026-04-30 14:15 ` [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode) Pedro Alves
2026-04-30 15:09 ` Eli Zaretskii
2026-04-30 16:18 ` [PATCH v2] " Pedro Alves
2026-04-30 16:27 ` Eli Zaretskii
2026-04-30 16:33 ` Pedro Alves
2026-04-30 17:45 ` [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-05-08 18:43 ` Tom Tromey
2026-05-08 21:27 ` Pedro Alves
2026-05-22 0:22 ` Pedro Alves
2026-06-02 19:00 ` Tom Tromey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox