* [PATCH v2 00/47] Windows non-stop mode
@ 2025-05-19 13:22 Pedro Alves
2025-05-19 13:22 ` [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes Pedro Alves
` (47 more replies)
0 siblings, 48 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
After a long while (a year, w00t!), here is another iteration of the
Windows non-stop work.
I've pushed the series to the users/palves/windows-non-stop-v2 branch
on sourceware.org, for your convenience.
Compared to the previous version from a year ago (gasp!), here:
https://inbox.sourceware.org/gdb-patches/20240507234233.371123-1-pedro@palves.net/
... many issues where fixed, and it should be a lot more stable. I
think I fixed the problems Tromey found out when he ran the series
against the AdaCore testsuite, particularly around detaching.
The series is bigger, it is now 47 patches instead of 34.
These patches are new:
+ [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes
+ [PATCH v2 02/47] Add test for continuing with some threads running
+ [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call
+ [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue
+ [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state
+ [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code
+ [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data
+ [PATCH v2 38/47] Introduce windows_nat::event_code_to_string
+ [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context
+ [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
+ [PATCH v2 43/47] Windows gdb: extra thread info => show exiting
+ [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp
+ [PATCH v2 45/47] infrun: with AS+NS, prefer process exit over thread exit
+ [PATCH v2 46/47] Windows gdb: Always non-stop
The last one above (#46) was split from the main non-stop patch so
that AS-on-NS is only activated after the new infrun changes are
merged.
Note that patches above with subject that starts with "infrun" or
"thread_info" are changes to common code.
This patch from the previous version of the series was dropped:
- windows_per_inferior::continue_one_thread, unify WoW64/non-WoW64 paths
... as no longer needed, since Hannes's:
commit b2682eade4db993bc8231007ba94aec4e4728c61
Author: Hannes Domani <ssbssa@yahoo.de>
AuthorDate: Fri Dec 6 14:04:00 2024 +0100
Reduce WOW64 code duplication
The "Windows gdb: Add non-stop support" patch changed in some
important areas compared to the previous version. Here's a summary:
- We can't use DBG_REPLY_LATER with thread/process exit events. For
those, we need to use the pre-exiting pending-status mechanism.
- When a thread that is abruptly killed has a pending DBG_REPLY_LATER
reply queued in the kernel, we always see that queued event first
before the thread exit event.
- The above two points required careful handling of the
(th->suspended == -1) state in some places.
- windows-nat.c was in some cases deleting the thread before
reporting TARGET_WAITKIND_THREAD_EXITED to infrun (when
target_thread_events was active), resulting in a double free,
because infrun deletes the thread as well. OTOH, when
TARGET_WAITKIND_THREAD_EXITED was reported and windows-nat.c didn't
delete the thread, nothing was deleting the corresponding
windows_thread_info object, resulting weird confusion in the
windows backend, with stale threads still in the backend list.
- TARGET_WAITKIND_NO_RESUMED is now implemented.
- The new "Add gdb.threads/leader-exit-schedlock.exp" patch adds a
test that exercises all the points above. The pre-existing
"gdb.base/ending-run.exp" testcase also covers some aspects.
- Adjusted to new model introduced by the
"thread_info::executing+resumed -> thread_info::internal_state"
patch earlier in the series.
- Adjusted to windows_thread_info being owned by the thread_info
object.
- The do_initial_windows_stuff run-to-initial-breakpoint loop now
handles target_thread_events enabled correctly.
- Fixes detaching with queued DBG_REPLY_LATER events on the kernel
side. 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, otherwise the inferior
dies immediately after the detach, due to an unhandled exception.
- The intro text added to windows-nat.c has more paragraphs and
sections detailing the new things I've learned along the way since
the last time.
In the NEWS patch, I'm now mentioning scheduler-locking support too.
Previously I only mentioned non-stop.
I've added "Approved-By:" tags from last year's review, to patches
that haven't changed since. Some of them I could have pushed back
then, but I didn't want to do that until the v2 series was complete,
in case some problems I was addressing in v2 were caused by those
preparatory patches.
The "prerequisite-patch-id" at the bottom is:
[PATCH] Fix build when RUSAGE_THREAD is not available & add warning
https://inbox.sourceware.org/gdb-patches/20250517213056.3251858-1-pedro@palves.net/T/#u
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 (47):
Make default_gdb_exit resilient to failed closes
Add test for continuing with some threads running
infrun: Remove unnecessary currently_stepping call
infrun: Split currently_stepping, fix sw watchpoints issue
thread_info::executing+resumed -> thread_info::internal_state
Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff
Windows gdb: Eliminate global current_process.dr[8] global
Windows gdb+gdbserver: New find_thread, replaces
thread_rec(DONT_INVALIDATE_CONTEXT)
Windows gdb: handle_output_debug_string return type
Windows gdb: Eliminate reload_context
Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls
Windows gdb+gdbserver: Eliminate DONT_SUSPEND
Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec
Windows gdb: Simplify windows_nat_target::wait
Windows gdb+gdbserver: Move suspending thread to when returning event
Windows gdb: Introduce continue_last_debug_event_main_thread
Windows gdb: Introduce windows_continue_flags
Windows gdb: Factor code out of windows_nat_target::windows_continue
Windows gdb: Pending stop and current_event
Windows gdb+gdbserver: Elim desired_stop_thread_id / rework
pending_stops
Windows gdb+gdbserver: Introduce get_last_debug_event_ptid
Windows gdb: Can't pass signal to thread other than last stopped
thread
Windows gdbserver: Fix scheduler-locking
Windows gdb: Enable "set scheduler-locking on"
Windows gdbserver: Eliminate soft-interrupt mechanism
Windows gdb+gdbserver: Make current_event per-thread state
Windows gdb+gdbserver: Make last_sig per-thread state
Windows gdb+gdbserver: Make siginfo_er per-thread state
Add backpointer from windows_thread_info to windows_process_info
Windows gdb+gdbserver: Share $_siginfo reading code
Windows gdb+gdbserver: Eliminate struct pending_stop
Windows gdb: Change serial_event management
Windows gdb: cygwin_set_dr => windows_set_dr, etc.
Windows gdb: Avoid writing debug registers if watchpoint hit pending
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: Eliminate invalidate_context
Windows gdb: Watchpoints while running (internal vs external stops)
gdb_test_multiple: Anchor prompt match if -lbl
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 scheduler-locking and non-stop support in NEWS
gdb/NEWS | 6 +
gdb/aarch64-tdep.c | 2 +-
gdb/amd-dbgapi-target.c | 8 +-
gdb/breakpoint.c | 9 +-
gdb/bsd-uthread.c | 4 +-
gdb/fork-child.c | 4 +-
gdb/frame.c | 4 +-
gdb/gcore.c | 4 +-
gdb/gdbthread.h | 150 +-
gdb/i386-tdep.c | 4 +-
gdb/inf-ptrace.c | 2 +-
gdb/infcall.c | 8 +-
gdb/infcmd.c | 39 +-
gdb/inferior.h | 4 +-
gdb/inflow.c | 2 +-
gdb/infrun.c | 429 ++--
gdb/infrun.h | 13 +-
gdb/linux-fork.c | 9 +-
gdb/linux-nat.c | 44 +-
gdb/linux-thread-db.c | 4 +-
gdb/mi/mi-cmd-var.c | 4 +-
gdb/mi/mi-interp.c | 2 +-
gdb/mi/mi-main.c | 8 +-
gdb/nat/windows-nat.c | 176 +-
gdb/nat/windows-nat.h | 223 +-
gdb/process-stratum-target.c | 6 +-
gdb/python/py-infthread.c | 6 +-
gdb/record-btrace.c | 20 +-
gdb/record-full.c | 6 +-
gdb/regcache.c | 2 +-
gdb/remote.c | 76 +-
gdb/sol-thread.c | 4 +-
gdb/target.c | 15 +-
.../gdb.threads/continue-some-running.c | 76 +
.../gdb.threads/continue-some-running.exp | 52 +
.../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 +
.../sw-watchpoint-step-over-bp-with-threads.c | 64 +
...w-watchpoint-step-over-bp-with-threads.exp | 91 +
gdb/testsuite/lib/gdb.exp | 8 +-
gdb/thread-iter.h | 2 +-
gdb/thread.c | 299 ++-
gdb/top.c | 2 +-
gdb/windows-nat.c | 2033 +++++++++++++----
gdbserver/win32-low.cc | 450 ++--
gdbserver/win32-low.h | 19 +-
48 files changed, 3369 insertions(+), 1410 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.c
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.exp
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
create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
base-commit: e1ec485cfa29f9c45370b9d0c12480ebb0a0be06
prerequisite-patch-id: 474281ed1de7dcad710a9f1415fe4866fe3e36cb
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:56 ` Andrew Burgess
2025-05-19 13:22 ` [PATCH v2 02/47] Add test for continuing with some threads running Pedro Alves
` (46 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
For some reason, when testing GDB on Cygwin, I get:
child process exited abnormally
while executing
"exec sh -c "exec > /dev/null 2>&1 && (kill -2 -$spid || kill -2 $spid)""
(procedure "close_wait_program" line 20)
invoked from within
"close_wait_program $shell_id $pid"
(procedure "standard_close" line 23)
invoked from within
"standard_close "Windows-ROCm""
("eval" body line 1)
invoked from within
"eval ${try}_${proc} \"$dest\" $args"
(procedure "call_remote" line 42)
invoked from within
"call_remote "" close $host"
(procedure "remote_close" line 3)
invoked from within
"remote_close host"
(procedure "log_and_exit" line 30)
invoked from within
"log_and_exit"
When that happens from within clean_restart, clean_restart doesn't
clear the gdb_spawn_id variable, and then when clean_restart starts up
a new GDB, that sees that gdb_spawn_id is already set, so it doesn't
actually spawn a new GDB, and so clean_restart happens to reuse the
same GDB (!). Many tests happen to actually work OK with this, but
some don't, and the failure modes can be head-scratching.
Of course, the failure to close GDB should be fixed, but when it
happens, I think it's good to not end up with the current weird state.
Connecting the "child process exit abnormally" errors at the end of a
testcase run with weird FAILs in other testcases took me a while (as
in, weeks!), it wasn't obvious to me immediately.
Thus, this patch makes default_gdb_exit more resilient to failed
closes, so that gdb_spawn_id is unset even is closing GDB fails, and
we move on to start a new GDB.
Change-Id: I9ec95aa61872a40095775534743525e0ad2097d2
---
gdb/testsuite/lib/gdb.exp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 49ef0d54654..c51cea86a9d 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -2315,7 +2315,9 @@ proc default_gdb_exit {} {
}
if ![is_remote host] {
- remote_close host
+ if {[catch { remote_close host } message]} {
+ warning "closing gdb failed with: $message"
+ }
}
unset gdb_spawn_id
unset ::gdb_tty_name
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 02/47] Add test for continuing with some threads running
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
2025-05-19 13:22 ` [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-21 19:36 ` Kevin Buettner
2025-05-19 13:22 ` [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call Pedro Alves
` (45 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
This testcase would have helped catch some issues I ran into while
working on the Windows non-stop support.
It tests continuing all threads in all-stop mode when at least one
thread is already running.
Change-Id: Ie8cd5c67502aed3c3b159d5eb5eeedee2f84eeef
---
.../gdb.threads/continue-some-running.c | 76 +++++++++++++++++++
.../gdb.threads/continue-some-running.exp | 52 +++++++++++++
2 files changed, 128 insertions(+)
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.c
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.exp
diff --git a/gdb/testsuite/gdb.threads/continue-some-running.c b/gdb/testsuite/gdb.threads/continue-some-running.c
new file mode 100644
index 00000000000..9e0d73e08a0
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-some-running.c
@@ -0,0 +1,76 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025 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>
+
+volatile int wait_for_gdb = 1;
+
+#define NUM_THREADS 3
+
+static pthread_barrier_t threads_started_barrier;
+
+static pthread_barrier_t may_exit_barrier;
+
+static void *
+thread_func (void *arg)
+{
+ /* Wait until all threads have started. */
+ pthread_barrier_wait (&threads_started_barrier);
+
+ /* Wait until the main thread lets us exit. */
+ pthread_barrier_wait (&may_exit_barrier);
+
+ return NULL;
+}
+
+static void
+threads_started (void)
+{
+}
+
+int
+main (void)
+{
+ pthread_t thread[NUM_THREADS];
+ int i;
+
+ alarm (30);
+
+ pthread_barrier_init (&threads_started_barrier, NULL, NUM_THREADS + 1);
+ pthread_barrier_init (&may_exit_barrier, NULL, NUM_THREADS + 1);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ {
+ int ret;
+
+ ret = pthread_create (&thread[i], NULL, thread_func, NULL);
+ assert (ret == 0);
+ }
+
+ pthread_barrier_wait (&threads_started_barrier);
+
+ threads_started ();
+
+ pthread_barrier_wait (&may_exit_barrier);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ pthread_join (thread[i], NULL);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/continue-some-running.exp b/gdb/testsuite/gdb.threads/continue-some-running.exp
new file mode 100644
index 00000000000..144aad1fd2e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-some-running.exp
@@ -0,0 +1,52 @@
+# Copyright 2025 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 continuing in all-stop mode when one thread is already running.
+
+standard_testfile .c
+
+if { [build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] \
+ == -1 } {
+ return
+}
+
+proc test {} {
+ clean_restart $::binfile
+
+ if ![runto threads_started] {
+ return
+ }
+
+ delete_breakpoints
+
+ # Set a non-main thread running, while everything else is left
+ # stopped.
+ gdb_test_no_output "set scheduler-locking on"
+
+ gdb_test "thread 2" ".*" "switch to secondary thread"
+
+ gdb_test -no-prompt-anchor "continue &" "Continuing\\."
+
+ # Now resume all threads, while there is already one thread
+ # running.
+ gdb_test "thread 1" ".*" "switch to main thread"
+
+ gdb_test_no_output "set scheduler-locking off"
+
+ gdb_continue_to_end "" continue 1
+}
+
+test
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
2025-05-19 13:22 ` [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes Pedro Alves
2025-05-19 13:22 ` [PATCH v2 02/47] Add test for continuing with some threads running Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-21 19:44 ` Kevin Buettner
2025-05-19 13:22 ` [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue Pedro Alves
` (44 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
There's one unnecessary check for currently_stepping in
handle_signal_stop that can be removed. It is unnecessary because
currently_stepping is only ever called if
ecs->event_thread->control.trap_expected is true, and then if it is
true, then currently_stepping always returns true too.
Change-Id: I7b07bc62e8570333d2e4856d2e55ae6e58f8260c
---
gdb/infrun.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 0b872879961..119bd151034 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -7069,8 +7069,7 @@ handle_signal_stop (struct execution_control_state *ecs)
if (ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
&& ecs->event_thread->control.trap_expected
- && gdbarch_single_step_through_delay_p (gdbarch)
- && currently_stepping (ecs->event_thread))
+ && gdbarch_single_step_through_delay_p (gdbarch))
{
/* We're trying to step off a breakpoint. Turns out that we're
also on an instruction that needs to be stepped multiple
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (2 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2026-04-02 13:33 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state Pedro Alves
` (43 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
The gdb.base/watchpoint.exp on Windows with non-stop support added
(later in the series) exposed an issue with the currently_stepping
logic when tested with software watchpoints.
The issue only happens when:
- You have multiple threads. gdb.base/watchpoint.exp exposed it on
Windows because there the OS always spawns a few extra threads.
- Displaced stepping is not available. The Windows non-stop work
does not implement displaced stepping yet. That is left as an
optimization for later.
- The target backend is working in non-stop mode.
I've written a new test that exposes the issue on GNU/Linux as well
(on hardware single-step targets, like x86-64). There, we see:
continue
Continuing.
../../src/gdb/infrun.c:2918: internal-error: resume_1: Assertion `!(thread_has_single_step_breakpoints_set (tp) && step)' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
FAIL: gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp: target-non-stop=on: displaced-stepping=off: continue until exit (GDB internal error)
Currently, software watchpoints are implemented by forcing
single-stepping. That is done by currently_stepping returning true
when we have a software watchpoint. proceed calls resume, which calls
resume_1, which then ends up always requesting a single-step resume,
even if the higher layers wanted a continue.
Now, if you set a software watchpoint, and then continue the program,
and there's a breakpoint at the current PC, GDB needs to step over
that breakpoint first. If displaced stepping is not available, then
GDB temporarily pauses all threads, removes the breakpoint,
single-steps the thread that needs to move past the breakpoint, and
then finally, reinserts the breakpoint, and restarts all threads
again. That last restarting step happens in the restart_threads
infrun function.
restart_threads iterates over all threads trying to restart them one
by one. There, we have:
if (currently_stepping (tp))
{
infrun_debug_printf ("restart threads: [%s] was stepping",
tp->ptid.to_string ().c_str ());
but, what if TP is actually a new thread that hasn't yet ever set
stepping? currently_stepping still returns true, due to the software
watchpoint, and we end up in keep_going_stepped_thread, here:
if (tp->stop_pc () != tp->prev_pc)
{
ptid_t resume_ptid;
infrun_debug_printf ("expected thread advanced also (%s -> %s)",
paddress (current_inferior ()->arch (), tp->prev_pc),
paddress (current_inferior ()->arch (),
tp->stop_pc ()));
... because prev_pc was stale at that point (we had no reason to
update it earlier). E.g. on Windows we see something like:
[infrun] restart_threads: start: event_thread=1867996.1867996.0, inf=-1
[infrun] restart_threads: restart threads: [1867996.1867996.0] is event thread
[infrun] restart_threads: restart threads: [1867996.1868003.0] was stepping
[infrun] keep_going_stepped_thread: resuming previously stepped thread
[infrun] keep_going_stepped_thread: expected thread advanced also (0 -> 0x7ffff7ce57f8)
[infrun] clear_step_over_info: clearing step over info
[infrun] do_target_resume: resume_ptid=1867996.1868003.0, step=0, sig=GDB_SIGNAL_0
On GNU/Linux, we may see:
[infrun] keep_going_stepped_thread: expected thread advanced also (0x7ffff7d2683d -> 0x7ffff7ce57f8)
there prev_pc might have been updated on an earlier proceed call,
which makes the issue harder to see, but it is stale too here.
That means we insert a single-step breakpoint at the current PC, and
continue the thread, with target_resume directly, asking for a normal
continue.
Eventually, something causes a user-visible stop. For example, the
software watchpoint triggers. That makes GDB stop all threads.
Now, if the user re-resumes the program, say, with "continue", we fail
this assertion in resume_1 coming from proceed:
/* If STEP is set, it's a request to use hardware stepping
facilities. But in that case, we should never
use singlestep breakpoint. */
gdb_assert (!(thread_has_single_step_breakpoints_set (tp) && step));
"step" is true because currently_stepping returns true since we have a
software watchpoint. And the thread has a single-step breakpoint
installed from earlier, because of that code mentioned above, in
keep_going_stepped_thread reached from restart_threads.
This patch fixes the root cause -- the currently_stepping call in
restart_threads returned true for a thread that has never set stepping
in the first place. This is because currently_stepping really serves
two purposes currently:
#1 - for a thread that we are about to resume, should we set it
stepping?
#2 - for a thread that just stopped, was it stepping before?
The fix is thus to decouple those two aspects:
- for #1, we simply rename currently_stepping to should_step.
- for #2, we record whether the thread was stepping before in a new
currently_stepping flag in thread_info.
As mentioned, there's a new testcase included. I tested this on
x86-64 GNU/Linux, native and gdbserver, and on Windows x64 with the
non-stop series. The assertion triggers on all of those with the fix,
and is fixed by this patch on all of those, too.
Change-Id: I7b07bc62e8570333d2e4856d2e55ae6e58f8260c
---
gdb/gdbthread.h | 4 +
gdb/infrun.c | 28 +++---
.../sw-watchpoint-step-over-bp-with-threads.c | 64 +++++++++++++
...w-watchpoint-step-over-bp-with-threads.exp | 91 +++++++++++++++++++
4 files changed, 173 insertions(+), 14 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index c561e9a7b64..a8fd967c702 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -152,6 +152,10 @@ struct thread_control_state
the finished single step. */
int trap_expected = 0;
+ /* True if the thread TP is in the middle of (software or hardware)
+ single-stepping. */
+ bool currently_stepping = false;
+
/* Nonzero if the thread is being proceeded for a "finish" command
or a similar situation when return value should be printed. */
int proceed_to_finish = 0;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 119bd151034..9f625a7bd44 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -87,7 +87,7 @@ static void sig_print_header (void);
static void follow_inferior_reset_breakpoints (void);
-static bool currently_stepping (struct thread_info *tp);
+static bool should_step (thread_info *tp);
static void insert_hp_step_resume_breakpoint_at_frame (const frame_info_ptr &);
@@ -2656,7 +2656,7 @@ resume_1 (enum gdb_signal sig)
"status %s (currently_stepping=%d).",
tp->ptid.to_string ().c_str (),
tp->pending_waitstatus ().to_string ().c_str (),
- currently_stepping (tp));
+ tp->control.currently_stepping);
tp->inf->process_target ()->threads_executing = true;
tp->set_resumed (true);
@@ -2685,7 +2685,7 @@ resume_1 (enum gdb_signal sig)
tp->stepped_breakpoint = 0;
/* Depends on stepped_breakpoint. */
- step = currently_stepping (tp);
+ step = tp->control.currently_stepping = should_step (tp);
if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
{
@@ -3060,7 +3060,7 @@ clear_proceed_status_thread (struct thread_info *tp)
("thread %s has pending wait status %s (currently_stepping=%d).",
tp->ptid.to_string ().c_str (),
tp->pending_waitstatus ().to_string ().c_str (),
- currently_stepping (tp));
+ tp->control.currently_stepping);
}
}
@@ -5041,7 +5041,7 @@ adjust_pc_after_break (struct thread_info *thread,
we also need to back up to the breakpoint address. */
if (thread_has_single_step_breakpoints_set (thread)
- || !currently_stepping (thread)
+ || !thread->control.currently_stepping
|| (thread->stepped_breakpoint
&& thread->prev_pc == breakpoint_pc))
regcache_write_pc (regcache, breakpoint_pc);
@@ -5368,7 +5368,7 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus &ws)
&& software_breakpoint_inserted_here_p (aspace, pc))
tp->set_stop_reason (TARGET_STOPPED_BY_SW_BREAKPOINT);
else if (!thread_has_single_step_breakpoints_set (tp)
- && currently_stepping (tp))
+ && tp->control.currently_stepping)
tp->set_stop_reason (TARGET_STOPPED_BY_SINGLE_STEP);
}
}
@@ -5563,7 +5563,7 @@ handle_one (const wait_one_event &event)
paddress (current_inferior ()->arch (),
t->stop_pc ()),
t->ptid.to_string ().c_str (),
- currently_stepping (t));
+ t->control.currently_stepping);
}
}
@@ -6668,7 +6668,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
tp->ptid.to_string ().c_str ());
}
- if (currently_stepping (tp))
+ if (tp->control.currently_stepping)
{
infrun_debug_printf ("restart threads: [%s] was stepping",
tp->ptid.to_string ().c_str ());
@@ -6795,7 +6795,7 @@ finish_step_over (struct execution_control_state *ecs)
paddress (current_inferior ()->arch (),
tp->stop_pc ()),
tp->ptid.to_string ().c_str (),
- currently_stepping (tp));
+ tp->control.currently_stepping);
/* This in-line step-over finished; clear this so we won't
start a new one. This is what handle_signal_stop would
@@ -7204,7 +7204,7 @@ handle_signal_stop (struct execution_control_state *ecs)
/* If not, perhaps stepping/nexting can. */
if (random_signal)
random_signal = !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
- && currently_stepping (ecs->event_thread));
+ && ecs->event_thread->control.currently_stepping);
/* Perhaps the thread hit a single-step breakpoint of _another_
thread. Single-step breakpoints are transparent to the
@@ -8632,12 +8632,12 @@ keep_going_stepped_thread (struct thread_info *tp)
return true;
}
-/* Is thread TP in the middle of (software or hardware)
- single-stepping? (Note the result of this function must never be
- passed directly as target_resume's STEP parameter.) */
+/* Should thread TP be stepped (software or hardware)? (Note the
+ result of this function must never be passed directly as
+ target_resume's STEP parameter.) */
static bool
-currently_stepping (struct thread_info *tp)
+should_step (thread_info *tp)
{
return ((tp->control.step_range_end
&& tp->control.step_resume_breakpoint == nullptr)
diff --git a/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
new file mode 100644
index 00000000000..7f014036c9b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
@@ -0,0 +1,64 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025 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 *
+thread_func (void *arg)
+{
+ pthread_barrier_wait (&threads_started_barrier);
+
+ while (1)
+ sleep (1);
+
+ return NULL;
+}
+
+static void
+dummy ()
+{
+}
+
+static unsigned watched_global = 0;
+
+int
+main (void)
+{
+ pthread_t thread;
+ int ret;
+
+ alarm (30);
+
+ pthread_barrier_init (&threads_started_barrier, NULL, 2);
+
+ ret = pthread_create (&thread, NULL, thread_func, NULL);
+ assert (ret == 0);
+
+ /* Make sure all threads are scheduled before hitting the
+ breakpoint. */
+ pthread_barrier_wait (&threads_started_barrier);
+
+ ++watched_global; /* break here start */
+
+ dummy (); /* just so there's extra code here */
+
+ return 0; /* break here end */
+}
diff --git a/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
new file mode 100644
index 00000000000..b45db57e80f
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
@@ -0,0 +1,91 @@
+# Copyright 2025 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 continuing with a software watchpoint installed, when there are
+# multiple threads, and previously we stepped over a breakpoint.
+#
+# This is a regression test for a GDB bug where stepping over a
+# breakpoint in-line made GDB insert a software single-step breakpoint
+# in some threads by mistake, which later would cause an assertion to
+# fail.
+#
+# The issue only triggered when:
+#
+# - The program has multiple threads.
+# - The target backend is working in non-stop mode.
+# - Displaced stepping is not available.
+# - The target supports hardware single-step.
+#
+# However, we exercise more combinations for completeness.
+
+standard_testfile .c
+
+if { [build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] \
+ == -1 } {
+ return
+}
+
+proc test {target-non-stop displaced-stepping} {
+
+ save_vars ::GDBFLAGS {
+ append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+ append ::GDBFLAGS " -ex \"set displaced ${displaced-stepping}\""
+ clean_restart $::binfile
+ }
+
+ if ![runto_main] {
+ return
+ }
+
+ # Run to a breakpoint, and leave it installed, so that GDB needs
+ # to step over it before continuing.
+ gdb_breakpoint [gdb_get_line_number "break here start"]
+ gdb_continue_to_breakpoint "started"
+
+ # GDB should know about at least two threads by now.
+ gdb_test "p \$_inferior_thread_count >= 2" " = 1"
+
+ # Set a software watchpoint. This makes GDB single-step all
+ # instructions when we next continue.
+ gdb_test_no_output "set can-use-hw-watchpoints 0"
+ gdb_test "watch watched_global" "Watchpoint $::decimal: watched_global"
+
+ # Continue with the software watchpoint in place. In the original
+ # bug, with displaced stepping disabled, this would make GDB
+ # incorrectly install a software single-step breakpoint on threads
+ # other than the main one.
+ gdb_test_multiple "cont" "continue to watchpoint" {
+ -re -wrap "Continuing.*Watchpoint.*watched_global.*Old value = 0.*New value = 1.*" {
+ pass $gdb_test_name
+ }
+ }
+
+ # The final continue, with the software watchpoint set, so that
+ # GDB single-steps all threads (if the target is non-stop). With
+ # the buggy GDB, the non-main thread had a software single-step
+ # breakpoint set, and on hardware single-step targets, GDB would
+ # fail an assertion that checks that we never ask the target to
+ # hardware single-step a thread when we have a software
+ # single-step breakpoint set for that thread.
+ gdb_breakpoint [gdb_get_line_number "break here end"]
+ gdb_continue_to_breakpoint "end"
+}
+
+foreach_with_prefix target-non-stop {auto on off} {
+ foreach_with_prefix displaced-stepping {auto on off} {
+ test ${target-non-stop} ${displaced-stepping}
+ }
+}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (3 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2026-04-06 18:01 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 06/47] Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff Pedro Alves
` (42 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
While working on Windows non-stop support, I ran into a
very-hard-to-track-down bug.
The problem turned out to be that
infrun.c:proceed_resume_thread_checked resumed an already-executing
thread because the thread was marked as "executing=true,
resumed=false", and that function only skips resuming threads that are
marked resumed=true. The consequence was that GDB corrupted the
registers of the Windows DLL loader threads, eventually leading to a
GDB+inferior deadlock.
Originally, the "resumed" flag was only ever set when infrun decided
is was ready to process a thread's pending wait status. infrun has
since evolved to set the resumed flag when we set a thread's executing
flag too. We are not always consistent throughout in guaranteeing
that a thread is marked resumed=true whenever it is marked
executing=true, though. For instance, no target code that supports
non-stop mode (linux-nat, remote, and windows-nat with this series) is
making sure that new threads are marked resumed=true when they are
added to the thread list. They are only marked as {state=running,
executing=true}, the "resumed" flag is not touched.
Making proceed_resume_thread_checked check thr->executing() in
addition to thr->resumed(), feels like papering over a combination of
states that shouldn't happen nowadays.
OTOH, having to have the target backends mark new threads as
resumed=true just feels like too many different states (three) to set:
add_thread (...);
set_running (...);
set_executing (...);
set_resumed (...);
Yuck. I think we can do better.
We really have too many "state tracking" flags in a thread.
Basically:
- whether a thread is "running/stopped/exited" (from the user's
perspective). This is the thread_info::state field.
- whether a thread is "executing" (infrun asked the target to set the
thread executing). This is thread_info::executing().
- whether a thread is "resumed" (infrun wants the thread to be
resumed, but maybe can't yet because the thread has a pending wait
status). This is thread_info::resumed()
"running", "executing", and "resumed" are almost synonyms, so this can
be highly confusing English-wise too.
For "running" vs "executing", in comments, we tipically need to
explain that "running/stopped/exited" is for the user/frontend
perspective, while "executing true/false" is for gdb's internal run
control.
(Also, "executing or not" can also mean something else in GDB's
codebase -- "target has execution" does not mean that threads are
actually running right now -- it's a test for whether we have a live
process vs a core dump!)
One simplification we can do that avoids this running vs executing
ambiguity is to replace the "executing" field with an "internal_state"
field, similar to the thread_info::state field, and make that new
internal_state field reuse the same enum thread_state type that is
used by thread_info::state. Like:
struct thread_info
{
...
/* Frontend/public/external/user view of the thread state. */
enum thread_state m_state = THREAD_STOPPED;
/* The thread's internal state. When the thread is stopped
internally while handling an internal event, like a software
single-step breakpoint, the internal state will be
THREAD_STOPPED, but the external state will still be
THREAD_RUNNING. */
enum thread_state m_internal_state = THREAD_STOPPED;
};
(Assume we'd add state() and internal_state() getters.)
With that, every check for thr->executing() is replaced with a
'thr->internal_state() == THREAD_RUNNING' check, and the code is
clearer by design. There is no confusion between "running" vs
"executing" any more, because they now mean the exact same thing.
Instead, we say e.g., 'thread has (user) state "running", and internal
state "stopped"'. Or simpler, 'thread is running (from the user's
perspective), but internally stopped'. That is after all what we
would way in comments today already.
That still leaves the 'resumed' flag, though. That's the least
obvious one. Turns out we can get rid of it, and make it a new state
tracked by thread_info::internal_state. That is, we make
internal_state have its own enumeration type (decoupled from
thread_info::state's type), and convert the resumed true/false flag to
a new enumerator of this new enumeration. Like so:
enum thread_int_state
{
THREAD_INT_STOPPED,
THREAD_INT_RUNNING,
+ THREAD_INT_RESUMED_PENDING_STATUS,
THREAD_INT_EXITED,
};
That is what this patch does. So in summary, we go from:
thread_info::state {THREAD_STOPPED, THREAD_RUNNING, THREAD_EXITED}
thread_info::executing {false, true}
thread_info::resumed {false, true}
to:
thread_info::state {THREAD_STOPPED, THREAD_RUNNING, THREAD_EXITED}
thread_info::internal_state {THREAD_INT_STOPPED, THREAD_INT_RUNNING,
THREAD_INT_RESUMED_PENDING_STATUS,
THREAD_INT_EXITED}
The patch adds getters/setters for both (user) state and
internal_state, and adds assertions around state transitions, ensuring
that internal_state doesn't get out of sync with
thread::have_pending_wait_status(). It also adds an assertion to
clear_proceed_status_thread, making sure that we don't try to proceed
a thread that is already running. Turns out that catches
attach_command calling init_wait_for_inferior too late, after
attaching has already created already-running threads.
The code that adds/removes threads from the proc_target's
resumed_with_pending_wait_status list is all centralized within
thread_info::set_internal_state, when we switch to/from the
resumed-pending-status state. With the assertions in place, it should
be impossible to end up with a THREAD_INT_RUNNING thread with a
pending status.
The thread.c:set_running, thread.c:set_executing, thread.c:set_resumed
global functions are all gone, replaced with new thread.c:set_state
and thread.c:set_internal_state functions.
Tested on x86_64-linux-gnu, native and gdbserver.
Change-Id: I4f5097d68f4694d44e1ae23fea3e9bce45fb078c
---
gdb/aarch64-tdep.c | 2 +-
gdb/amd-dbgapi-target.c | 8 +-
gdb/breakpoint.c | 9 +-
gdb/bsd-uthread.c | 4 +-
gdb/fork-child.c | 4 +-
gdb/frame.c | 4 +-
gdb/gcore.c | 4 +-
gdb/gdbthread.h | 146 +++++++++--------
gdb/i386-tdep.c | 4 +-
gdb/inf-ptrace.c | 2 +-
gdb/infcall.c | 8 +-
gdb/infcmd.c | 39 ++---
gdb/inferior.h | 4 +-
gdb/inflow.c | 2 +-
gdb/infrun.c | 152 +++++++++---------
gdb/infrun.h | 7 +-
gdb/linux-fork.c | 9 +-
gdb/linux-nat.c | 7 +-
gdb/linux-thread-db.c | 4 +-
gdb/mi/mi-cmd-var.c | 4 +-
gdb/mi/mi-interp.c | 2 +-
gdb/mi/mi-main.c | 8 +-
gdb/process-stratum-target.c | 6 +-
gdb/python/py-infthread.c | 6 +-
gdb/record-btrace.c | 20 +--
gdb/record-full.c | 6 +-
gdb/regcache.c | 2 +-
gdb/remote.c | 76 +++++----
gdb/sol-thread.c | 4 +-
gdb/target.c | 15 +-
gdb/thread-iter.h | 2 +-
gdb/thread.c | 299 ++++++++++++++++++++---------------
gdb/top.c | 2 +-
33 files changed, 474 insertions(+), 397 deletions(-)
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 8d54e59f332..8e1908452c5 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -4265,7 +4265,7 @@ aarch64_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
/* If the thread is running, we will not be able to fetch the mask
registers. */
- if (thread != nullptr && thread->state != THREAD_RUNNING)
+ if (thread != nullptr && thread->state () != THREAD_RUNNING)
{
/* Otherwise, fetch the register cache and the masks. */
struct regcache *regs
diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
index 8e067b66bd8..e68b953888b 100644
--- a/gdb/amd-dbgapi-target.c
+++ b/gdb/amd-dbgapi-target.c
@@ -907,7 +907,7 @@ amd_dbgapi_target::stop (ptid_t ptid)
/* Use the threads_safe iterator since stop_one_thread may delete the
thread if it has exited. */
for (auto *thread : inf->threads_safe ())
- if (thread->state != THREAD_EXITED && thread->ptid.matches (ptid)
+ if (thread->state () != THREAD_EXITED && thread->ptid.matches (ptid)
&& ptid_is_gpu (thread->ptid))
stop_one_thread (thread);
}
@@ -1130,8 +1130,8 @@ add_gpu_thread (inferior *inf, ptid_t wave_ptid)
/* Create new GPU threads silently to avoid spamming the terminal
with thousands of "[New Thread ...]" messages. */
thread_info *thread = add_thread_silent (proc_target, wave_ptid);
- set_running (proc_target, wave_ptid, true);
- set_executing (proc_target, wave_ptid, true);
+ set_state (proc_target, wave_ptid, THREAD_RUNNING);
+ set_internal_state (proc_target, wave_ptid, THREAD_INT_RUNNING);
return thread;
}
@@ -1871,7 +1871,7 @@ amd_dbgapi_target::update_thread_list ()
which does not have a corresponding wave_id represents a wave which
is gone at this point and should be deleted. */
for (thread_info *tp : inf->threads_safe ())
- if (ptid_is_gpu (tp->ptid) && tp->state != THREAD_EXITED)
+ if (ptid_is_gpu (tp->ptid) && tp->state () != THREAD_EXITED)
{
auto it = threads.find (tp->ptid.tid ());
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index f44ac457b22..847bf059364 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -617,9 +617,9 @@ breakpoints_should_be_inserted_now (void)
return 1;
/* Don't remove breakpoints yet if, even though all threads are
- stopped, we still have events to process. */
+ stopped, some still have pending events to process. */
for (thread_info *tp : all_non_exited_threads ())
- if (tp->resumed () && tp->has_pending_waitstatus ())
+ if (tp->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
return 1;
}
return 0;
@@ -2041,7 +2041,7 @@ watchpoint_in_thread_scope (struct watchpoint *b)
return (b->pspace == current_program_space
&& (b->watchpoint_thread == null_ptid
|| (inferior_ptid == b->watchpoint_thread
- && !inferior_thread ()->executing ())));
+ && inferior_thread ()->internal_state () != THREAD_INT_RUNNING)));
}
/* Set watchpoint B to disp_del_at_next_stop, even including its possible
@@ -4982,7 +4982,8 @@ get_bpstat_thread ()
return NULL;
thread_info *tp = inferior_thread ();
- if (tp->state == THREAD_EXITED || tp->executing ())
+ if (tp->internal_state () == THREAD_INT_EXITED
+ || tp->internal_state () == THREAD_INT_RUNNING)
return NULL;
return tp;
}
diff --git a/gdb/bsd-uthread.c b/gdb/bsd-uthread.c
index 341aea98fb7..3b8ee0fa04a 100644
--- a/gdb/bsd-uthread.c
+++ b/gdb/bsd-uthread.c
@@ -417,7 +417,7 @@ bsd_uthread_target::wait (ptid_t ptid, struct target_waitstatus *status,
/* Don't let the core see a ptid without a corresponding thread. */
thread_info *thread = beneath->find_thread (ptid);
- if (thread == NULL || thread->state == THREAD_EXITED)
+ if (thread == NULL || thread->state () == THREAD_EXITED)
add_thread (beneath, ptid);
return ptid;
@@ -468,7 +468,7 @@ bsd_uthread_target::update_thread_list ()
process_stratum_target *proc_target
= as_process_stratum_target (this->beneath ());
thread_info *thread = proc_target->find_thread (ptid);
- if (thread == nullptr || thread->state == THREAD_EXITED)
+ if (thread == nullptr || thread->state () == THREAD_EXITED)
{
/* If INFERIOR_PTID doesn't have a tid member yet, then ptid
is still the initial thread of the process. Notify GDB
diff --git a/gdb/fork-child.c b/gdb/fork-child.c
index 8abfbda2b78..0913435fb80 100644
--- a/gdb/fork-child.c
+++ b/gdb/fork-child.c
@@ -130,8 +130,8 @@ gdb_startup_inferior (pid_t pid, int num_traps)
ptid_t ptid = startup_inferior (proc_target, pid, num_traps, NULL, NULL);
- /* Mark all threads non-executing. */
- set_executing (proc_target, ptid, false);
+ /* Mark all threads internally stopped. */
+ set_internal_state (proc_target, ptid, THREAD_INT_STOPPED);
return ptid;
}
diff --git a/gdb/frame.c b/gdb/frame.c
index fe5336f2401..59e72ec37d8 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1873,11 +1873,11 @@ has_stack_frames ()
thread_info *tp = inferior_thread ();
/* Don't try to read from a dead thread. */
- if (tp->state == THREAD_EXITED)
+ if (tp->internal_state () == THREAD_INT_EXITED)
return false;
/* ... or from a spinning thread. */
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
return false;
}
diff --git a/gdb/gcore.c b/gdb/gcore.c
index fa15d06796b..b9ed7991efb 100644
--- a/gdb/gcore.c
+++ b/gdb/gcore.c
@@ -846,7 +846,7 @@ thread_info *
gcore_find_signalled_thread ()
{
thread_info *curr_thr = inferior_thread ();
- if (curr_thr->state != THREAD_EXITED
+ if (curr_thr->state () != THREAD_EXITED
&& curr_thr->stop_signal () != GDB_SIGNAL_0)
return curr_thr;
@@ -855,7 +855,7 @@ gcore_find_signalled_thread ()
return thr;
/* Default to the current thread, unless it has exited. */
- if (curr_thr->state != THREAD_EXITED)
+ if (curr_thr->state () != THREAD_EXITED)
return curr_thr;
return nullptr;
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index a8fd967c702..344c808e9cb 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -51,8 +51,8 @@ extern bool debug_threads;
#define threads_debug_printf(fmt, ...) \
debug_prefixed_printf_cond (debug_threads, "threads", fmt, ##__VA_ARGS__)
-/* Frontend view of the thread state. Possible extensions: stepping,
- finishing, until(ling),...
+/* User/frontend view of the thread state. Possible extensions:
+ stepping, finishing, until(ling),...
NOTE: Since the thread state is not a boolean, most times, you do
not want to check it with negation. If you really want to check if
@@ -81,6 +81,34 @@ enum thread_state
THREAD_EXITED,
};
+/* Internal view of the thread's running state. When a thread is
+ running from the user's perspective, it will still occasionally
+ stop, due to breakpoint hits, single-stepping, etc. Often those
+ stops are not meant to be user-visible. In such situations, the
+ user state will be THREAD_RUNNING, while the internal state
+ transitions between stopped, running, etc. */
+
+enum thread_int_state
+{
+ /* The thread is stopped. If the thread has a pending wait status,
+ we should not process it until we try to let the thread run, in
+ which case we switch the thread to
+ THREAD_INT_RESUMED_PENDING_STATUS state. */
+ THREAD_INT_STOPPED,
+
+ /* The thread is running. */
+ THREAD_INT_RUNNING,
+
+ /* infrun wants the thread to be resumed, but didn't set it running
+ yet, because the thread has a pending wait status to process. We
+ shouldn't let the thread really run until that wait status has
+ been processed. */
+ THREAD_INT_RESUMED_PENDING_STATUS,
+
+ /* The thread is listed, but known to have exited. */
+ THREAD_INT_EXITED,
+};
+
/* STEP_OVER_ALL means step over all subroutine calls.
STEP_OVER_UNDEBUGGABLE means step over calls to undebuggable functions.
STEP_OVER_NONE means don't step over any subroutine calls. */
@@ -218,10 +246,10 @@ struct thread_suspend_state
last stopped, a pending breakpoint waitstatus is discarded.
- If the thread is running, then this field has its value removed by
- calling stop_pc.reset() (see thread_info::set_executing()).
+ calling stop_pc.reset() (see thread_info::set_internal_state()).
Attempting to read a std::optional with no value is undefined
behavior and will trigger an assertion error when _GLIBCXX_DEBUG is
- defined, which should make error easier to track down. */
+ defined, which should make errors easier to track down. */
std::optional<CORE_ADDR> stop_pc;
};
@@ -263,8 +291,23 @@ class thread_info : public intrusive_list_node<thread_info>,
bool deletable () const;
- /* Mark this thread as running and notify observers. */
- void set_running (bool running);
+ /* Get the thread's (user-visible) state. */
+ thread_state state () const { return m_state; }
+
+ /* Set this thread's (user-visible) state. If the thread is set
+ running, notify observers. */
+ void set_state (thread_state state) { set_state (state, false); }
+
+ /* Get the thread's internal state. */
+ thread_int_state internal_state () const { return m_internal_state; }
+
+ /* Set the thread's internal state from STATE. If the state
+ switches to THREAD_INT_RUNNING, also clears the thread's stop_pc.
+ The thread may also be added to (when switching to
+ THREAD_INT_RESUMED_PENDING_STATUS), or removed from (when
+ switching from THREAD_INT_RESUMED_PENDING_STATUS), the list of
+ threads with a pending wait status. */
+ void set_internal_state (thread_int_state state);
ptid_t ptid; /* "Actual process id";
In fact, this may be overloaded with
@@ -326,28 +369,6 @@ class thread_info : public intrusive_list_node<thread_info>,
m_name = std::move (name);
}
- bool executing () const
- { return m_executing; }
-
- /* Set the thread's 'm_executing' field from EXECUTING, and if EXECUTING
- is true also clears the thread's stop_pc. */
- void set_executing (bool executing);
-
- bool resumed () const
- { return m_resumed; }
-
- /* Set the thread's 'm_resumed' field from RESUMED. The thread may also
- be added to (when RESUMED is true), or removed from (when RESUMED is
- false), the list of threads with a pending wait status. */
- void set_resumed (bool resumed);
-
- /* Frontend view of the thread state. Note that the THREAD_RUNNING/
- THREAD_STOPPED states are different from EXECUTING. When the
- thread is stopped internally while handling an internal event,
- like a software single-step breakpoint, EXECUTING will be false,
- but STATE will still be THREAD_RUNNING. */
- enum thread_state state = THREAD_STOPPED;
-
/* State of GDB control of inferior thread execution.
See `struct thread_control_state'. */
thread_control_state control;
@@ -573,20 +594,22 @@ class thread_info : public intrusive_list_node<thread_info>,
displaced_step_thread_state displaced_step_state;
private:
- /* True if this thread is resumed from infrun's perspective.
- Note that a thread can be marked both as not-executing and
- resumed at the same time. This happens if we try to resume a
- thread that has a wait status pending. We shouldn't let the
- thread really run until that wait status has been processed, but
- we should not process that wait status if we didn't try to let
- the thread run. */
- bool m_resumed = false;
-
- /* True means the thread is executing. Note: this is different
- from saying that there is an active target and we are stopped at
- a breakpoint, for instance. This is a real indicator whether the
- thread is off and running. */
- bool m_executing = false;
+ /* Set this thread's (user-visible) state. If the thread is set
+ running, notify observers, unless SUPPRESS_NOTIFICATION is true.
+ Returns the thread's previous state. */
+ thread_state set_state (thread_state state, bool suppress_notification);
+ friend void set_state (process_stratum_target *targ,
+ ptid_t ptid,
+ thread_state state);
+ friend void finish_thread_state (process_stratum_target *targ,
+ ptid_t ptid);
+
+ /* User view of the thread's stopped/running/exited state. */
+ enum thread_state m_state = THREAD_STOPPED;
+
+ /* The thread's internal state. See definition of
+ thread_int_state. */
+ enum thread_int_state m_internal_state = THREAD_INT_STOPPED;
/* State of inferior thread to restore after GDB is done with an inferior
call. See `struct thread_suspend_state'. */
@@ -815,17 +838,15 @@ extern void switch_to_no_thread ();
/* Switch from one thread to another. Does not read registers. */
extern void switch_to_thread_no_regs (struct thread_info *thread);
-/* Marks or clears thread(s) PTID of TARG as resumed. If PTID is
- MINUS_ONE_PTID, applies to all threads of TARG. If
- ptid_is_pid(PTID) is true, applies to all threads of the process
- pointed at by {TARG,PTID}. */
-extern void set_resumed (process_stratum_target *targ,
- ptid_t ptid, bool resumed);
+/* Marks thread PTID of TARG with user state STATE. If PTID is
+ minus_one_ptid, marks all threads of TARG. */
+extern void set_state (process_stratum_target *targ,
+ ptid_t ptid, thread_state state);
-/* Marks thread PTID of TARG as running, or as stopped. If PTID is
+/* Marks thread PTID of TARG with internal state STATE. If PTID is
minus_one_ptid, marks all threads of TARG. */
-extern void set_running (process_stratum_target *targ,
- ptid_t ptid, bool running);
+extern void set_internal_state (process_stratum_target *targ,
+ ptid_t ptid, thread_int_state state);
/* Marks or clears thread(s) PTID of TARG as having been requested to
stop. If PTID is MINUS_ONE_PTID, applies to all threads of TARG.
@@ -835,25 +856,19 @@ extern void set_running (process_stratum_target *targ,
extern void set_stop_requested (process_stratum_target *targ,
ptid_t ptid, bool stop);
-/* Marks thread PTID of TARG as executing, or not. If PTID is
- minus_one_ptid, marks all threads of TARG.
-
- Note that this is different from the running state. See the
- description of state and executing fields of struct
- thread_info. */
-extern void set_executing (process_stratum_target *targ,
- ptid_t ptid, bool executing);
-
/* True if any (known or unknown) thread of TARG is or may be
executing. */
extern bool threads_are_executing (process_stratum_target *targ);
-/* Merge the executing property of thread PTID of TARG over to its
- thread state property (frontend running/stopped view).
+/* Propagate the internal thread state of thread PTID of TARG over to
+ its (user) thread state.
- "not executing" -> "stopped"
- "executing" -> "running"
- "exited" -> "exited"
+ user <- internal
+ ------- ------------------------
+ stopped <- stopped
+ running <- running
+ running <- continued-pending-status
+ exited <- exited
If PTID is minus_one_ptid, go over all threads of TARG.
@@ -1074,5 +1089,6 @@ extern void thread_try_catch_cmd (thread_info *thr,
/* Return a string representation of STATE. */
extern const char *thread_state_string (enum thread_state state);
+extern const char *thread_int_state_string (enum thread_int_state state);
#endif /* GDB_GDBTHREAD_H */
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index 23788b44e92..a37eb132f47 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -4811,8 +4811,8 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
/* Since we are reading pseudo registers, we need to tell GDB that it is
safe to do so, by saying we aren't _really_ running the inferior right
now. */
- SCOPE_EXIT { inferior_thread ()->set_executing (true); };
- inferior_thread () -> set_executing (false);
+ SCOPE_EXIT { inferior_thread ()->set_internal_state (THREAD_INT_RUNNING); };
+ inferior_thread ()->set_internal_state (THREAD_INT_STOPPED);
switch (opcode)
{
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index efa269cbe23..d39f2e5b015 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -175,7 +175,7 @@ inf_ptrace_target::attach (const char *args, int from_tty)
/* Don't consider the thread stopped until we've processed its
initial SIGSTOP stop. */
- set_executing (this, thr->ptid, true);
+ set_internal_state (this, thr->ptid, THREAD_INT_RUNNING);
unpusher.release ();
}
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 098072dfd2a..697bbbacc5a 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -797,7 +797,7 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
struct gdb_exception caught_error;
ptid_t call_thread_ptid = call_thread->ptid;
- int was_running = call_thread->state == THREAD_RUNNING;
+ int was_running = call_thread->state () == THREAD_RUNNING;
*timed_out_p = false;
infcall_debug_printf ("call function at %s in thread %s, was_running = %d",
@@ -928,7 +928,7 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
of error out of resume()), then we wouldn't need this. */
if (caught_error.reason < 0)
{
- if (call_thread->state != THREAD_EXITED)
+ if (call_thread->state () != THREAD_EXITED)
breakpoint_auto_delete (call_thread->control.stop_bpstat);
}
@@ -1565,7 +1565,7 @@ call_function_by_hand_dummy (struct value *function,
infcall_debug_printf ("after inferior call, exception (%d): %s",
e.reason, e.what ());
infcall_debug_printf ("after inferior call, thread state is: %s",
- thread_state_string (call_thread->state));
+ thread_state_string (call_thread->state ()));
gdb::observers::inferior_call_post.notify (call_thread_ptid, funaddr);
@@ -1576,7 +1576,7 @@ call_function_by_hand_dummy (struct value *function,
threads appear after GDB has reported a stop. */
update_thread_list ();
- if (call_thread->state != THREAD_EXITED)
+ if (call_thread->state () != THREAD_EXITED)
{
/* The FSM should still be the same. */
gdb_assert (call_thread->thread_fsm () == sm);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index e9b58ce5521..6c07e07619c 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -537,12 +537,12 @@ proceed_thread_callback (struct thread_info *thread)
into a single target `resume_all' request, because some threads
may be stopped in internal breakpoints/events, or stopped waiting
for its turn in the displaced stepping queue (that is, they are
- running && !executing). The target side has no idea about why
- the thread is stopped, so a `resume_all' command would resume too
- much. If/when GDB gains a way to tell the target `hold this
- thread stopped until I say otherwise', then we can optimize
- this. */
- if (thread->state != THREAD_STOPPED)
+ running from the user's perspective but internally stopped). The
+ target side has no idea about why the thread is stopped, so a
+ `resume_all' command would resume too much. If/when GDB gains a
+ way to tell the target `hold this thread stopped until I say
+ otherwise', then we can optimize this. */
+ if (thread->state () != THREAD_STOPPED)
return false;
if (!thread->inf->has_execution ())
@@ -558,7 +558,7 @@ static void
ensure_valid_thread (void)
{
if (inferior_ptid == null_ptid
- || inferior_thread ()->state == THREAD_EXITED)
+ || inferior_thread ()->state () == THREAD_EXITED)
error (_("Cannot execute this command without a live selected thread."));
}
@@ -587,7 +587,7 @@ error_is_running (void)
static void
ensure_not_running (void)
{
- if (inferior_thread ()->state == THREAD_RUNNING)
+ if (inferior_thread ()->state () == THREAD_RUNNING)
error_is_running ();
}
@@ -945,7 +945,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
/* Pretend that we've ran. */
resume_ptid = user_visible_resume_ptid (1);
- set_running (tp->inf->process_target (), resume_ptid, true);
+ set_state (tp->inf->process_target (), resume_ptid,
+ THREAD_RUNNING);
step_into_inline_frame (tp);
@@ -1921,12 +1922,12 @@ info_program_command (const char *args, int from_tty)
print_thread_id (tp),
target_pid_to_str (tp->ptid).c_str ());
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
{
gdb_printf (_("Selected thread has exited.\n"));
return;
}
- else if (tp->state == THREAD_RUNNING)
+ else if (tp->state () == THREAD_RUNNING)
{
gdb_printf (_("Selected thread is running.\n"));
return;
@@ -1948,13 +1949,13 @@ info_program_command (const char *args, int from_tty)
print_thread_id (tp),
target_pid_to_str (tp->ptid).c_str ());
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
{
gdb_printf (_("Thread has since exited.\n"));
return;
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
{
gdb_printf (_("Thread is now running.\n"));
return;
@@ -2473,7 +2474,7 @@ proceed_after_attach (inferior *inf)
scoped_restore_current_thread restore_thread;
for (thread_info *thread : inf->non_exited_threads ())
- if (!thread->executing ()
+ if (thread->internal_state () != THREAD_INT_RUNNING
&& !thread->stop_requested
&& thread->stop_signal () == GDB_SIGNAL_0)
{
@@ -2628,6 +2629,10 @@ attach_command (const char *args, int from_tty)
this function should probably be moved into target_pre_inferior. */
target_pre_inferior ();
+ /* Set up execution context to know that we should return from
+ wait_for_inferior as soon as the target reports a stop. */
+ init_wait_for_inferior ();
+
gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec);
args = stripped.get ();
@@ -2670,10 +2675,6 @@ attach_command (const char *args, int from_tty)
finished. */
target_terminal::inferior ();
- /* Set up execution context to know that we should return from
- wait_for_inferior as soon as the target reports a stop. */
- init_wait_for_inferior ();
-
inferior->needs_setup = true;
if (target_is_non_stop_p ())
@@ -2753,7 +2754,7 @@ notice_new_inferior (thread_info *thr, bool leave_running, int from_tty)
/* When we "notice" a new inferior we need to do all the things we
would normally do if we had just attached to it. */
- if (thr->executing ())
+ if (thr->internal_state () == THREAD_INT_RUNNING)
{
struct inferior *inferior = current_inferior ();
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 54f5229b420..301a78f9ebb 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -201,8 +201,8 @@ extern void child_interrupt (struct target_ops *self);
/* From fork-child.c */
/* Helper function to call STARTUP_INFERIOR with PID and NUM_TRAPS.
- This function already calls set_executing. Return the ptid_t from
- STARTUP_INFERIOR. */
+ This function already sets the threads' internal state to
+ THREAD_STOPPED. Return the ptid_t from STARTUP_INFERIOR. */
extern ptid_t gdb_startup_inferior (pid_t pid, int num_traps);
/* From infcmd.c */
diff --git a/gdb/inflow.c b/gdb/inflow.c
index f5ae6cd982f..aef3c8c7081 100644
--- a/gdb/inflow.c
+++ b/gdb/inflow.c
@@ -525,7 +525,7 @@ child_interrupt (struct target_ops *self)
thread_info *resumed = NULL;
for (thread_info *thr : all_non_exited_threads ())
{
- if (thr->executing ())
+ if (thr->internal_state () == THREAD_INT_RUNNING)
{
resumed = thr;
break;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9f625a7bd44..20649ace942 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -991,14 +991,14 @@ follow_inferior_reset_breakpoints (void)
insert_breakpoints ();
}
-/* The child has exited or execed: resume THREAD, a thread of the parent,
- if it was meant to be executing. */
+/* The child has exited or execed: resume THREAD, a thread of the
+ parent, if it was meant to be running. */
static void
proceed_after_vfork_done (thread_info *thread)
{
- if (thread->state == THREAD_RUNNING
- && !thread->executing ()
+ if (thread->state () == THREAD_RUNNING
+ && thread->internal_state () != THREAD_INT_RUNNING
&& !thread->stop_requested
&& thread->stop_signal () == GDB_SIGNAL_0)
{
@@ -2227,15 +2227,14 @@ start_step_over (void)
}
if (tp->control.trap_expected
- || tp->resumed ()
- || tp->executing ())
+ || tp->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS
+ || tp->internal_state () == THREAD_INT_RUNNING)
{
internal_error ("[%s] has inconsistent state: "
- "trap_expected=%d, resumed=%d, executing=%d\n",
+ "trap_expected=%d, internal_state=%s\n",
tp->ptid.to_string ().c_str (),
tp->control.trap_expected,
- tp->resumed (),
- tp->executing ());
+ thread_int_state_string (tp->internal_state ()));
}
infrun_debug_printf ("resuming [%s] for step-over",
@@ -2259,7 +2258,7 @@ start_step_over (void)
/* If the thread's step over could not be initiated because no buffers
were available, it was re-added to the global step over chain. */
- if (tp->resumed ())
+ if (tp->internal_state () != THREAD_INT_STOPPED)
{
infrun_debug_printf ("[%s] was resumed.",
tp->ptid.to_string ().c_str ());
@@ -2659,7 +2658,7 @@ resume_1 (enum gdb_signal sig)
tp->control.currently_stepping);
tp->inf->process_target ()->threads_executing = true;
- tp->set_resumed (true);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
/* FIXME: What should we do if we are supposed to resume this
thread with a signal? Maybe we should maintain a queue of
@@ -2784,7 +2783,6 @@ resume_1 (enum gdb_signal sig)
resume_ptid = internal_resume_ptid (user_step);
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
- tp->set_resumed (true);
return;
}
}
@@ -2976,7 +2974,6 @@ resume_1 (enum gdb_signal sig)
}
do_target_resume (resume_ptid, step, sig);
- tp->set_resumed (true);
}
/* Resume the inferior. SIG is the signal to give the inferior
@@ -3040,6 +3037,7 @@ static void
clear_proceed_status_thread (struct thread_info *tp)
{
infrun_debug_printf ("%s", tp->ptid.to_string ().c_str ());
+ gdb_assert (tp->internal_state () != THREAD_INT_RUNNING);
/* If we're starting a new sequence, then the previous finished
single-step is no longer relevant. */
@@ -3051,6 +3049,7 @@ clear_proceed_status_thread (struct thread_info *tp)
"Discarding.",
tp->ptid.to_string ().c_str ());
+ tp->set_internal_state (THREAD_INT_STOPPED);
tp->clear_pending_waitstatus ();
tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
}
@@ -3125,7 +3124,8 @@ clear_proceed_status (int step)
/* In all-stop mode, delete the per-thread status of all threads
we're about to resume, implicitly and explicitly. */
for (thread_info *tp : all_non_exited_threads (resume_target, resume_ptid))
- clear_proceed_status_thread (tp);
+ if (tp->internal_state () != THREAD_INT_RUNNING)
+ clear_proceed_status_thread (tp);
}
if (inferior_ptid != null_ptid)
@@ -3485,6 +3485,8 @@ check_multi_target_resumption (process_stratum_target *resume_target)
static void
proceed_resume_thread_checked (thread_info *tp)
{
+ gdb_assert (tp->internal_state () != THREAD_INT_EXITED);
+
if (!tp->inf->has_execution ())
{
infrun_debug_printf ("[%s] target has no execution",
@@ -3492,11 +3494,10 @@ proceed_resume_thread_checked (thread_info *tp)
return;
}
- if (tp->resumed ())
+ if (tp->internal_state () != THREAD_INT_STOPPED)
{
infrun_debug_printf ("[%s] resumed",
tp->ptid.to_string ().c_str ());
- gdb_assert (tp->executing () || tp->has_pending_waitstatus ());
return;
}
@@ -3672,7 +3673,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
inferior function, as in that case we pretend the inferior
doesn't run at all. */
if (!cur_thr->control.in_infcall)
- set_running (resume_target, resume_ptid, true);
+ set_state (resume_target, resume_ptid, THREAD_RUNNING);
infrun_debug_printf ("addr=%s, signal=%s, resume_ptid=%s",
paddress (gdbarch, addr),
@@ -3873,9 +3874,9 @@ infrun_thread_stop_requested (ptid_t ptid)
for reporting the stop now. */
for (thread_info *tp : all_threads (curr_target, ptid))
{
- if (tp->state != THREAD_RUNNING)
+ if (tp->state () != THREAD_RUNNING)
continue;
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
continue;
/* Remove matching threads from the step-over queue, so
@@ -3906,10 +3907,10 @@ infrun_thread_stop_requested (ptid_t ptid)
if (step_over_info_valid_p ())
continue;
- /* Otherwise we can process the (new) pending event now. Set
- it so this pending event is considered by
+ /* Otherwise we can process the (new) pending event now. Switch
+ state so this pending event is considered by
do_target_wait. */
- tp->set_resumed (true);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
}
}
@@ -4015,7 +4016,7 @@ random_pending_event_thread (inferior *inf, ptid_t waiton_ptid)
}
infrun_debug_printf ("Found %s.", thread->ptid.to_string ().c_str ());
- gdb_assert (thread->resumed ());
+ gdb_assert (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS);
gdb_assert (thread->has_pending_waitstatus ());
return thread;
@@ -4128,6 +4129,7 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
*status = tp->pending_waitstatus ();
+ tp->set_internal_state (THREAD_INT_STOPPED);
tp->clear_pending_waitstatus ();
/* Wake up the event loop again, until all pending events are
@@ -4326,7 +4328,7 @@ prepare_for_detach (void)
{
if (thr->displaced_step_state.in_progress ())
{
- if (thr->executing ())
+ if (thr->internal_state () == THREAD_INT_RUNNING)
{
if (!thr->stop_requested)
{
@@ -4334,8 +4336,6 @@ prepare_for_detach (void)
thr->stop_requested = true;
}
}
- else
- thr->set_resumed (false);
}
}
@@ -4474,7 +4474,7 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
for (thread_info *thr : all_threads_safe ())
{
- if (thr->state == THREAD_EXITED)
+ if (thr->state () == THREAD_EXITED)
continue;
if (thr == ecs->event_thread)
@@ -4810,7 +4810,7 @@ fetch_inferior_event ()
if (cmd_done
&& exec_done_display_p
&& (inferior_ptid == null_ptid
- || inferior_thread ()->state != THREAD_RUNNING))
+ || inferior_thread ()->state () != THREAD_RUNNING))
gdb_printf (_("completed.\n"));
}
@@ -5373,14 +5373,14 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus &ws)
}
}
-/* Mark the non-executing threads accordingly. In all-stop, all
+/* Mark the internally stopped threads accordingly. In all-stop, all
threads of all processes are stopped when we get any event
reported. In non-stop mode, only the event thread stops. */
static void
-mark_non_executing_threads (process_stratum_target *target,
- ptid_t event_ptid,
- const target_waitstatus &ws)
+mark_internally_stopped_threads (process_stratum_target *target,
+ ptid_t event_ptid,
+ const target_waitstatus &ws)
{
ptid_t mark_ptid;
@@ -5400,18 +5400,15 @@ mark_non_executing_threads (process_stratum_target *target,
target_mourn_inferior, by associating the same
inferior/thread to another fork. We haven't mourned yet at
this point, but we must mark any threads left in the
- process as not-executing so that finish_thread_state marks
- them stopped (in the user's perspective) if/when we present
+ process as internally stopped so that finish_thread_state marks
+ them stopped in the user's perspective if/when we present
the stop to the user. */
mark_ptid = ptid_t (event_ptid.pid ());
}
else
mark_ptid = event_ptid;
- set_executing (target, mark_ptid, false);
-
- /* Likewise the resumed flag. */
- set_resumed (target, mark_ptid, false);
+ set_internal_state (target, mark_ptid, THREAD_INT_STOPPED);
}
/* Handle one event after stopping threads. If the eventing thread
@@ -5477,11 +5474,11 @@ handle_one (const wait_one_event &event)
if (t != nullptr)
{
- /* Set the threads as non-executing to avoid
- another stop attempt on them. */
+ /* Set the threads as internally stopped to avoid another
+ stop attempt on them. */
switch_to_thread_no_regs (t);
- mark_non_executing_threads (event.target, event.ptid,
- event.ws);
+ mark_internally_stopped_threads (event.target, event.ptid,
+ event.ws);
save_waitstatus (t, event.ws);
t->stop_requested = false;
@@ -5503,8 +5500,7 @@ handle_one (const wait_one_event &event)
t = add_thread (event.target, event.ptid);
t->stop_requested = false;
- t->set_executing (false);
- t->set_resumed (false);
+ t->set_internal_state (THREAD_INT_STOPPED);
t->control.may_range_step = 0;
/* This may be the first time we see the inferior report
@@ -5733,7 +5729,7 @@ stop_all_threads (const char *reason, inferior *inf)
if (!target_is_non_stop_p ())
continue;
- if (t->executing ())
+ if (t->internal_state () == THREAD_INT_RUNNING)
{
/* If already stopping, don't request a stop again.
We just haven't seen the notification yet. */
@@ -5760,7 +5756,7 @@ stop_all_threads (const char *reason, inferior *inf)
/* The thread may be not executing, but still be
resumed with a pending status to process. */
- t->set_resumed (false);
+ t->set_internal_state (THREAD_INT_STOPPED);
}
}
@@ -5876,7 +5872,7 @@ handle_no_resumed (struct execution_control_state *ecs)
for (thread_info *thread : all_non_exited_threads ())
{
- if (swap_terminal && thread->executing ())
+ if (swap_terminal && thread->internal_state () == THREAD_INT_RUNNING)
{
if (thread->inf != curr_inf)
{
@@ -5888,7 +5884,7 @@ handle_no_resumed (struct execution_control_state *ecs)
swap_terminal = false;
}
- if (!ignore_event && thread->resumed ())
+ if (!ignore_event && thread->internal_state () != THREAD_INT_STOPPED)
{
/* Either there were no unwaited-for children left in the
target at some point, but there are now, or some target
@@ -6160,7 +6156,7 @@ handle_inferior_event (struct execution_control_state *ecs)
}
}
- mark_non_executing_threads (ecs->target, ecs->ptid, ecs->ws);
+ mark_internally_stopped_threads (ecs->target, ecs->ptid, ecs->ws);
switch (ecs->ws.kind ())
{
@@ -6424,7 +6420,7 @@ handle_inferior_event (struct execution_control_state *ecs)
/* If not resuming the parent, mark it stopped. */
if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
&& follow_child && !detach_fork && !non_stop && !sched_multi)
- parent->set_running (false);
+ parent->set_state (THREAD_STOPPED);
/* If resuming the child, mark it running. */
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
@@ -6432,7 +6428,7 @@ handle_inferior_event (struct execution_control_state *ecs)
|| (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
&& (follow_child
|| (!detach_fork && (non_stop || sched_multi)))))
- child->set_running (true);
+ child->set_state (THREAD_RUNNING);
/* In non-stop mode, also resume the other branch. */
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
@@ -6624,18 +6620,17 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
continue;
}
- if (!(tp->state == THREAD_RUNNING || tp->control.in_infcall))
+ if (!(tp->state () == THREAD_RUNNING || tp->control.in_infcall))
{
infrun_debug_printf ("restart threads: [%s] not meant to be running",
tp->ptid.to_string ().c_str ());
continue;
}
- if (tp->resumed ())
+ if (tp->internal_state () != THREAD_INT_STOPPED)
{
- infrun_debug_printf ("restart threads: [%s] resumed",
+ infrun_debug_printf ("restart threads: [%s] already resumed",
tp->ptid.to_string ().c_str ());
- gdb_assert (tp->executing () || tp->has_pending_waitstatus ());
continue;
}
@@ -6643,7 +6638,6 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
{
infrun_debug_printf ("restart threads: [%s] needs step-over",
tp->ptid.to_string ().c_str ());
- gdb_assert (!tp->resumed ());
continue;
}
@@ -6652,7 +6646,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
{
infrun_debug_printf ("restart threads: [%s] has pending status",
tp->ptid.to_string ().c_str ());
- tp->set_resumed (true);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
continue;
}
@@ -6691,7 +6685,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
static bool
resumed_thread_with_pending_status (struct thread_info *tp)
{
- return tp->resumed () && tp->has_pending_waitstatus ();
+ return tp->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS;
}
/* Called when we get an event that may finish an in-line or
@@ -6780,12 +6774,12 @@ finish_step_over (struct execution_control_state *ecs)
/* Record the event thread's event for later. */
save_waitstatus (tp, ecs->ws);
- /* This was cleared early, by handle_inferior_event. Set it
+ /* The internal state was reset to stopped early, by
+ handle_inferior_event. Switch to resumed-pending-status
so this pending event is considered by
do_target_wait. */
- tp->set_resumed (true);
-
- gdb_assert (!tp->executing ());
+ gdb_assert (tp->internal_state () == THREAD_INT_STOPPED);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
regcache = get_thread_regcache (tp);
tp->set_stop_pc (regcache_read_pc (regcache));
@@ -8437,7 +8431,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
for (thread_info *tp : all_threads_safe ())
{
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
continue;
if (tp->has_pending_waitstatus ())
@@ -8461,7 +8455,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
for (thread_info *tp : all_threads_safe ())
{
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
continue;
if (tp->has_pending_waitstatus ())
@@ -8496,26 +8490,27 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
current inferior may no longer have a process_stratum target
pushed, as we just detached. */
- /* See if we have a THREAD_RUNNING thread that need to be
- re-resumed. If we have any thread that is already executing,
- then we don't need to resume the target -- it is already been
- resumed. With the remote target (in all-stop), it's even
- impossible to issue another resumption if the target is already
- resumed, until the target reports a stop. */
+ /* See if we have a thread that is running from the user's
+ perspective that need to be re-resumed. If we have any thread
+ that is already executing, then we don't need to resume the
+ target -- it is already been resumed. With the remote target (in
+ all-stop), it's even impossible to issue another resumption if
+ the target is already resumed, until the target reports a
+ stop. */
for (thread_info *thr : all_threads (proc_target))
{
- if (thr->state != THREAD_RUNNING)
+ if (thr->state () != THREAD_RUNNING)
continue;
/* If we have any thread that is already executing, then we
don't need to resume the target -- it is already been
resumed. */
- if (thr->executing ())
+ if (thr->internal_state () == THREAD_INT_RUNNING)
return;
- /* If we have a pending event to process, skip resuming the
+ /* If we have a pending status to process, skip resuming the
target and go straight to processing it. */
- if (thr->resumed () && thr->has_pending_waitstatus ())
+ if (thr->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
return;
}
@@ -8528,7 +8523,7 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
it. */
for (thread_info *thr : all_threads (proc_target))
{
- if (thr->state != THREAD_RUNNING)
+ if (thr->state () != THREAD_RUNNING)
continue;
execution_control_state ecs (thr);
@@ -8565,7 +8560,7 @@ keep_going_stepped_thread (struct thread_info *tp)
stepping thread is still alive. For that reason, we need to
synchronously query the target now. */
- if (tp->state == THREAD_EXITED || !target_thread_alive (tp->ptid))
+ if (tp->state () == THREAD_EXITED || !target_thread_alive (tp->ptid))
{
infrun_debug_printf ("not resuming previously stepped thread, it has "
"vanished");
@@ -8618,7 +8613,6 @@ keep_going_stepped_thread (struct thread_info *tp)
get_frame_address_space (frame),
tp->stop_pc ());
- tp->set_resumed (true);
resume_ptid = internal_resume_ptid (tp->control.stepping_command);
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
}
@@ -9027,7 +9021,7 @@ static void
keep_going_pass_signal (struct execution_control_state *ecs)
{
gdb_assert (ecs->event_thread->ptid == inferior_ptid);
- gdb_assert (!ecs->event_thread->resumed ());
+ gdb_assert (ecs->event_thread->internal_state () == THREAD_INT_STOPPED);
/* Save the pc before execution, to compare with pc after stop. */
ecs->event_thread->prev_pc
@@ -9495,7 +9489,7 @@ stop_context::changed () const
return true;
if (inf_num != current_inferior ()->num)
return true;
- if (thread != nullptr && thread->state != THREAD_STOPPED)
+ if (thread != nullptr && thread->state () != THREAD_STOPPED)
return true;
if (get_stop_id () != stop_id)
return true;
diff --git a/gdb/infrun.h b/gdb/infrun.h
index b9b64aca45a..b09e7c1df8a 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -66,12 +66,11 @@ infrun_debug_show_threads (const char *title, ThreadRange threads)
infrun_debug_printf ("%s:", title);
for (thread_info *thread : threads)
- infrun_debug_printf (" thread %s, executing = %d, resumed = %d, "
+ infrun_debug_printf (" thread %s, internal_state = %s, "
"state = %s",
thread->ptid.to_string ().c_str (),
- thread->executing (),
- thread->resumed (),
- thread_state_string (thread->state));
+ thread_int_state_string (thread->internal_state ()),
+ thread_state_string (thread->state ()));
}
}
diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c
index 338ba032a0e..377c68ae6b9 100644
--- a/gdb/linux-fork.c
+++ b/gdb/linux-fork.c
@@ -363,8 +363,7 @@ fork_load_infrun_state (struct fork_info *fp)
inferior_thread ()->set_stop_pc
(regcache_read_pc (get_thread_regcache (inferior_thread ())));
- inferior_thread ()->set_executing (false);
- inferior_thread ()->set_resumed (false);
+ inferior_thread ()->set_internal_state (THREAD_INT_STOPPED);
nullify_last_target_wait_ptid ();
/* Now restore the file positions of open file descriptors. */
@@ -726,7 +725,7 @@ delete_checkpoint_command (const char *args, int from_tty)
ptid. */
thread_info *parent = linux_target->find_thread (pptid);
if ((parent == NULL && find_fork_ptid (pptid).first != nullptr)
- || (parent != NULL && parent->state == THREAD_STOPPED))
+ || (parent != NULL && parent->state () == THREAD_STOPPED))
{
if (inferior_call_waitpid (pptid, ptid.pid ()))
warning (_("Unable to wait pid %s"),
@@ -866,7 +865,7 @@ print_checkpoints (struct ui_out *uiout, inferior *req_inf, fork_info *req_fi)
uiout->field_string
("target-id", target_pid_to_str (proc_ptid (fi.ptid)).c_str ());
- if (t->state == THREAD_RUNNING && is_current)
+ if (t->state () == THREAD_RUNNING && is_current)
uiout->text ("(running)");
else
{
@@ -1108,7 +1107,7 @@ restart_command (const char *args, int from_tty)
/* Don't allow switching from a thread/fork that's running. */
inferior *curinf = current_inferior ();
if (curinf->pid != 0
- && any_thread_of_inferior (curinf)->state == THREAD_RUNNING)
+ && any_thread_of_inferior (curinf)->state () == THREAD_RUNNING)
error (_("Cannot execute this command while "
"the selected thread is running."));
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 2f98060506b..4de4cc83cfa 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1246,8 +1246,8 @@ linux_nat_target::attach (const char *args, int from_tty)
if (lwp->ptid.pid () != lwp->ptid.lwp ())
{
add_thread (linux_target, lwp->ptid);
- set_running (linux_target, lwp->ptid, true);
- set_executing (linux_target, lwp->ptid, true);
+ set_state (linux_target, lwp->ptid, THREAD_RUNNING);
+ set_internal_state (linux_target, lwp->ptid, THREAD_INT_RUNNING);
}
return 0;
});
@@ -1331,7 +1331,8 @@ get_detach_signal (struct lwp_info *lp)
{
thread_info *tp = linux_target->find_thread (lp->ptid);
- if (target_is_non_stop_p () && !tp->executing ())
+ if (target_is_non_stop_p ()
+ && tp->internal_state () != THREAD_INT_RUNNING)
{
if (tp->has_pending_waitstatus ())
{
diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c
index f60116afb2c..7f8f9eeaa53 100644
--- a/gdb/linux-thread-db.c
+++ b/gdb/linux-thread-db.c
@@ -1361,7 +1361,7 @@ record_thread (struct thread_db_info *info,
/* Add the thread to GDB's thread list. If we already know about a
thread with this PTID, but it's marked exited, then the kernel
reused the tid of an old thread. */
- if (tp == NULL || tp->state == THREAD_EXITED)
+ if (tp == NULL || tp->state () == THREAD_EXITED)
tp = add_thread_with_info (info->process_target, ptid,
private_thread_info_up (priv));
else
@@ -1625,7 +1625,7 @@ thread_db_target::update_thread_list ()
continue;
thread_info *thread = any_live_thread_of_inferior (inf);
- if (thread == NULL || thread->executing ())
+ if (thread == NULL || thread->internal_state () == THREAD_INT_RUNNING)
continue;
/* It's best to avoid td_ta_thr_iter if possible. That walks
diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c
index 9cbb85722b3..79c8b9979e2 100644
--- a/gdb/mi/mi-cmd-var.c
+++ b/gdb/mi/mi-cmd-var.c
@@ -608,14 +608,14 @@ mi_cmd_var_update_iter (struct varobj *var, bool only_floating,
if (thread_id == -1)
{
thread_stopped = (inferior_ptid == null_ptid
- || inferior_thread ()->state == THREAD_STOPPED);
+ || inferior_thread ()->state () == THREAD_STOPPED);
}
else
{
thread_info *tp = find_thread_global_id (thread_id);
thread_stopped = (tp == NULL
- || tp->state == THREAD_STOPPED);
+ || tp->state () == THREAD_STOPPED);
}
if (thread_stopped
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index ae1070eb188..0465d6e9fc0 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -868,7 +868,7 @@ mi_interp::on_user_selected_context_changed (user_selected_what selection)
gdb_printf (this->event_channel, "thread-selected,id=\"%d\"",
tp->global_num);
- if (tp->state != THREAD_RUNNING)
+ if (tp->state () != THREAD_RUNNING)
{
if (has_stack_frames ())
print_stack_frame_to_uiout (mi_uiout, get_selected_frame (NULL),
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index cda72ca4f5b..cfb6fcf0772 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -239,7 +239,7 @@ mi_cmd_exec_jump (const char *args, const char *const *argv, int argc)
static void
proceed_thread (struct thread_info *thread, int pid)
{
- if (thread->state != THREAD_STOPPED)
+ if (thread->state () != THREAD_STOPPED)
return;
if (pid != 0 && thread->ptid.pid () != pid)
@@ -367,7 +367,7 @@ mi_cmd_exec_interrupt (const char *command, const char *const *argv, int argc)
iterate_over_threads ([&] (struct thread_info *thread)
{
- if (thread->state != THREAD_RUNNING)
+ if (thread->state () != THREAD_RUNNING)
return false;
if (thread->ptid.pid () != inf->pid)
@@ -508,7 +508,7 @@ mi_cmd_target_detach (const char *command, const char *const *argv, int argc)
target_detach detaches from the parent of inferior_ptid. */
tp = iterate_over_threads ([&] (struct thread_info *ti)
{
- return ti->ptid.pid () == pid && ti->state != THREAD_EXITED;
+ return ti->ptid.pid () == pid && ti->state () != THREAD_EXITED;
});
if (!tp)
error (_("Thread group is empty"));
@@ -2059,7 +2059,7 @@ mi_cmd_execute (struct mi_parse *parse)
if (tp == NULL)
error (_("Invalid thread id: %d"), parse->thread);
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
error (_("Thread id: %d has terminated"), parse->thread);
if (parse->cmd->preserve_user_selected_context ())
diff --git a/gdb/process-stratum-target.c b/gdb/process-stratum-target.c
index 329589dc34b..039dac4c0fe 100644
--- a/gdb/process-stratum-target.c
+++ b/gdb/process-stratum-target.c
@@ -114,8 +114,9 @@ process_stratum_target::maybe_add_resumed_with_pending_wait_status
{
gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
- if (thread->resumed () && thread->has_pending_waitstatus ())
+ if (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
{
+ gdb_assert (thread->has_pending_waitstatus ());
infrun_debug_printf ("adding to resumed threads with event list: %s",
thread->ptid.to_string ().c_str ());
m_resumed_with_pending_wait_status.push_back (*thread);
@@ -128,8 +129,9 @@ void
process_stratum_target::maybe_remove_resumed_with_pending_wait_status
(thread_info *thread)
{
- if (thread->resumed () && thread->has_pending_waitstatus ())
+ if (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
{
+ gdb_assert (thread->has_pending_waitstatus ());
infrun_debug_printf ("removing from resumed threads with event list: %s",
thread->ptid.to_string ().c_str ());
gdb_assert (thread->resumed_with_pending_wait_status_node.is_linked ());
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index 4f1f8d47e32..0a392616d48 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -261,7 +261,7 @@ thpy_is_stopped (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_STOPPED)
+ if (thread_obj->thread->state () == THREAD_STOPPED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
@@ -277,7 +277,7 @@ thpy_is_running (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_RUNNING)
+ if (thread_obj->thread->state () == THREAD_RUNNING)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
@@ -293,7 +293,7 @@ thpy_is_exited (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_EXITED)
+ if (thread_obj->thread->state () == THREAD_EXITED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index 2d71b72ec51..9705d3a251a 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -2042,18 +2042,18 @@ get_thread_current_frame_id (struct thread_info *tp)
process_stratum_target *proc_target = tp->inf->process_target ();
- /* Clear the executing flag to allow changes to the current frame.
- We are not actually running, yet. We just started a reverse execution
- command or a record goto command.
- For the latter, EXECUTING is false and this has no effect.
- For the former, EXECUTING is true and we're in wait, about to
- move the thread. Since we need to recompute the stack, we temporarily
- set EXECUTING to false. */
- bool executing = tp->executing ();
- set_executing (proc_target, inferior_ptid, false);
+ /* Temporarily set the thread to internally stopped to allow changes
+ to the current frame. We are not actually running, yet. We just
+ started a reverse execution command or a record goto command.
+ For the latter, the thread is stopped and this has no effect.
+ For the former, the thread is running and we're in wait, about to
+ move the thread. Since we need to recompute the stack, we
+ temporarily set the thread to internally stopped. */
+ thread_int_state prev_int_state = tp->internal_state ();
+ set_internal_state (proc_target, inferior_ptid, THREAD_INT_STOPPED);
SCOPE_EXIT
{
- set_executing (proc_target, inferior_ptid, executing);
+ set_internal_state (proc_target, inferior_ptid, prev_int_state);
};
return get_frame_id (get_current_frame ());
}
diff --git a/gdb/record-full.c b/gdb/record-full.c
index b52fb062d80..cf162f451fc 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -1270,10 +1270,12 @@ record_full_wait_1 (struct target_ops *ops,
{
/* Try to insert the software single step breakpoint.
If insert success, set step to 0. */
- set_executing (proc_target, inferior_ptid, false);
+ set_internal_state (proc_target, inferior_ptid,
+ THREAD_INT_STOPPED);
SCOPE_EXIT
{
- set_executing (proc_target, inferior_ptid, true);
+ set_internal_state (proc_target, inferior_ptid,
+ THREAD_INT_RUNNING);
};
reinit_frame_cache ();
diff --git a/gdb/regcache.c b/gdb/regcache.c
index e3d435f90d5..cd6e65f9dc9 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -429,7 +429,7 @@ get_thread_regcache (process_stratum_target *target, ptid_t ptid)
struct regcache *
get_thread_regcache (thread_info *thread)
{
- gdb_assert (thread->state != THREAD_EXITED);
+ gdb_assert (thread->state () != THREAD_EXITED);
return get_thread_regcache (thread->inf->process_target (),
thread->ptid);
diff --git a/gdb/remote.c b/gdb/remote.c
index 1c49cdf0fc0..413425ab8e7 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -1220,12 +1220,15 @@ class remote_target : public process_stratum_target
ptid_t select_thread_for_ambiguous_stop_reply
(const struct target_waitstatus &status);
- void remote_notice_new_inferior (ptid_t currthread, bool executing);
+ void remote_notice_new_inferior (ptid_t currthread,
+ thread_int_state internal_state);
void print_one_stopped_thread (thread_info *thread);
void process_initial_stop_replies (int from_tty);
- thread_info *remote_add_thread (ptid_t ptid, bool running, bool executing,
+ thread_info *remote_add_thread (ptid_t ptid,
+ thread_state state,
+ thread_int_state internal_state,
bool silent_p);
void btrace_sync_conf (const btrace_config *conf);
@@ -2924,13 +2927,16 @@ static remote_thread_info *get_remote_thread_info (thread_info *thread);
static remote_thread_info *get_remote_thread_info (remote_target *target,
ptid_t ptid);
-/* Add thread PTID to GDB's thread list. Tag it as executing/running
- according to EXECUTING and RUNNING respectively. If SILENT_P (or the
- remote_state::starting_up flag) is true then the new thread is added
- silently, otherwise the new thread will be announced to the user. */
+/* Add thread PTID to GDB's thread list. Tag its user and internal
+ states according to STATE and INTERNAL_STATE respectively. If
+ SILENT_P (or the remote_state::starting_up flag) is true then the
+ new thread is added silently, otherwise the new thread will be
+ announced to the user. */
thread_info *
-remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
+remote_target::remote_add_thread (ptid_t ptid,
+ thread_state state,
+ thread_int_state internal_state,
bool silent_p)
{
struct remote_state *rs = get_remote_state ();
@@ -2947,10 +2953,10 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
else
thread = add_thread (this, ptid);
- if (executing)
+ if (internal_state == THREAD_INT_RUNNING)
get_remote_thread_info (thread)->set_resumed ();
- set_executing (this, ptid, executing);
- set_running (this, ptid, running);
+ set_internal_state (this, ptid, internal_state);
+ set_state (this, ptid, state);
return thread;
}
@@ -2959,26 +2965,29 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
It may be the first time we hear about such thread, so take the
opportunity to add it to GDB's thread list. In case this is the
first time we're noticing its corresponding inferior, add it to
- GDB's inferior list as well. EXECUTING indicates whether the
- thread is (internally) executing or stopped. */
+ GDB's inferior list as well. INTERNAL_STATE indicates whether the
+ thread is internally running or stopped. */
void
-remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
+remote_target::remote_notice_new_inferior (ptid_t currthread,
+ thread_int_state internal_state)
{
/* In non-stop mode, we assume new found threads are (externally)
running until proven otherwise with a stop reply. In all-stop,
we can only get here if all threads are stopped. */
- bool running = target_is_non_stop_p ();
+ thread_state state = (target_is_non_stop_p ()
+ ? THREAD_RUNNING
+ : THREAD_STOPPED);
/* If this is a new thread, add it to GDB's thread list.
If we leave it up to WFI to do this, bad things will happen. */
thread_info *tp = this->find_thread (currthread);
- if (tp != NULL && tp->state == THREAD_EXITED)
+ if (tp != NULL && tp->state () == THREAD_EXITED)
{
/* We're seeing an event on a thread id we knew had exited.
This has to be a new thread reusing the old id. Add it. */
- remote_add_thread (currthread, running, executing, false);
+ remote_add_thread (currthread, state, internal_state, false);
return;
}
@@ -3000,7 +3009,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
else
{
thread_info *thr
- = remote_add_thread (currthread, running, executing, false);
+ = remote_add_thread (currthread, state, internal_state, false);
switch_to_thread (thr);
}
return;
@@ -3031,7 +3040,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
/* This is really a new thread. Add it. */
thread_info *new_thr
- = remote_add_thread (currthread, running, executing, false);
+ = remote_add_thread (currthread, state, internal_state, false);
/* If we found a new inferior, let the common code do whatever
it needs to with it (e.g., read shared libraries, insert
@@ -3042,7 +3051,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
struct remote_state *rs = get_remote_state ();
if (!rs->starting_up)
- notice_new_inferior (new_thr, executing, 0);
+ notice_new_inferior (new_thr, internal_state, 0);
}
}
}
@@ -4363,12 +4372,14 @@ remote_target::update_thread_list ()
if (item.ptid != null_ptid)
{
/* In non-stop mode, we assume new found threads are
- executing until proven otherwise with a stop reply.
- In all-stop, we can only get here if all threads are
+ running until proven otherwise with a stop reply. In
+ all-stop, we can only get here if all threads are
stopped. */
- bool executing = target_is_non_stop_p ();
+ thread_int_state internal_state = (target_is_non_stop_p ()
+ ? THREAD_INT_RUNNING
+ : THREAD_INT_STOPPED);
- remote_notice_new_inferior (item.ptid, executing);
+ remote_notice_new_inferior (item.ptid, internal_state);
thread_info *tp = this->find_thread (item.ptid);
remote_thread_info *info = get_remote_thread_info (tp);
@@ -4982,8 +4993,8 @@ remote_target::process_initial_stop_replies (int from_tty)
|| ws.sig () != GDB_SIGNAL_0)
evthread->set_pending_waitstatus (ws);
- set_executing (this, event_ptid, false);
- set_running (this, event_ptid, false);
+ set_internal_state (this, event_ptid, THREAD_INT_STOPPED);
+ set_state (this, event_ptid, THREAD_STOPPED);
get_remote_thread_info (evthread)->set_not_resumed ();
}
@@ -4996,7 +5007,7 @@ remote_target::process_initial_stop_replies (int from_tty)
if (non_stop)
{
thread_info *thread = any_live_thread_of_inferior (inf);
- notice_new_inferior (thread, thread->state == THREAD_RUNNING,
+ notice_new_inferior (thread, thread->state () == THREAD_RUNNING,
from_tty);
}
}
@@ -5038,8 +5049,8 @@ remote_target::process_initial_stop_replies (int from_tty)
first = thread;
if (!non_stop)
- thread->set_running (false);
- else if (thread->state != THREAD_STOPPED)
+ thread->set_state (THREAD_STOPPED);
+ else if (thread->state () != THREAD_STOPPED)
continue;
if (selected == nullptr && thread->has_pending_waitstatus ())
@@ -6585,7 +6596,7 @@ remote_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
void
remote_target::follow_clone (ptid_t child_ptid)
{
- remote_add_thread (child_ptid, false, false, false);
+ remote_add_thread (child_ptid, THREAD_STOPPED, THREAD_INT_STOPPED, false);
}
/* Target follow-exec function for remote targets. Save EXECD_PATHNAME
@@ -6693,7 +6704,8 @@ extended_remote_target::attach (const char *args, int from_tty)
/* Add the main thread to the thread list. We add the thread
silently in this case (the final true parameter). */
- thread_info *thr = remote_add_thread (curr_ptid, true, true, true);
+ thread_info *thr = remote_add_thread (curr_ptid, THREAD_RUNNING,
+ THREAD_INT_RUNNING, true);
switch_to_thread (thr);
}
@@ -8571,7 +8583,7 @@ remote_target::process_stop_reply (stop_reply_up stop_reply,
&& status->kind () != TARGET_WAITKIND_SIGNALLED
&& status->kind () != TARGET_WAITKIND_NO_RESUMED)
{
- remote_notice_new_inferior (ptid, false);
+ remote_notice_new_inferior (ptid, THREAD_INT_STOPPED);
/* Expedited registers. */
if (!stop_reply->regcache.empty ())
@@ -8677,7 +8689,7 @@ static ptid_t
first_remote_resumed_thread (remote_target *target)
{
for (thread_info *tp : all_non_exited_threads (target, minus_one_ptid))
- if (tp->resumed ())
+ if (tp->internal_state () != THREAD_INT_STOPPED)
return tp->ptid;
return null_ptid;
}
diff --git a/gdb/sol-thread.c b/gdb/sol-thread.c
index ae1e5c07457..3c7f0d3d353 100644
--- a/gdb/sol-thread.c
+++ b/gdb/sol-thread.c
@@ -451,7 +451,7 @@ sol_thread_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
if (rtnval.tid_p ())
{
thread_info *thr = current_inferior ()->find_thread (rtnval);
- if (thr == NULL || thr->state == THREAD_EXITED)
+ if (thr == NULL || thr->state () == THREAD_EXITED)
{
process_stratum_target *proc_target
= current_inferior ()->process_target ();
@@ -997,7 +997,7 @@ sol_update_thread_list_callback (const td_thrhandle_t *th, void *ignored)
ptid_t ptid = ptid_t (current_inferior ()->pid, 0, ti.ti_tid);
thread_info *thr = current_inferior ()->find_thread (ptid);
- if (thr == NULL || thr->state == THREAD_EXITED)
+ if (thr == NULL || thr->state () == THREAD_EXITED)
{
process_stratum_target *proc_target
= current_inferior ()->process_target ();
diff --git a/gdb/target.c b/gdb/target.c
index 522bed8e939..c55c0878c6b 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2653,10 +2653,10 @@ target_resume (ptid_t scope_ptid, int step, enum gdb_signal signal)
current_inferior ()->top_target ()->resume (scope_ptid, step, signal);
registers_changed_ptid (curr_target, scope_ptid);
- /* We only set the internal executing state here. The user/frontend
- running state is set at a higher level. This also clears the
- thread's stop_pc as side effect. */
- set_executing (curr_target, scope_ptid, true);
+ /* We only set the internal state here. The user/frontend state is
+ set at a higher level. This also clears the thread's stop_pc as
+ side effect. */
+ set_internal_state (curr_target, scope_ptid, THREAD_INT_RUNNING);
clear_inline_frame_state (curr_target, scope_ptid);
if (target_can_async_p ())
@@ -3805,9 +3805,10 @@ target_pass_ctrlc (void)
for (thread_info *thr : inf->non_exited_threads ())
{
- /* A thread can be THREAD_STOPPED and executing, while
- running an infcall. */
- if (thr->state == THREAD_RUNNING || thr->executing ())
+ /* A thread can be externally THREAD_STOPPED and internally
+ THREAD_INT_RUNNING, while running an infcall. */
+ if (thr->state () == THREAD_RUNNING
+ || thr->internal_state () == THREAD_INT_RUNNING)
{
/* We can get here quite deep in target layers. Avoid
switching thread context or anything that would
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index abd7daff1f8..4463aaf37a6 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -151,7 +151,7 @@ struct non_exited_thread_filter
{
bool operator() (struct thread_info *thr) const
{
- return thr->state != THREAD_EXITED;
+ return thr->state () != THREAD_EXITED;
}
};
diff --git a/gdb/thread.c b/gdb/thread.c
index 0228027fb92..da45c263c92 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -223,23 +223,13 @@ set_thread_exited (thread_info *tp, std::optional<ULONGEST> exit_code,
if (thread_is_in_step_over_chain (tp))
global_thread_step_over_chain_remove (tp);
- if (tp->state != THREAD_EXITED)
+ if (tp->state () != THREAD_EXITED)
{
- process_stratum_target *proc_target = tp->inf->process_target ();
-
- /* Some targets unpush themselves from the inferior's target stack before
- clearing the inferior's thread list (which marks all threads as exited,
- and therefore leads to this function). In this case, the inferior's
- process target will be nullptr when we arrive here.
-
- See also the comment in inferior::unpush_target. */
- if (proc_target != nullptr)
- proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
-
notify_thread_exited (tp, exit_code, silent);
/* Tag it as exited. */
- tp->state = THREAD_EXITED;
+ tp->set_state (THREAD_EXITED);
+ tp->set_internal_state (THREAD_INT_EXITED);
/* Clear breakpoints, etc. associated with this thread. */
clear_thread_inferior_resources (tp);
@@ -373,34 +363,50 @@ thread_info::deletable () const
/* See gdbthread.h. */
void
-thread_info::set_executing (bool executing)
-{
- m_executing = executing;
- if (executing)
- this->clear_stop_pc ();
-}
-
-/* See gdbthread.h. */
-
-void
-thread_info::set_resumed (bool resumed)
+thread_info::set_internal_state (thread_int_state state)
{
- if (resumed == m_resumed)
+ if (m_internal_state == state)
return;
- process_stratum_target *proc_target = this->inf->process_target ();
-
- /* If we transition from resumed to not resumed, we might need to remove
- the thread from the resumed threads with pending statuses list. */
- if (!resumed)
- proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+ if (state == THREAD_INT_RUNNING)
+ this->clear_stop_pc ();
- m_resumed = resumed;
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS)
+ gdb_assert (this->has_pending_waitstatus ());
+ else if (state == THREAD_INT_RUNNING)
+ gdb_assert (!this->has_pending_waitstatus ());
- /* If we transition from not resumed to resumed, we might need to add
+ /* If we transition from resumed-pending-status to another state, we
+ might need to remove the thread from the resumed threads with
+ pending statuses list. Conversely, if we transition to
+ resumed-pending-status from another state, we might need to add
the thread to the resumed threads with pending statuses list. */
- if (resumed)
- proc_target->maybe_add_resumed_with_pending_wait_status (this);
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS
+ || m_internal_state == THREAD_INT_RESUMED_PENDING_STATUS)
+ {
+ process_stratum_target *proc_target = this->inf->process_target ();
+
+ /* We need the proc_target NULL checks below, because some
+ targets unpush themselves from the inferior's target stack
+ before clearing the inferior's thread list (which marks all
+ threads as exited, and therefore leads to this function). In
+ this case, the inferior's process target will be nullptr when
+ we arrive here. See also the comment in
+ inferior::unpush_target. */
+
+ if (state != THREAD_INT_RESUMED_PENDING_STATUS && proc_target != nullptr)
+ proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+
+ /* Note maybe_remove_resumed_with_pending_wait_status internally
+ reads this state. Thus it must be updated after the call
+ above, and before the call below. */
+ m_internal_state = state;
+
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS && proc_target != nullptr)
+ proc_target->maybe_add_resumed_with_pending_wait_status (this);
+ }
+ else
+ m_internal_state = state;
}
/* See gdbthread.h. */
@@ -409,12 +415,13 @@ void
thread_info::set_pending_waitstatus (const target_waitstatus &ws)
{
gdb_assert (!this->has_pending_waitstatus ());
+ /* Doesn't make sense to set a pending status on an exited or
+ running thread. */
+ gdb_assert (this->internal_state () == THREAD_INT_STOPPED
+ || this->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS);
m_suspend.waitstatus = ws;
m_suspend.waitstatus_pending_p = 1;
-
- process_stratum_target *proc_target = this->inf->process_target ();
- proc_target->maybe_add_resumed_with_pending_wait_status (this);
}
/* See gdbthread.h. */
@@ -424,9 +431,6 @@ thread_info::clear_pending_waitstatus ()
{
gdb_assert (this->has_pending_waitstatus ());
- process_stratum_target *proc_target = this->inf->process_target ();
- proc_target->maybe_remove_resumed_with_pending_wait_status (this);
-
m_suspend.waitstatus_pending_p = 0;
}
@@ -435,8 +439,8 @@ thread_info::clear_pending_waitstatus ()
void
thread_info::set_thread_options (gdb_thread_options thread_options)
{
- gdb_assert (this->state != THREAD_EXITED);
- gdb_assert (!this->executing ());
+ gdb_assert (this->state () != THREAD_EXITED);
+ gdb_assert (this->internal_state () == THREAD_INT_STOPPED);
if (m_thread_options == thread_options)
return;
@@ -682,30 +686,30 @@ any_thread_of_inferior (inferior *inf)
thread_info *
any_live_thread_of_inferior (inferior *inf)
{
- struct thread_info *curr_tp = NULL;
- struct thread_info *tp_executing = NULL;
+ thread_info *curr_tp = NULL;
+ thread_info *tp_running = NULL;
gdb_assert (inf != NULL && inf->pid != 0);
- /* Prefer the current thread if it's not executing. */
+ /* Prefer the current thread if it's stopped. */
if (inferior_ptid != null_ptid && current_inferior () == inf)
{
- /* If the current thread is dead, forget it. If it's not
- executing, use it. Otherwise, still choose it (below), but
- only if no other non-executing thread is found. */
+ /* If the current thread is dead, forget it. If it's stopped,
+ use it. Otherwise, still choose it (below), but only if no
+ other running thread is found. */
curr_tp = inferior_thread ();
- if (curr_tp->state == THREAD_EXITED)
+ if (curr_tp->internal_state () == THREAD_INT_EXITED)
curr_tp = NULL;
- else if (!curr_tp->executing ())
+ else if (curr_tp->internal_state () != THREAD_INT_RUNNING)
return curr_tp;
}
for (thread_info *tp : inf->non_exited_threads ())
{
- if (!tp->executing ())
+ if (tp->internal_state () != THREAD_INT_RUNNING)
return tp;
- tp_executing = tp;
+ tp_running = tp;
}
/* If both the current thread and all live threads are executing,
@@ -713,15 +717,15 @@ any_live_thread_of_inferior (inferior *inf)
if (curr_tp != NULL)
return curr_tp;
- /* Otherwise, just return an executing thread, if any. */
- return tp_executing;
+ /* Otherwise, just return a running thread, if any. */
+ return tp_running;
}
/* Return true if TP is an active thread. */
static bool
thread_alive (thread_info *tp)
{
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
return false;
/* Ensure we're looking at the right target stack. */
@@ -773,7 +777,7 @@ void
delete_exited_threads (void)
{
for (thread_info *tp : all_threads_safe ())
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
delete_thread (tp);
}
@@ -852,43 +856,6 @@ thread_change_ptid (process_stratum_target *targ,
gdb::observers::thread_ptid_changed.notify (targ, old_ptid, new_ptid);
}
-/* See gdbthread.h. */
-
-void
-set_resumed (process_stratum_target *targ, ptid_t ptid, bool resumed)
-{
- for (thread_info *tp : all_non_exited_threads (targ, ptid))
- tp->set_resumed (resumed);
-}
-
-/* Helper for set_running, that marks one thread either running or
- stopped. */
-
-static bool
-set_running_thread (struct thread_info *tp, bool running)
-{
- bool started = false;
-
- if (running && tp->state == THREAD_STOPPED)
- started = true;
- tp->state = running ? THREAD_RUNNING : THREAD_STOPPED;
-
- threads_debug_printf ("thread: %s, running? %d%s",
- tp->ptid.to_string ().c_str (), running,
- (started ? " (started)" : ""));
-
- if (!running)
- {
- /* If the thread is now marked stopped, remove it from
- the step-over queue, so that we don't try to resume
- it until the user wants it to. */
- if (thread_is_in_step_over_chain (tp))
- global_thread_step_over_chain_remove (tp);
- }
-
- return started;
-}
-
/* Notify interpreters and observers that the target was resumed. */
static void
@@ -905,41 +872,73 @@ notify_target_resumed (ptid_t ptid)
/* See gdbthread.h. */
-void
-thread_info::set_running (bool running)
+thread_state
+thread_info::set_state (thread_state state,
+ bool suppress_notification)
{
- if (set_running_thread (this, running))
- notify_target_resumed (this->ptid);
+ thread_state prev_state = m_state;
+
+ if (prev_state == state)
+ return prev_state;
+
+ threads_debug_printf ("thread: %s, %s -> %s",
+ this->ptid.to_string ().c_str (),
+ thread_state_string (m_state),
+ thread_state_string (state));
+
+ m_state = state;
+
+ switch (m_state)
+ {
+ case THREAD_EXITED:
+ break;
+
+ case THREAD_STOPPED:
+ /* If the thread is now marked stopped, remove it from
+ the step-over queue, so that we don't try to resume
+ it until the user wants it to. */
+ if (thread_is_in_step_over_chain (this))
+ global_thread_step_over_chain_remove (this);
+ break;
+
+ case THREAD_RUNNING:
+ if (!suppress_notification)
+ notify_target_resumed (this->ptid);
+ break;
+ }
+
+ return prev_state;
}
void
-set_running (process_stratum_target *targ, ptid_t ptid, bool running)
+set_state (process_stratum_target *targ, ptid_t ptid, thread_state state)
{
/* We try not to notify the observer if no thread has actually
- changed the running state -- merely to reduce the number of
+ changed its public state -- merely to reduce the number of
messages to the MI frontend. A frontend is supposed to handle
multiple *running notifications just fine. */
- bool any_started = false;
+ bool any_changed = false;
for (thread_info *tp : all_non_exited_threads (targ, ptid))
- if (set_running_thread (tp, running))
- any_started = true;
+ if (tp->set_state (state, true) != state)
+ any_changed = true;
- if (any_started)
+ if (any_changed && state == THREAD_RUNNING)
notify_target_resumed (ptid);
}
void
-set_executing (process_stratum_target *targ, ptid_t ptid, bool executing)
+set_internal_state (process_stratum_target *targ, ptid_t ptid,
+ thread_int_state state)
{
for (thread_info *tp : all_non_exited_threads (targ, ptid))
- tp->set_executing (executing);
+ tp->set_internal_state (state);
/* It only takes one running thread to spawn more threads. */
- if (executing)
+ if (state == THREAD_INT_RUNNING)
targ->threads_executing = true;
/* Only clear the flag if the caller is telling us everything is
- stopped. */
+ stopped or dead. */
else if (minus_one_ptid == ptid)
targ->threads_executing = false;
}
@@ -964,14 +963,41 @@ set_stop_requested (process_stratum_target *targ, ptid_t ptid, bool stop)
gdb::observers::thread_stop_requested.notify (ptid);
}
+/* Map INT_STATE to a user state. */
+
+static thread_state
+state_from_int_state (thread_int_state int_state)
+{
+ switch (int_state)
+ {
+ case THREAD_INT_RUNNING:
+ case THREAD_INT_RESUMED_PENDING_STATUS:
+ return THREAD_RUNNING;
+ case THREAD_INT_STOPPED:
+ return THREAD_STOPPED;
+ case THREAD_INT_EXITED:
+ return THREAD_EXITED;
+ }
+
+ gdb_assert_not_reached ("unknown thread_int_state: %d", int_state);
+}
+
+/* See gdbthread.h. Note this is a friend of thread_info so that it
+ can access the thread_info::set_state overload that lets us
+ suppress the target_resumed notification. */
+
void
finish_thread_state (process_stratum_target *targ, ptid_t ptid)
{
bool any_started = false;
for (thread_info *tp : all_non_exited_threads (targ, ptid))
- if (set_running_thread (tp, tp->executing ()))
- any_started = true;
+ {
+ thread_state new_state = state_from_int_state (tp->internal_state ());
+ thread_state prev_state = tp->set_state (new_state, true);
+ if (prev_state != new_state && new_state == THREAD_RUNNING)
+ any_started = true;
+ }
if (any_started)
notify_target_resumed (ptid);
@@ -989,7 +1015,7 @@ validate_registers_access (void)
thread_info *tp = inferior_thread ();
/* Don't try to read from a dead thread. */
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
error (_("The current thread has terminated"));
/* ... or from a spinning thread. FIXME: This isn't actually fully
@@ -997,7 +1023,7 @@ validate_registers_access (void)
at the prompt) when a thread is not executing for some internal
reason, but is marked running from the user's perspective. E.g.,
the thread is waiting for its turn in the step-over queue. */
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
{
/* If we are replaying with the record-full subsystem, even though
the thread is executing, it is always safe to read from it since
@@ -1017,11 +1043,11 @@ can_access_registers_thread (thread_info *thread)
return false;
/* Don't try to read from a dead thread. */
- if (thread->state == THREAD_EXITED)
+ if (thread->state () == THREAD_EXITED)
return false;
/* ... or from a spinning thread. FIXME: see validate_registers_access. */
- if (thread->executing ())
+ if (thread->internal_state () == THREAD_INT_RUNNING)
{
/* See validate_registers_access. */
if (!record_full_is_replaying ())
@@ -1105,14 +1131,14 @@ should_print_thread (const char *requested_threads,
return false;
}
- if (thr->state == THREAD_EXITED)
+ if (thr->state () == THREAD_EXITED)
return false;
- bool is_stopped = (thr->state == THREAD_STOPPED);
+ bool is_stopped = (thr->state () == THREAD_STOPPED);
if (opts.show_stopped_threads && is_stopped)
return true;
- bool is_running = (thr->state == THREAD_RUNNING);
+ bool is_running = (thr->state () == THREAD_RUNNING);
if (opts.show_running_threads && is_running)
return true;
@@ -1203,7 +1229,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads,
uiout->field_string ("target-id", thread_target_id_str (tp));
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
uiout->text ("(running)\n");
else
{
@@ -1219,7 +1245,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads,
{
const char *state = "stopped";
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
state = "running";
uiout->field_string ("state", state);
}
@@ -1330,7 +1356,7 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
for (inferior *inf : all_inferiors ())
for (thread_info *tp : inf->threads ())
{
- if (tp == current_thread && tp->state == THREAD_EXITED)
+ if (tp == current_thread && tp->state () == THREAD_EXITED)
current_exited = true;
print_thread (uiout, requested_threads, opts, global_ids, pid,
@@ -1495,7 +1521,7 @@ scoped_restore_current_thread::restore ()
changed, so we have to recheck it here. */
if (inferior_ptid != null_ptid
&& m_was_stopped
- && m_thread->state == THREAD_STOPPED
+ && m_thread->state () == THREAD_STOPPED
&& target_has_registers ()
&& target_has_stack ()
&& target_has_memory ())
@@ -1518,7 +1544,7 @@ scoped_restore_current_thread::scoped_restore_current_thread ()
{
m_thread = thread_info_ref::new_reference (inferior_thread ());
- m_was_stopped = m_thread->state == THREAD_STOPPED;
+ m_was_stopped = m_thread->state () == THREAD_STOPPED;
save_selected_frame (&m_selected_frame_id, &m_selected_frame_level);
}
}
@@ -1964,7 +1990,7 @@ thread_command (const char *tidstr, int from_tty)
{
struct thread_info *tp = inferior_thread ();
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
gdb_printf (_("[Current thread is %s (%s) (exited)]\n"),
print_thread_id (tp),
target_pid_to_str (inferior_ptid).c_str ());
@@ -2122,7 +2148,7 @@ print_selected_thread_frame (struct ui_out *uiout,
}
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
{
if (selection & USER_SELECTED_THREAD)
uiout->text ("(running)\n");
@@ -2167,7 +2193,7 @@ update_threads_executing (void)
for (thread_info *tp : inf->non_exited_threads ())
{
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
{
targ->threads_executing = true;
return;
@@ -2221,6 +2247,29 @@ thread_state_string (enum thread_state state)
gdb_assert_not_reached ("unknown thread state");
}
+/* See gdbthread.h. */
+
+const char *
+thread_int_state_string (thread_int_state state)
+{
+ switch (state)
+ {
+ case THREAD_INT_STOPPED:
+ return "INT_STOPPED";
+
+ case THREAD_INT_RUNNING:
+ return "INT_RUNNING";
+
+ case THREAD_INT_RESUMED_PENDING_STATUS:
+ return "INT_RESUMED_PENDING_STATUS";
+
+ case THREAD_INT_EXITED:
+ return "INT_EXITED";
+ }
+
+ gdb_assert_not_reached ("unknown thread internal state");
+}
+
/* Return a new value for the selected thread's id. Return a value of
0 if no thread is selected. If GLOBAL is true, return the thread's
global number. Otherwise return the per-inferior number. */
diff --git a/gdb/top.c b/gdb/top.c
index 6adef467b90..8b35d0467b6 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -586,7 +586,7 @@ execute_command (const char *p, int from_tty)
we just finished executing did not resume the inferior's execution.
If it did resume the inferior, we will do that check after
the inferior stopped. */
- if (has_stack_frames () && inferior_thread ()->state != THREAD_RUNNING)
+ if (has_stack_frames () && inferior_thread ()->state () != THREAD_RUNNING)
check_frame_language_change ();
cleanup_if_error.release ();
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 06/47] Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (4 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
` (41 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
In windows_nat_target::do_initial_windows_stuff, there's no point in
setting windows_process.current_event.dwProcessId. It's a nop, given
the following memset.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I2fe460341b598ad293ea60d5f702b10cefc30711
---
gdb/windows-nat.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 0fee4ad5866..092cb699a4a 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1783,7 +1783,6 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
windows_process.cygwin_load_start = 0;
windows_process.cygwin_load_end = 0;
#endif
- windows_process.current_event.dwProcessId = pid;
memset (&windows_process.current_event, 0,
sizeof (windows_process.current_event));
inf = current_inferior ();
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (5 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 06/47] Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:09 ` Tom Tromey
2026-04-06 19:44 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 08/47] Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT) Pedro Alves
` (40 subsequent siblings)
47 siblings, 2 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
current_process.dr needs to be per-thread for non-stop. Actually, it
doesn't even need to exist at all. We have x86_debug_reg_state
recording intent, and then the
cygwin_get_dr/cygwin_get_dr6/cygwin_get_dr7 functions are registered
as x86_dr_low_type vector functions, so they should return the current
value in the inferior's registers. See this comment in x86-dregs.c:
~~~
/* In non-stop/async, threads can be running while we change the
global dr_mirror (and friends). Say, we set a watchpoint, and
let threads resume. Now, say you delete the watchpoint, or
add/remove watchpoints such that dr_mirror changes while threads
are running. On targets that support non-stop,
inserting/deleting watchpoints updates the global dr_mirror only.
It does not update the real thread's debug registers; that's only
done prior to resume. Instead, 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. Now, say, a thread hit a watchpoint before
having been updated with the new dr_mirror contents, and we
haven't yet handled the corresponding SIGTRAP. If we trusted
dr_mirror below, we'd mistake the real trapped address (from the
last time we had updated debug registers in the thread) with
whatever was currently in dr_mirror. So to fix this, dr_mirror
always represents intention, what we _want_ threads to have in
debug registers. To get at the address and cause of the trap, we
need to read the state the thread still has in its debug
registers.
In sum, always get the current debug register values the current
thread has, instead of trusting the global mirror. If the thread
was running when we last changed watchpoints, the mirror no
longer represents what was set in this thread's debug
registers. */
~~~
This patch makes the Windows native target follow that model as well.
I don't understand why would windows_nat_target::resume want to call
SetThreadContext itself. That duplicates things as it is currently
worrying about setting the debug registers as well. windows_continue
also does that, and windows_nat_target::resume always calls it. So
this patch simplifies windows_nat_target::resume too.
Tromey pointed out that gdb/2388 mentioned in the code being removed
was moved to https://sourceware.org/bugzilla/show_bug.cgi?id=9493 in
the bugzilla migration. I tried the reproducer mentioned there, and
it still works correctly.
Change-Id: Id762d0faa7d5e788402f2ff5adad5352447a7526
---
gdb/nat/windows-nat.h | 1 +
gdb/windows-nat.c | 92 ++++++++++++++++++------------------------
gdbserver/win32-low.cc | 1 +
3 files changed, 42 insertions(+), 52 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 2f50742c810..806bd352705 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -150,6 +150,7 @@ struct windows_process_info
{
/* The process handle */
HANDLE handle = 0;
+ DWORD process_id = 0;
DWORD main_thread_id = 0;
enum gdb_signal last_sig = GDB_SIGNAL_0;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 092cb699a4a..fbe36102e2e 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -100,8 +100,6 @@ struct windows_per_inferior : public windows_process_info
void handle_unload_dll () override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
- uintptr_t dr[8] {};
-
int windows_initialization_done = 0;
std::vector<std::unique_ptr<windows_thread_info>> thread_list;
@@ -721,18 +719,6 @@ windows_nat_target::fetch_registers (struct regcache *regcache, int r)
{
context->ContextFlags = WindowsContext<decltype(context)>::all;
CHECK (get_thread_context (th->h, context));
- /* Copy dr values from that thread.
- But only if there were not modified since last stop.
- PR gdb/2388 */
- if (!th->debug_registers_changed)
- {
- windows_process.dr[0] = context->Dr0;
- windows_process.dr[1] = context->Dr1;
- windows_process.dr[2] = context->Dr2;
- windows_process.dr[3] = context->Dr3;
- windows_process.dr[6] = context->Dr6;
- windows_process.dr[7] = context->Dr7;
- }
});
th->reload_context = false;
@@ -1224,18 +1210,21 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
for (auto &th : windows_process.thread_list)
if (id == -1 || id == (int) th->tid)
{
+ struct x86_debug_reg_state *state
+ = x86_debug_reg_state (windows_process.process_id);
+
windows_process.with_context (th.get (), [&] (auto *context)
{
if (th->debug_registers_changed)
{
context->ContextFlags
|= WindowsContext<decltype(context)>::debug;
- context->Dr0 = windows_process.dr[0];
- context->Dr1 = windows_process.dr[1];
- context->Dr2 = windows_process.dr[2];
- context->Dr3 = windows_process.dr[3];
+ context->Dr0 = state->dr_mirror[0];
+ context->Dr1 = state->dr_mirror[1];
+ context->Dr2 = state->dr_mirror[2];
+ context->Dr3 = state->dr_mirror[3];
context->Dr6 = DR6_CLEAR_VALUE;
- context->Dr7 = windows_process.dr[7];
+ context->Dr7 = state->dr_control_mirror;
th->debug_registers_changed = false;
}
if (context->ContextFlags)
@@ -1374,22 +1363,6 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
context->EFlags |= FLAG_TRACE_BIT;
}
-
- if (context->ContextFlags)
- {
- if (th->debug_registers_changed)
- {
- context->Dr0 = windows_process.dr[0];
- context->Dr1 = windows_process.dr[1];
- context->Dr2 = windows_process.dr[2];
- context->Dr3 = windows_process.dr[3];
- context->Dr6 = DR6_CLEAR_VALUE;
- context->Dr7 = windows_process.dr[7];
- th->debug_registers_changed = false;
- }
- CHECK (set_thread_context (th->h, context));
- context->ContextFlags = 0;
- }
});
}
@@ -1770,19 +1743,15 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
void
windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
{
- int i;
struct inferior *inf;
windows_process.last_sig = GDB_SIGNAL_0;
windows_process.open_process_used = 0;
- for (i = 0;
- i < sizeof (windows_process.dr) / sizeof (windows_process.dr[0]);
- i++)
- windows_process.dr[i] = 0;
#ifdef __CYGWIN__
windows_process.cygwin_load_start = 0;
windows_process.cygwin_load_end = 0;
#endif
+ windows_process.process_id = pid;
memset (&windows_process.current_event, 0,
sizeof (windows_process.current_event));
inf = current_inferior ();
@@ -3205,7 +3174,6 @@ cygwin_set_dr (int i, CORE_ADDR addr)
{
if (i < 0 || i > 3)
internal_error (_("Invalid register %d in cygwin_set_dr.\n"), i);
- windows_process.dr[i] = addr;
for (auto &th : windows_process.thread_list)
th->debug_registers_changed = true;
@@ -3217,8 +3185,6 @@ cygwin_set_dr (int i, CORE_ADDR addr)
static void
cygwin_set_dr7 (unsigned long val)
{
- windows_process.dr[7] = (CORE_ADDR) val;
-
for (auto &th : windows_process.thread_list)
th->debug_registers_changed = true;
}
@@ -3228,26 +3194,48 @@ cygwin_set_dr7 (unsigned long val)
static CORE_ADDR
cygwin_get_dr (int i)
{
- return windows_process.dr[i];
+ windows_thread_info *th
+ = windows_process.thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+
+ return windows_process.with_context (th, [&] (auto *context) -> CORE_ADDR
+ {
+ gdb_assert (context->ContextFlags != 0);
+ switch (i)
+ {
+ case 0:
+ return context->Dr0;
+ case 1:
+ return context->Dr1;
+ case 2:
+ return context->Dr2;
+ case 3:
+ return context->Dr3;
+ case 6:
+ return context->Dr6;
+ case 7:
+ return context->Dr7;
+ };
+
+ gdb_assert_not_reached ("invalid x86 dr register number: %d", i);
+ });
}
-/* Get the value of the DR6 debug status register from the inferior.
- Here we just return the value stored in dr[6]
- by the last call to thread_rec for current_event.dwThreadId id. */
+/* Get the value of the DR6 debug status register from the
+ inferior. */
+
static unsigned long
cygwin_get_dr6 (void)
{
- return (unsigned long) windows_process.dr[6];
+ return cygwin_get_dr (6);
}
-/* Get the value of the DR7 debug status register from the inferior.
- Here we just return the value stored in dr[7] by the last call to
- thread_rec for current_event.dwThreadId id. */
+/* Get the value of the DR7 debug status register from the
+ inferior. */
static unsigned long
cygwin_get_dr7 (void)
{
- return (unsigned long) windows_process.dr[7];
+ return cygwin_get_dr (7);
}
/* Determine if the thread referenced by "ptid" is alive
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 89831de9d43..61f124337a5 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -296,6 +296,7 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
windows_process.last_sig = GDB_SIGNAL_0;
windows_process.handle = proch;
+ windows_process.process_id = pid;
windows_process.main_thread_id = 0;
windows_process.soft_interrupt_requested = 0;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 08/47] Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT)
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (6 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 09/47] Windows gdb: handle_output_debug_string return type Pedro Alves
` (39 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
The goal of the next few patches is to eliminate thread_rec
completely. This is the first patch in that effort.
thread_rec(DONT_INVALIDATE_CONTEXT) is really just a thread lookup
with no side effects, so this adds a find_thread function that lets
you do that.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ie486badce00e234b10caa478b066c34537103e3f
---
gdb/nat/windows-nat.c | 5 ++-
gdb/nat/windows-nat.h | 7 ++--
gdb/windows-nat.c | 74 +++++++++++++++++++++---------------------
gdbserver/win32-low.cc | 32 ++++++++++--------
gdbserver/win32-low.h | 1 +
5 files changed, 62 insertions(+), 57 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 7b0fbc53413..b68d99a2496 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -315,9 +315,8 @@ windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
if (named_thread_id == (DWORD) -1)
named_thread_id = current_event.dwThreadId;
- named_thread = thread_rec (ptid_t (current_event.dwProcessId,
- named_thread_id, 0),
- DONT_INVALIDATE_CONTEXT);
+ named_thread = find_thread (ptid_t (current_event.dwProcessId,
+ named_thread_id, 0));
if (named_thread != NULL)
{
int thread_name_len;
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 806bd352705..b0e51e93fd0 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -111,9 +111,6 @@ struct windows_thread_info
/* Possible values to pass to 'thread_rec'. */
enum thread_disposition_type
{
- /* Do not invalidate the thread's context, and do not suspend the
- thread. */
- DONT_INVALIDATE_CONTEXT,
/* Invalidate the context, but do not suspend the thread. */
DONT_SUSPEND,
/* Invalidate the context and suspend the thread. */
@@ -184,6 +181,10 @@ struct windows_process_info
bool ignore_first_breakpoint = false;
#endif
+ /* Find a thread record given a thread id.
+
+ This function must be supplied by the embedding application. */
+ virtual windows_thread_info *find_thread (ptid_t ptid) = 0;
/* Find a thread record given a thread id. THREAD_DISPOSITION
controls whether the thread is suspended, and whether the context
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index fbe36102e2e..5b00b341afa 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -93,6 +93,7 @@ struct windows_solib
struct windows_per_inferior : public windows_process_info
{
+ windows_thread_info *find_thread (ptid_t ptid) override;
windows_thread_info *thread_rec (ptid_t ptid,
thread_disposition_type disposition) override;
int handle_output_debug_string (struct target_waitstatus *ourstatus) override;
@@ -250,8 +251,7 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
bool stopped_by_sw_breakpoint () override
{
- windows_thread_info *th
- = windows_process.thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
return th->stopped_at_software_breakpoint;
}
@@ -508,34 +508,38 @@ windows_nat_target::wait_for_debug_event_main_thread (DEBUG_EVENT *event)
/* See nat/windows-nat.h. */
windows_thread_info *
-windows_per_inferior::thread_rec
- (ptid_t ptid, thread_disposition_type disposition)
+windows_per_inferior::find_thread (ptid_t ptid)
{
for (auto &th : thread_list)
if (th->tid == ptid.lwp ())
- {
- if (!th->suspended)
- {
- switch (disposition)
- {
- case DONT_INVALIDATE_CONTEXT:
- /* Nothing. */
- break;
- case INVALIDATE_CONTEXT:
- if (ptid.lwp () != current_event.dwThreadId)
- th->suspend ();
- th->reload_context = true;
- break;
- case DONT_SUSPEND:
- th->reload_context = true;
- th->suspended = -1;
- break;
- }
- }
- return th.get ();
- }
+ return th.get ();
+ return nullptr;
+}
+
+/* See nat/windows-nat.h. */
+
+windows_thread_info *
+windows_per_inferior::thread_rec (ptid_t ptid,
+ thread_disposition_type disposition)
+{
+ windows_thread_info *th = find_thread (ptid);
- return NULL;
+ if (th != nullptr && !th->suspended)
+ {
+ switch (disposition)
+ {
+ case INVALIDATE_CONTEXT:
+ if (ptid.lwp () != current_event.dwThreadId)
+ th->suspend ();
+ th->reload_context = true;
+ break;
+ case DONT_SUSPEND:
+ th->reload_context = true;
+ th->suspended = -1;
+ break;
+ }
+ }
+ return th;
}
/* Add a thread to the thread list.
@@ -554,7 +558,7 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
gdb_assert (ptid.lwp () != 0);
- if ((th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT)))
+ if ((th = windows_process.find_thread (ptid)))
return th;
CORE_ADDR base = (CORE_ADDR) (uintptr_t) tlb;
@@ -1131,7 +1135,7 @@ display_selectors (const char * args, int from_tty)
}
windows_thread_info *current_windows_thread
- = windows_process.thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+ = windows_process.find_thread (inferior_ptid);
if (!args)
{
@@ -1350,7 +1354,7 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
ptid.pid (), (unsigned) ptid.lwp (), step, sig);
/* Get context for currently selected thread. */
- th = windows_process.thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+ th = windows_process.find_thread (inferior_ptid);
if (th)
{
windows_process.with_context (th, [&] (auto *context)
@@ -3029,7 +3033,7 @@ windows_nat_target::get_tib_address (ptid_t ptid, CORE_ADDR *addr)
{
windows_thread_info *th;
- th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
+ th = windows_process.find_thread (ptid);
if (th == NULL)
return false;
@@ -3050,9 +3054,7 @@ windows_nat_target::get_ada_task_ptid (long lwp, ULONGEST thread)
const char *
windows_nat_target::thread_name (struct thread_info *thr)
{
- windows_thread_info *th
- = windows_process.thread_rec (thr->ptid,
- DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (thr->ptid);
return th->thread_name ();
}
@@ -3194,8 +3196,7 @@ cygwin_set_dr7 (unsigned long val)
static CORE_ADDR
cygwin_get_dr (int i)
{
- windows_thread_info *th
- = windows_process.thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
return windows_process.with_context (th, [&] (auto *context) -> CORE_ADDR
{
@@ -3247,8 +3248,7 @@ windows_nat_target::thread_alive (ptid_t ptid)
{
gdb_assert (ptid.lwp () != 0);
- windows_thread_info *th
- = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (ptid);
return WaitForSingleObject (th->h, 0) != WAIT_OBJECT_0;
}
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 61f124337a5..ea11c3b7795 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -123,6 +123,17 @@ win32_require_context (windows_thread_info *th)
/* See nat/windows-nat.h. */
+windows_thread_info *
+gdbserver_windows_process::find_thread (ptid_t ptid)
+{
+ thread_info *thread = find_thread_ptid (ptid);
+ if (thread == nullptr)
+ return nullptr;
+ return static_cast<windows_thread_info *> (thread->target_data ());
+}
+
+/* See nat/windows-nat.h. */
+
windows_thread_info *
gdbserver_windows_process::thread_rec
(ptid_t ptid, thread_disposition_type disposition)
@@ -132,8 +143,7 @@ gdbserver_windows_process::thread_rec
return NULL;
auto th = static_cast<windows_thread_info *> (thread->target_data ());
- if (disposition != DONT_INVALIDATE_CONTEXT)
- win32_require_context (th);
+ win32_require_context (th);
return th;
}
@@ -144,7 +154,7 @@ child_add_thread (DWORD pid, DWORD tid, HANDLE h, void *tlb)
windows_thread_info *th;
ptid_t ptid = ptid_t (pid, tid, 0);
- if ((th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT)))
+ if ((th = windows_process.find_thread (ptid)))
return th;
CORE_ADDR base = (CORE_ADDR) (uintptr_t) tlb;
@@ -798,7 +808,7 @@ win32_process_target::resume (thread_resume *resume_info, size_t n)
/* Get context for the currently selected thread. */
ptid = debug_event_ptid (&windows_process.current_event);
- th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
+ th = windows_process.find_thread (ptid);
if (th)
{
win32_prepare_to_resume (th);
@@ -944,8 +954,7 @@ maybe_adjust_pc ()
child_fetch_inferior_registers (regcache, -1);
windows_thread_info *th
- = windows_process.thread_rec (current_thread->id,
- DONT_INVALIDATE_CONTEXT);
+ = windows_process.find_thread (current_thread->id);
th->stopped_at_software_breakpoint = false;
if (windows_process.current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
@@ -1345,8 +1354,7 @@ win32_process_target::supports_get_tib_address ()
int
win32_process_target::get_tib_address (ptid_t ptid, CORE_ADDR *addr)
{
- windows_thread_info *th;
- th = windows_process.thread_rec (ptid, DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (ptid);
if (th == NULL)
return 0;
if (addr != NULL)
@@ -1366,9 +1374,7 @@ win32_process_target::sw_breakpoint_from_kind (int kind, int *size)
bool
win32_process_target::stopped_by_sw_breakpoint ()
{
- windows_thread_info *th
- = windows_process.thread_rec (current_thread->id,
- DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (current_thread->id);
return th == nullptr ? false : th->stopped_at_software_breakpoint;
}
@@ -1393,9 +1399,7 @@ win32_process_target::write_pc (struct regcache *regcache, CORE_ADDR pc)
const char *
win32_process_target::thread_name (ptid_t thread)
{
- windows_thread_info *th
- = windows_process.thread_rec (current_thread->id,
- DONT_INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (current_thread->id);
return th->thread_name ();
}
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index a76ed9fad3f..fb757da9718 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -181,6 +181,7 @@ 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;
windows_nat::windows_thread_info *thread_rec
(ptid_t ptid,
windows_nat::thread_disposition_type disposition) override;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 09/47] Windows gdb: handle_output_debug_string return type
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (7 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 08/47] Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT) Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 10/47] Windows gdb: Eliminate reload_context Pedro Alves
` (38 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
handle_output_debug_string returns a Windows thread id, so it should
return a DWORD instead of an int.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Icbd071a1a37de8a0fc8918bd13254a8d40311e32
---
gdb/nat/windows-nat.h | 2 +-
gdb/windows-nat.c | 16 ++++++++--------
gdbserver/win32-low.cc | 2 +-
gdbserver/win32-low.h | 2 +-
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index b0e51e93fd0..902508b0aea 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -202,7 +202,7 @@ struct windows_process_info
a Cygwin signal. Otherwise just print the string as a warning.
This function must be supplied by the embedding application. */
- virtual int handle_output_debug_string (struct target_waitstatus *ourstatus) = 0;
+ virtual DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) = 0;
/* Handle a DLL load event.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 5b00b341afa..e9389169ab3 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -96,7 +96,7 @@ struct windows_per_inferior : public windows_process_info
windows_thread_info *find_thread (ptid_t ptid) override;
windows_thread_info *thread_rec (ptid_t ptid,
thread_disposition_type disposition) override;
- int handle_output_debug_string (struct target_waitstatus *ourstatus) override;
+ DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll () override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
@@ -983,11 +983,11 @@ signal_event_command (const char *args, int from_tty)
/* See nat/windows-nat.h. */
-int
+DWORD
windows_per_inferior::handle_output_debug_string
(struct target_waitstatus *ourstatus)
{
- int retval = 0;
+ DWORD thread_id = 0;
gdb::unique_xmalloc_ptr<char> s
= (target_read_string
@@ -1028,19 +1028,19 @@ windows_per_inferior::handle_output_debug_string
if (gotasig)
{
ourstatus->set_stopped (gotasig);
- retval = strtoul (p, &p, 0);
- if (!retval)
- retval = current_event.dwThreadId;
+ thread_id = strtoul (p, &p, 0);
+ if (thread_id == 0)
+ thread_id = current_event.dwThreadId;
else
x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
}
DEBUG_EVENTS ("gdb: cygwin signal %d, thread 0x%x, CONTEXT @ %p",
- gotasig, retval, x);
+ gotasig, thread_id, x);
}
#endif
- return retval;
+ return thread_id;
}
static int
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index ea11c3b7795..2bf42f42350 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -619,7 +619,7 @@ win32_process_target::attach (unsigned long pid)
/* See nat/windows-nat.h. */
-int
+DWORD
gdbserver_windows_process::handle_output_debug_string
(struct target_waitstatus *ourstatus)
{
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index fb757da9718..b12c217c406 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -185,7 +185,7 @@ struct gdbserver_windows_process : public windows_nat::windows_process_info
windows_nat::windows_thread_info *thread_rec
(ptid_t ptid,
windows_nat::thread_disposition_type disposition) override;
- int handle_output_debug_string (struct target_waitstatus *ourstatus) override;
+ DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll () override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 10/47] Windows gdb: Eliminate reload_context
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (8 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 09/47] Windows gdb: handle_output_debug_string return type Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 11/47] Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls Pedro Alves
` (37 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
We don't need reload_context, because we can get the same information
out of th->context.ContextFlags. If ContextFlags is zero, then we
need to fetch the context out of the inferior thread. This is what
gdbserver does too.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ied566037c81383414c46c77713bdd1aec6377b23
---
gdb/nat/windows-nat.h | 4 ----
gdb/windows-nat.c | 49 +++++++++++++++++++++++--------------------
2 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 902508b0aea..71560930e84 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -90,10 +90,6 @@ struct windows_thread_info
the thread. */
bool debug_registers_changed = false;
- /* Nonzero if CONTEXT is invalidated and must be re-read from the
- inferior thread. */
- bool reload_context = false;
-
/* True if this thread is currently stopped at a software
breakpoint. This is used to offset the PC when needed. */
bool stopped_at_software_breakpoint = false;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index e9389169ab3..4a10cf962a9 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -101,6 +101,8 @@ struct windows_per_inferior : public windows_process_info
void handle_unload_dll () override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
+ void invalidate_context (windows_thread_info *th);
+
int windows_initialization_done = 0;
std::vector<std::unique_ptr<windows_thread_info>> thread_list;
@@ -516,7 +518,18 @@ windows_per_inferior::find_thread (ptid_t ptid)
return nullptr;
}
-/* See nat/windows-nat.h. */
+/* Invalidate TH's context. */
+
+void
+windows_per_inferior::invalidate_context (windows_thread_info *th)
+{
+#ifdef __x86_64__
+ if (windows_process.wow64_process)
+ th->wow64_context.ContextFlags = 0;
+ else
+#endif
+ th->context.ContextFlags = 0;
+}
windows_thread_info *
windows_per_inferior::thread_rec (ptid_t ptid,
@@ -531,11 +544,11 @@ windows_per_inferior::thread_rec (ptid_t ptid,
case INVALIDATE_CONTEXT:
if (ptid.lwp () != current_event.dwThreadId)
th->suspend ();
- th->reload_context = true;
+ invalidate_context (th);
break;
case DONT_SUSPEND:
- th->reload_context = true;
th->suspended = -1;
+ invalidate_context (th);
break;
}
}
@@ -637,18 +650,13 @@ windows_nat_target::delete_thread (ptid_t ptid, DWORD exit_code,
and supplies its value to the given regcache.
This function assumes that R is non-negative. A failed assertion
- is raised if that is not true.
-
- This function assumes that TH->RELOAD_CONTEXT is not set, meaning
- that the windows_thread_info has an up-to-date context. A failed
- assertion is raised if that assumption is violated. */
+ is raised if that is not true. */
static void
windows_fetch_one_register (struct regcache *regcache,
windows_thread_info *th, int r)
{
gdb_assert (r >= 0);
- gdb_assert (!th->reload_context);
char *context_ptr = windows_process.with_context (th, [] (auto *context)
{
@@ -717,16 +725,14 @@ windows_nat_target::fetch_registers (struct regcache *regcache, int r)
if (th == NULL)
return;
- if (th->reload_context)
- {
- windows_process.with_context (th, [&] (auto *context)
- {
- context->ContextFlags = WindowsContext<decltype(context)>::all;
- CHECK (get_thread_context (th->h, context));
- });
-
- th->reload_context = false;
- }
+ windows_process.with_context (th, [&] (auto *context)
+ {
+ if (context->ContextFlags == 0)
+ {
+ context->ContextFlags = WindowsContext<decltype(context)>::all;
+ CHECK (get_thread_context (th->h, context));
+ }
+ });
if (r < 0)
for (r = 0; r < gdbarch_num_regs (regcache->arch()); r++)
@@ -1446,10 +1452,7 @@ windows_nat_target::get_windows_debug_event
*ourstatus = stop->status;
ptid_t ptid (windows_process.current_event.dwProcessId, thread_id);
- windows_thread_info *th
- = windows_process.thread_rec (ptid, INVALIDATE_CONTEXT);
- th->reload_context = true;
-
+ windows_process.thread_rec (ptid, INVALIDATE_CONTEXT);
return ptid;
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 11/47] Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (9 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 10/47] Windows gdb: Eliminate reload_context Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 12/47] Windows gdb+gdbserver: Eliminate DONT_SUSPEND Pedro Alves
` (36 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Replace thread_rec(INVALIDATE_CONTEXT) calls with find_thread, and
invalidate_context / suspend calls in the spots that might need those.
I don't know why does the INVALIDATE_CONTEXT implementation in GDB
avoid suspending the event thread:
case INVALIDATE_CONTEXT:
if (ptid.lwp () != current_event.dwThreadId)
th->suspend ();
Checks for a global "current_event" get in the way of non-stop support
later in the series, as each thread will have its own "last debug
event". Regardless, it should be fine to suspend the event thread.
As a data point, the GDBserver implementation always suspends. So
this patch does not try to avoid suspending the event thread on the
native side either.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I8d2f0a749d23329956e62362a7007189902dddb5
---
gdb/nat/windows-nat.h | 2 --
gdb/windows-nat.c | 23 +++++++++--------------
gdbserver/win32-low.cc | 10 ++++------
3 files changed, 13 insertions(+), 22 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 71560930e84..f7f2f3e62a7 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -109,8 +109,6 @@ enum thread_disposition_type
{
/* Invalidate the context, but do not suspend the thread. */
DONT_SUSPEND,
- /* Invalidate the context and suspend the thread. */
- INVALIDATE_CONTEXT
};
/* A single pending stop. See "pending_stops" for more
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 4a10cf962a9..be62d2a6cad 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -541,11 +541,6 @@ windows_per_inferior::thread_rec (ptid_t ptid,
{
switch (disposition)
{
- case INVALIDATE_CONTEXT:
- if (ptid.lwp () != current_event.dwThreadId)
- th->suspend ();
- invalidate_context (th);
- break;
case DONT_SUSPEND:
th->suspended = -1;
invalidate_context (th);
@@ -717,8 +712,7 @@ windows_fetch_one_register (struct regcache *regcache,
void
windows_nat_target::fetch_registers (struct regcache *regcache, int r)
{
- windows_thread_info *th
- = windows_process.thread_rec (regcache->ptid (), INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (regcache->ptid ());
/* Check if TH exists. Windows sometimes uses a non-existent
thread id in its events. */
@@ -729,6 +723,7 @@ windows_nat_target::fetch_registers (struct regcache *regcache, int r)
{
if (context->ContextFlags == 0)
{
+ th->suspend ();
context->ContextFlags = WindowsContext<decltype(context)>::all;
CHECK (get_thread_context (th->h, context));
}
@@ -755,6 +750,7 @@ windows_store_one_register (const struct regcache *regcache,
char *context_ptr = windows_process.with_context (th, [] (auto *context)
{
+ gdb_assert (context->ContextFlags != 0);
return (char *) context;
});
@@ -789,8 +785,7 @@ windows_store_one_register (const struct regcache *regcache,
void
windows_nat_target::store_registers (struct regcache *regcache, int r)
{
- windows_thread_info *th
- = windows_process.thread_rec (regcache->ptid (), INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (regcache->ptid ());
/* Check if TH exists. Windows sometimes uses a non-existent
thread id in its events. */
@@ -1452,7 +1447,9 @@ windows_nat_target::get_windows_debug_event
*ourstatus = stop->status;
ptid_t ptid (windows_process.current_event.dwProcessId, thread_id);
- windows_process.thread_rec (ptid, INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+ if (th != nullptr)
+ windows_process.invalidate_context (th);
return ptid;
}
@@ -1673,8 +1670,7 @@ windows_nat_target::get_windows_debug_event
&& windows_process.windows_initialization_done)
{
ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
- windows_thread_info *th
- = windows_process.thread_rec (ptid, INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (ptid);
th->stopped_at_software_breakpoint = true;
th->pc_adjusted = false;
}
@@ -1712,8 +1708,7 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
&& ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
{
- windows_thread_info *th
- = windows_process.thread_rec (result, INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (result);
if (th != nullptr)
{
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 2bf42f42350..42026971037 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -443,9 +443,8 @@ static void
child_fetch_inferior_registers (struct regcache *regcache, int r)
{
int regno;
- windows_thread_info *th
- = windows_process.thread_rec (current_thread->id,
- INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (current_thread->id);
+ win32_require_context (th);
if (r == -1 || r > NUM_REGS)
child_fetch_inferior_registers (regcache, NUM_REGS);
else
@@ -459,9 +458,8 @@ static void
child_store_inferior_registers (struct regcache *regcache, int r)
{
int regno;
- windows_thread_info *th
- = windows_process.thread_rec (current_thread->id,
- INVALIDATE_CONTEXT);
+ windows_thread_info *th = windows_process.find_thread (current_thread->id);
+ win32_require_context (th);
if (r == -1 || r == 0 || r > NUM_REGS)
child_store_inferior_registers (regcache, NUM_REGS);
else
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 12/47] Windows gdb+gdbserver: Eliminate DONT_SUSPEND
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (10 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 11/47] Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 13/47] Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec Pedro Alves
` (35 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
There's a single call to thread_rec(DONT_SUSPEND), in
windows_process_info::handle_exception.
In GDB, the windows-nat.c thread_rec implementation avoids actually
calling SuspendThread on the event thread by doing:
th->suspended = -1;
I am not exactly sure why, but it kind of looks like it is done as an
optimization, avoiding a SuspendThread call? It is probably done for
the same reason as the code touched in the previous patch avoided
suspending the event thread.
This however gets in the way of non-stop mode, which will really want
to SuspendThread the event thread for DBG_REPLY_LATER.
In gdbserver's thread_rec implementation DONT_SUSPEND is ignored, and
thread_rec actually always suspends, which really suggests that
SuspendThread on the event thread is really not a problem. I really
can't imagine why it would be.
DONT_SUSPEND invalidates the thread's context, but there is no need to
invalidate the context when we get an event for a thread, because we
invalidate it when we previously resumed the thread.
So, we can just remove the thread_rec call from
windows_process_info::handle_exception. That's what this patch does.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I0f328542bda6d8268814ca1ee4ae7a478098ecf2
---
gdb/nat/windows-nat.c | 4 ----
gdb/nat/windows-nat.h | 2 --
gdb/windows-nat.c | 11 -----------
3 files changed, 17 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index b68d99a2496..40a58a0afd9 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -356,10 +356,6 @@ windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
memcpy (&siginfo_er, rec, sizeof siginfo_er);
- /* Record the context of the current thread. */
- thread_rec (ptid_t (current_event.dwProcessId, current_event.dwThreadId, 0),
- DONT_SUSPEND);
-
last_sig = GDB_SIGNAL_0;
switch (code)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index f7f2f3e62a7..4fb38e81631 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -107,8 +107,6 @@ struct windows_thread_info
/* Possible values to pass to 'thread_rec'. */
enum thread_disposition_type
{
- /* Invalidate the context, but do not suspend the thread. */
- DONT_SUSPEND,
};
/* A single pending stop. See "pending_stops" for more
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index be62d2a6cad..2425ec5f897 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -536,17 +536,6 @@ windows_per_inferior::thread_rec (ptid_t ptid,
thread_disposition_type disposition)
{
windows_thread_info *th = find_thread (ptid);
-
- if (th != nullptr && !th->suspended)
- {
- switch (disposition)
- {
- case DONT_SUSPEND:
- th->suspended = -1;
- invalidate_context (th);
- break;
- }
- }
return th;
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 13/47] Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (11 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 12/47] Windows gdb+gdbserver: Eliminate DONT_SUSPEND Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait Pedro Alves
` (34 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
After the previous patches, thread_rec is no longer called anywhere.
Delete it.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ib14e5807fc427e1c3c4a393a9ea7b36b6047a2d7
---
gdb/nat/windows-nat.h | 13 -------------
gdb/windows-nat.c | 10 ----------
gdbserver/win32-low.cc | 15 ---------------
gdbserver/win32-low.h | 3 ---
4 files changed, 41 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 4fb38e81631..9f1ecb4429b 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -104,11 +104,6 @@ struct windows_thread_info
};
-/* Possible values to pass to 'thread_rec'. */
-enum thread_disposition_type
-{
-};
-
/* A single pending stop. See "pending_stops" for more
information. */
struct pending_stop
@@ -178,14 +173,6 @@ struct windows_process_info
This function must be supplied by the embedding application. */
virtual windows_thread_info *find_thread (ptid_t ptid) = 0;
- /* Find a thread record given a thread id. THREAD_DISPOSITION
- controls whether the thread is suspended, and whether the context
- is invalidated.
-
- This function must be supplied by the embedding application. */
- virtual windows_thread_info *thread_rec (ptid_t ptid,
- thread_disposition_type disposition) = 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.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 2425ec5f897..f15b7520296 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -94,8 +94,6 @@ struct windows_solib
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- windows_thread_info *thread_rec (ptid_t ptid,
- thread_disposition_type disposition) override;
DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll () override;
@@ -531,14 +529,6 @@ windows_per_inferior::invalidate_context (windows_thread_info *th)
th->context.ContextFlags = 0;
}
-windows_thread_info *
-windows_per_inferior::thread_rec (ptid_t ptid,
- thread_disposition_type disposition)
-{
- windows_thread_info *th = find_thread (ptid);
- return th;
-}
-
/* Add a thread to the thread list.
PTID is the ptid of the thread to be added.
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 42026971037..c99248d37cb 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -132,21 +132,6 @@ gdbserver_windows_process::find_thread (ptid_t ptid)
return static_cast<windows_thread_info *> (thread->target_data ());
}
-/* See nat/windows-nat.h. */
-
-windows_thread_info *
-gdbserver_windows_process::thread_rec
- (ptid_t ptid, thread_disposition_type disposition)
-{
- thread_info *thread = find_thread_ptid (ptid);
- if (thread == NULL)
- return NULL;
-
- auto th = static_cast<windows_thread_info *> (thread->target_data ());
- win32_require_context (th);
- return th;
-}
-
/* Add a thread to the thread list. */
static windows_thread_info *
child_add_thread (DWORD pid, DWORD tid, HANDLE h, void *tlb)
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index b12c217c406..3c49ca7de9b 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -182,9 +182,6 @@ 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;
- windows_nat::windows_thread_info *thread_rec
- (ptid_t ptid,
- windows_nat::thread_disposition_type disposition) override;
DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) override;
void handle_load_dll (const char *dll_name, LPVOID base) override;
void handle_unload_dll () override;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (12 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 13/47] Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:16 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event Pedro Alves
` (33 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
The logic in windows_nat_target::wait, where we decide what to do
depending on the result from get_windows_debug_event is harder to
grasp than it looks.
It is not easy to tell what should happen when in async mode
get_windows_debug_event returns that there's no event to process.
And then, if get_windows_debug_event returns null_ptid /
TARGET_WAITKIND_SPURIOUS, then we need to issue a ContinueDebugEvent.
There's also this comment in windows_nat_target::wait, which we're not
really implementing today:
~~~~
/* We loop when we get a non-standard exception rather than return
with a SPURIOUS because resume can try and step or modify things,
which needs a current_thread->h. But some of these exceptions mark
the birth or death of threads, which mean that the current thread
isn't necessarily what you think it is. */
~~~~
This patch changes things a bit so that the code is more obvious:
- look at the status kind, instead of ptid_t.
- add an explicit early return case for no-event.
- add an explicit case for TARGET_WAITKIND_SPURIOUS.
- with those, we no longer need to handle the case of find_thread not
finding a thread, so we can drop one indentation level.
Change-Id: I76c41762e1f893a7ff23465856ccf6a44af1f0e7
---
gdb/windows-nat.c | 41 +++++++++++++++++++++++++----------------
1 file changed, 25 insertions(+), 16 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index f15b7520296..9b8bb66d700 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1661,7 +1661,10 @@ windows_nat_target::get_windows_debug_event
}
if (thread_id == 0)
- return null_ptid;
+ {
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
return ptid_t (windows_process.current_event.dwProcessId, thread_id, 0);
}
@@ -1682,27 +1685,33 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
{
ptid_t result = get_windows_debug_event (pid, ourstatus, options);
- if (result != null_ptid)
+ if ((options & TARGET_WNOHANG) != 0
+ && ourstatus->kind () == TARGET_WAITKIND_IGNORE)
+ return result;
+
+ if (ourstatus->kind () == TARGET_WAITKIND_SPURIOUS)
+ {
+ CHECK (windows_continue (DBG_CONTINUE,
+ windows_process.desired_stop_thread_id, 0));
+ }
+ else if (ourstatus->kind () != TARGET_WAITKIND_IGNORE)
{
if (ourstatus->kind () != TARGET_WAITKIND_EXITED
- && ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
+ && ourstatus->kind () != TARGET_WAITKIND_SIGNALLED)
{
windows_thread_info *th = windows_process.find_thread (result);
- if (th != nullptr)
+ th->stopped_at_software_breakpoint = false;
+ if (windows_process.current_event.dwDebugEventCode
+ == EXCEPTION_DEBUG_EVENT
+ && ((windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ == EXCEPTION_BREAKPOINT)
+ || (windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ == STATUS_WX86_BREAKPOINT))
+ && windows_process.windows_initialization_done)
{
- th->stopped_at_software_breakpoint = false;
- if (windows_process.current_event.dwDebugEventCode
- == EXCEPTION_DEBUG_EVENT
- && ((windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
- == EXCEPTION_BREAKPOINT)
- || (windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
- == STATUS_WX86_BREAKPOINT))
- && windows_process.windows_initialization_done)
- {
- th->stopped_at_software_breakpoint = true;
- th->pc_adjusted = false;
- }
+ th->stopped_at_software_breakpoint = true;
+ th->pc_adjusted = false;
}
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (13 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:17 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread Pedro Alves
` (32 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
The current code suspends a thread just before calling
GetThreadContext. You can only call GetThreadContext if the thread is
suspended. But, after WaitForDebugEvent, all threads are implicitly
suspended. So I don't think we even needed to call SuspendThread
explictly at all before our GetThreadContext calls.
However, suspending threads when we're about to present a stop to gdb
simplifies adding non-stop support later. This way, the windows
SuspendThread state corresponds to whether a thread is suspended or
resumed from the core's perspective. Curiously, I noticed that Wine's
winedbg does something similar:
https://github.com/wine-mirror/wine/blob/234943344f7495d1e072338f0e06fa2d5cbf0aa1/programs/winedbg/gdbproxy.c#L651
This makes it much easier to reason about a thread's suspend state,
and simplifies adding non-stop mode later on.
Change-Id: Ifd6889a8afc041fad33cd1c4500e38941da6781b
---
gdb/windows-nat.c | 12 +++++-------
gdbserver/win32-low.cc | 5 +++++
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 9b8bb66d700..61bccb365d0 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -702,7 +702,6 @@ windows_nat_target::fetch_registers (struct regcache *regcache, int r)
{
if (context->ContextFlags == 0)
{
- th->suspend ();
context->ContextFlags = WindowsContext<decltype(context)>::all;
CHECK (get_thread_context (th->h, context));
}
@@ -1229,12 +1228,6 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
th->resume ();
}
- else
- {
- /* When single-stepping a specific thread, other threads must
- be suspended. */
- th->suspend ();
- }
std::optional<unsigned> err;
do_synchronously ([&] ()
@@ -1713,6 +1706,11 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
th->stopped_at_software_breakpoint = true;
th->pc_adjusted = false;
}
+
+ /* All-stop, suspend all threads until they are
+ explicitly resumed. */
+ for (auto &thr : windows_process.thread_list)
+ thr->suspend ();
}
return result;
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index c99248d37cb..a23a41b9c02 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -1194,6 +1194,11 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
OUTMSG2 (("Child Stopped with signal = %d \n",
ourstatus->sig ()));
maybe_adjust_pc ();
+
+ /* All-stop, suspend all threads until they are explicitly
+ resumed. */
+ for_each_thread (suspend_one_thread);
+
return debug_event_ptid (&windows_process.current_event);
}
default:
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (14 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:18 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 17/47] Windows gdb: Introduce windows_continue_flags Pedro Alves
` (31 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
We have code using do_synchronously to call continue_last_debug_event,
and later patches in the series would need to add the same code in few
more places. Factor it out to a continue_last_debug_event_main_thread
function so these other places in future patches can just call it.
In v2:
- Fix context_str not used in the body of the method.
Change-Id: I945e668d2b3daeb9de968219925a7b3c7c7ce9ed
---
gdb/windows-nat.c | 47 +++++++++++++++++++++++++++++++----------------
1 file changed, 31 insertions(+), 16 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 61bccb365d0..6393f394804 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -347,6 +347,12 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
needed. */
void wait_for_debug_event_main_thread (DEBUG_EVENT *event);
+ /* This continues the last debug event, dispatching to the worker
+ thread as needed. */
+ void continue_last_debug_event_main_thread (const char *context_str,
+ DWORD continue_status,
+ bool last_call = false);
+
/* Force the process_thread thread to return from WaitForDebugEvent.
PROCESS_ALIVE is set to false if the inferior process exits while
we're trying to break out the process_thread thread. This can
@@ -505,6 +511,28 @@ windows_nat_target::wait_for_debug_event_main_thread (DEBUG_EVENT *event)
m_continued = false;
}
+void
+windows_nat_target::continue_last_debug_event_main_thread
+ (const char *context_str, DWORD continue_status, bool last_call)
+{
+ std::optional<unsigned> err;
+ do_synchronously ([&] ()
+ {
+ if (!continue_last_debug_event (continue_status, debug_events))
+ err = (unsigned) GetLastError ();
+
+ /* On the last call, do not block waiting for an event that will
+ never come. */
+ return !last_call;
+ });
+ if (err.has_value ())
+ throw_winerror_with_name (string_printf (_("ContinueDebugEvent failed: %s"),
+ context_str).c_str (),
+ *err);
+
+ m_continued = !last_call;
+}
+
/* See nat/windows-nat.h. */
windows_thread_info *
@@ -1229,22 +1257,9 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
th->resume ();
}
- std::optional<unsigned> err;
- do_synchronously ([&] ()
- {
- if (!continue_last_debug_event (continue_status, debug_events))
- err = (unsigned) GetLastError ();
- /* On the last call, do not block waiting for an event that will
- never come. */
- return !last_call;
- });
-
- if (err.has_value ())
- throw_winerror_with_name (_("Failed to resume program execution"
- " - ContinueDebugEvent failed"),
- *err);
-
- m_continued = !last_call;
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status,
+ last_call);
return TRUE;
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 17/47] Windows gdb: Introduce windows_continue_flags
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (15 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 18/47] Windows gdb: Factor code out of windows_nat_target::windows_continue Pedro Alves
` (30 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
windows_continue already has two boolean parameters:
(..., int killed, bool last_call = false)
A patch later in the series would need a third. Instead, convert
windows_continue to use an optional enum-flags parameter instead of
multiple booleans.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I17c4d8a12b662190f972c380f838cb3317bd2e1e
---
gdb/windows-nat.c | 46 ++++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 16 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 6393f394804..778cd8e348f 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -91,6 +91,22 @@ struct windows_solib
std::string name;
};
+/* Flags that can be passed to windows_continue. */
+
+enum windows_continue_flag
+ {
+ /* This means we have killed the inferior, so windows_continue
+ should ignore weird errors due to threads shutting down. */
+ WCONT_KILLED = 1,
+
+ /* This means we expect this windows_continue call to be the last
+ call to continue the inferior -- we are either mourning it or
+ detaching. */
+ WCONT_LAST_CALL = 2,
+ };
+
+DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
+
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
@@ -325,8 +341,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process ();
- BOOL windows_continue (DWORD continue_status, int id, int killed,
- bool last_call = false);
+ BOOL windows_continue (DWORD continue_status, int id,
+ windows_continue_flags cont_flags = 0);
/* Helper function to start process_thread. */
static DWORD WINAPI process_thread_starter (LPVOID self);
@@ -1198,14 +1214,12 @@ windows_per_inferior::handle_access_violation
}
/* Resume thread specified by ID, or all artificially suspended
- threads, if we are continuing execution. KILLED non-zero means we
- have killed the inferior, so we should ignore weird errors due to
- threads shutting down. LAST_CALL is true if we expect this to be
- the last call to continue the inferior -- we are either mourning it
- or detaching. */
+ threads, if we are continuing execution. See description of
+ windows_continue_flags for CONT_FLAGS. */
+
BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
- int killed, bool last_call)
+ windows_continue_flags cont_flags)
{
windows_process.desired_stop_thread_id = id;
@@ -1247,7 +1261,7 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
{
BOOL status = set_thread_context (th->h, context);
- if (!killed)
+ if ((cont_flags & WCONT_KILLED) == 0)
CHECK (status);
}
context->ContextFlags = 0;
@@ -1259,7 +1273,7 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
continue_last_debug_event_main_thread
(_("Failed to resume program execution"), continue_status,
- last_call);
+ cont_flags & WCONT_LAST_CALL);
return TRUE;
}
@@ -1362,9 +1376,9 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
Otherwise complain. */
if (resume_all)
- windows_continue (continue_status, -1, 0);
+ windows_continue (continue_status, -1);
else
- windows_continue (continue_status, ptid.lwp (), 0);
+ windows_continue (continue_status, ptid.lwp ());
}
/* Interrupt the inferior. */
@@ -2072,7 +2086,7 @@ windows_nat_target::detach (inferior *inf, int from_tty)
if (m_continued)
break_out_process_thread (process_alive);
- windows_continue (DBG_CONTINUE, -1, 0, true);
+ windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
std::optional<unsigned> err;
if (process_alive)
@@ -2820,13 +2834,13 @@ windows_nat_target::create_inferior (const char *exec_file,
do_initial_windows_stuff (pi.dwProcessId, 0);
- /* windows_continue (DBG_CONTINUE, -1, 0); */
+ /* windows_continue (DBG_CONTINUE, -1); */
}
void
windows_nat_target::mourn_inferior ()
{
- (void) windows_continue (DBG_CONTINUE, -1, 0, true);
+ windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
x86_cleanup_dregs();
if (windows_process.open_process_used)
{
@@ -2884,7 +2898,7 @@ windows_nat_target::kill ()
for (;;)
{
- if (!windows_continue (DBG_CONTINUE, -1, 1))
+ if (!windows_continue (DBG_CONTINUE, -1, WCONT_KILLED))
break;
wait_for_debug_event_main_thread (&windows_process.current_event);
if (windows_process.current_event.dwDebugEventCode
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 18/47] Windows gdb: Factor code out of windows_nat_target::windows_continue
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (16 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 17/47] Windows gdb: Introduce windows_continue_flags Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 19/47] Windows gdb: Pending stop and current_event Pedro Alves
` (29 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This factors some code out of windows_nat_target::windows_continue
into a new windows_continue_one function. This will make the
following patch easier to read (as well as the resulting code itself).
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I14a0386b1b8b03015e86273060af173b5130e375
---
gdb/windows-nat.c | 78 +++++++++++++++++++++++++----------------------
1 file changed, 42 insertions(+), 36 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 778cd8e348f..1f6293aa8c0 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -117,6 +117,9 @@ struct windows_per_inferior : public windows_process_info
void invalidate_context (windows_thread_info *th);
+ void continue_one_thread (windows_thread_info *th,
+ windows_continue_flags cont_flags);
+
int windows_initialization_done = 0;
std::vector<std::unique_ptr<windows_thread_info>> thread_list;
@@ -1213,6 +1216,44 @@ windows_per_inferior::handle_access_violation
return false;
}
+void
+windows_per_inferior::continue_one_thread (windows_thread_info *th,
+ windows_continue_flags cont_flags)
+{
+ struct x86_debug_reg_state *state = x86_debug_reg_state (process_id);
+
+ windows_process.with_context (th, [&] (auto *context)
+ {
+ if (th->debug_registers_changed)
+ {
+ context->ContextFlags |= WindowsContext<decltype(context)>::debug;
+ context->Dr0 = state->dr_mirror[0];
+ context->Dr1 = state->dr_mirror[1];
+ context->Dr2 = state->dr_mirror[2];
+ context->Dr3 = state->dr_mirror[3];
+ context->Dr6 = DR6_CLEAR_VALUE;
+ context->Dr7 = state->dr_control_mirror;
+ th->debug_registers_changed = false;
+ }
+ if (context->ContextFlags)
+ {
+ DWORD ec = 0;
+
+ if (GetExitCodeThread (th->h, &ec)
+ && ec == STILL_ACTIVE)
+ {
+ BOOL status = set_thread_context (th->h, context);
+
+ if ((cont_flags & WCONT_KILLED) == 0)
+ CHECK (status);
+ }
+ context->ContextFlags = 0;
+ }
+ });
+
+ th->resume ();
+}
+
/* Resume thread specified by ID, or all artificially suspended
threads, if we are continuing execution. See description of
windows_continue_flags for CONT_FLAGS. */
@@ -1234,42 +1275,7 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
for (auto &th : windows_process.thread_list)
if (id == -1 || id == (int) th->tid)
- {
- struct x86_debug_reg_state *state
- = x86_debug_reg_state (windows_process.process_id);
-
- windows_process.with_context (th.get (), [&] (auto *context)
- {
- if (th->debug_registers_changed)
- {
- context->ContextFlags
- |= WindowsContext<decltype(context)>::debug;
- context->Dr0 = state->dr_mirror[0];
- context->Dr1 = state->dr_mirror[1];
- context->Dr2 = state->dr_mirror[2];
- context->Dr3 = state->dr_mirror[3];
- context->Dr6 = DR6_CLEAR_VALUE;
- context->Dr7 = state->dr_control_mirror;
- th->debug_registers_changed = false;
- }
- if (context->ContextFlags)
- {
- DWORD ec = 0;
-
- if (GetExitCodeThread (th->h, &ec)
- && ec == STILL_ACTIVE)
- {
- BOOL status = set_thread_context (th->h, context);
-
- if ((cont_flags & WCONT_KILLED) == 0)
- CHECK (status);
- }
- context->ContextFlags = 0;
- }
- });
-
- th->resume ();
- }
+ windows_process.continue_one_thread (th.get (), cont_flags);
continue_last_debug_event_main_thread
(_("Failed to resume program execution"), continue_status,
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 19/47] Windows gdb: Pending stop and current_event
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (17 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 18/47] Windows gdb: Factor code out of windows_nat_target::windows_continue Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops Pedro Alves
` (28 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
I noticed that windows_nat_target::get_windows_debug_event does not
copy the event recorded in pending stop to
windows_process.current_event. This seems like an oversight. The
equivalent code in gdbserver/win32-low.cc does copy it.
This change will become moot later in the series, but I figure its
still clearer to correct the buglet as preparatory patch.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ic8935d854cf67a3a3c4edcbc1a1e8957b800d907
---
gdb/windows-nat.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 1f6293aa8c0..2bed29edc50 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1452,6 +1452,7 @@ windows_nat_target::get_windows_debug_event
{
thread_id = stop->thread_id;
*ourstatus = stop->status;
+ windows_process.current_event = stop->event;
ptid_t ptid (windows_process.current_event.dwProcessId, thread_id);
windows_thread_info *th = windows_process.find_thread (ptid);
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (18 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 19/47] Windows gdb: Pending stop and current_event Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-30 20:41 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid Pedro Alves
` (27 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
windows_process.desired_stop_thread_id doesn't work for non-stop, as
in that case every thread will have its own independent
WaitForDebugEvent event.
Instead, detect whether we have been reported a stop that was not
supposed to be reported by simply checking whether the thread that is
reporting the event is suspended. This is now easilly possible since
each thread's suspend state is kept in sync with whether infrun wants
the thread executing or not.
windows_process.desired_stop_thread_id was also used as thread to pass
to windows_continue. However, we don't really need that either.
windows_continue is used to update the thread's registers, unsuspend
them, and then finally call ContinueDebugEvent. In most cases, we
only need the ContinueDebugEvent step, so we can convert the
windows_continue calls to continue_last_debug_event_main_thread calls.
The exception is when we see a thread creation event -- in that case,
we need to update the debug registers of the new thread. We can use
continue_one_thread for that.
Since the pending stop is now stored in windows_thread_info,
get_windows_debug_event needs to avoid reaching the bottom code if
there's no thread associated with the event anymore (i.e.,
EXIT_THREAD_DEBUG_EVENT / EXIT_PROCESS_DEBUG_EVENT).
I considered whether it would be possible to keep the pending_stop
handling code shared in gdb/nat/windows-nat.c, in this patch and
throughout the series, but I conclused that it isn't worth it, until
gdbserver is taught about async and non-stop as well.
The pending_stop struct will eventually be eliminated later down the
series.
Change-Id: Ib7c8e8d16edc0900b7c411976c5d70cf93931c1c
---
gdb/nat/windows-nat.c | 51 -------------------
gdb/nat/windows-nat.h | 71 ++++++++++----------------
gdb/windows-nat.c | 112 +++++++++++++++++++++++------------------
gdbserver/win32-low.cc | 55 ++++++++++++--------
4 files changed, 125 insertions(+), 164 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 40a58a0afd9..fbb48f8e3ee 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -637,57 +637,6 @@ windows_process_info::add_all_dlls ()
/* See nat/windows-nat.h. */
-bool
-windows_process_info::matching_pending_stop (bool debug_events)
-{
- /* If there are pending stops, and we might plausibly hit one of
- them, we don't want to actually continue the inferior -- we just
- want to report the stop. In this case, we just pretend to
- continue. See the comment by the definition of "pending_stops"
- for details on why this is needed. */
- for (const auto &item : pending_stops)
- {
- if (desired_stop_thread_id == -1
- || desired_stop_thread_id == item.thread_id)
- {
- DEBUG_EVENTS ("pending stop anticipated, desired=0x%x, item=0x%x",
- desired_stop_thread_id, item.thread_id);
- return true;
- }
- }
-
- return false;
-}
-
-/* See nat/windows-nat.h. */
-
-std::optional<pending_stop>
-windows_process_info::fetch_pending_stop (bool debug_events)
-{
- std::optional<pending_stop> result;
- for (auto iter = pending_stops.begin ();
- iter != pending_stops.end ();
- ++iter)
- {
- if (desired_stop_thread_id == -1
- || desired_stop_thread_id == iter->thread_id)
- {
- result = *iter;
- current_event = iter->event;
-
- DEBUG_EVENTS ("pending stop found in 0x%x (desired=0x%x)",
- iter->thread_id, desired_stop_thread_id);
-
- pending_stops.erase (iter);
- break;
- }
- }
-
- return result;
-}
-
-/* See nat/windows-nat.h. */
-
BOOL
continue_last_debug_event (DWORD continue_status, bool debug_events)
{
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 9f1ecb4429b..fec6becb594 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -38,6 +38,21 @@
namespace windows_nat
{
+/* Info about a potential pending stop. Each thread holds one of
+ these. See "windows_thread_info::pending_stop" for more
+ information. */
+struct pending_stop
+{
+ /* The target waitstatus we computed. TARGET_WAITKIND_IGNORE if the
+ thread does not have a pending stop. */
+ target_waitstatus status;
+
+ /* The event. A few fields of this can be referenced after a stop,
+ and it seemed simplest to store the entire event. */
+ DEBUG_EVENT event;
+};
+
+
/* Thread information structure used to track extra information about
each thread. */
struct windows_thread_info
@@ -77,6 +92,18 @@ struct windows_thread_info
was not. */
int suspended = 0;
+/* Info about a potential pending stop.
+
+ Sometimes, Windows will report a stop on a thread that has been
+ ostensibly suspended. We believe what happens here is that two
+ threads hit a breakpoint simultaneously, and the Windows kernel
+ queues the stop events. However, this can result in the strange
+ effect of trying to single step thread A -- leaving all other
+ threads suspended -- and then seeing a stop in thread B. To handle
+ this scenario, we queue all such "pending" stops here, and then
+ process them once the step has completed. See PR gdb/22992. */
+ struct pending_stop pending_stop {};
+
/* The context of the thread, including any manipulations. */
union
{
@@ -103,22 +130,6 @@ struct windows_thread_info
gdb::unique_xmalloc_ptr<char> name;
};
-
-/* A single pending stop. See "pending_stops" for more
- information. */
-struct pending_stop
-{
- /* The thread id. */
- DWORD thread_id;
-
- /* The target waitstatus we computed. */
- target_waitstatus status;
-
- /* The event. A few fields of this can be referenced after a stop,
- and it seemed simplest to store the entire event. */
- DEBUG_EVENT event;
-};
-
enum handle_exception_result
{
HANDLE_EXCEPTION_UNHANDLED = 0,
@@ -142,22 +153,6 @@ struct windows_process_info
stop. */
DEBUG_EVENT current_event {};
- /* The ID of the thread for which we anticipate a stop event.
- Normally this is -1, meaning we'll accept an event in any
- thread. */
- DWORD desired_stop_thread_id = -1;
-
- /* A vector of pending stops. Sometimes, Windows will report a stop
- on a thread that has been ostensibly suspended. We believe what
- happens here is that two threads hit a breakpoint simultaneously,
- and the Windows kernel queues the stop events. However, this can
- result in the strange effect of trying to single step thread A --
- leaving all other threads suspended -- and then seeing a stop in
- thread B. To handle this scenario, we queue all such "pending"
- stops here, and then process them once the step has completed. See
- PR gdb/22992. */
- std::vector<pending_stop> pending_stops;
-
/* Contents of $_siginfo */
EXCEPTION_RECORD siginfo_er {};
@@ -223,18 +218,6 @@ struct windows_process_info
void add_all_dlls ();
- /* Return true if there is a pending stop matching
- desired_stop_thread_id. If DEBUG_EVENTS is true, logging will be
- enabled. */
-
- bool matching_pending_stop (bool debug_events);
-
- /* See if a pending stop matches DESIRED_STOP_THREAD_ID. If so,
- remove it from the list of pending stops, set 'current_event', and
- return it. Otherwise, return an empty optional. */
-
- std::optional<pending_stop> fetch_pending_stop (bool debug_events);
-
const char *pid_to_exec_file (int);
template<typename Function>
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 2bed29edc50..f86e6ae9a28 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1262,15 +1262,22 @@ BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- windows_process.desired_stop_thread_id = id;
-
- if (windows_process.matching_pending_stop (debug_events))
+ for (auto &th : windows_process.thread_list)
{
- /* 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 ((id == -1 || id == (int) th->tid)
+ && !th->suspended
+ && th->pending_stop.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;
+ }
}
for (auto &th : windows_process.thread_list)
@@ -1444,21 +1451,24 @@ windows_nat_target::get_windows_debug_event
DWORD thread_id = 0;
/* If there is a relevant pending stop, report it now. See the
- comment by the definition of "pending_stops" for details on why
- this is needed. */
- std::optional<pending_stop> stop
- = windows_process.fetch_pending_stop (debug_events);
- if (stop.has_value ())
+ comment by the definition of "windows_thread_info::pending_stop"
+ for details on why this is needed. */
+ for (auto &th : windows_process.thread_list)
{
- thread_id = stop->thread_id;
- *ourstatus = stop->status;
- windows_process.current_event = stop->event;
+ if (!th->suspended
+ && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
+
+ thread_id = th->tid;
+ *ourstatus = th->pending_stop.status;
+ th->pending_stop.status.set_ignore ();
+ windows_process.current_event = th->pending_stop.event;
- ptid_t ptid (windows_process.current_event.dwProcessId, thread_id);
- windows_thread_info *th = windows_process.find_thread (ptid);
- if (th != nullptr)
- windows_process.invalidate_context (th);
- return ptid;
+ ptid_t ptid (windows_process.process_id, thread_id);
+ windows_process.invalidate_context (th.get ());
+ return ptid;
+ }
}
windows_process.last_sig = GDB_SIGNAL_0;
@@ -1500,12 +1510,18 @@ windows_nat_target::get_windows_debug_event
}
/* Record the existence of this thread. */
thread_id = current_event->dwThreadId;
- add_thread
- (ptid_t (current_event->dwProcessId, current_event->dwThreadId, 0),
- current_event->u.CreateThread.hThread,
- current_event->u.CreateThread.lpThreadLocalBase,
- false /* main_thread_p */);
+ {
+ windows_thread_info *th
+ = (add_thread
+ (ptid_t (current_event->dwProcessId, current_event->dwThreadId, 0),
+ current_event->u.CreateThread.hThread,
+ current_event->u.CreateThread.lpThreadLocalBase,
+ false /* main_thread_p */));
+
+ /* This updates debug registers if necessary. */
+ windows_process.continue_one_thread (th, 0);
+ }
break;
case EXIT_THREAD_DEBUG_EVENT:
@@ -1517,6 +1533,7 @@ windows_nat_target::get_windows_debug_event
current_event->dwThreadId, 0),
current_event->u.ExitThread.dwExitCode,
false /* main_thread_p */);
+ thread_id = 0;
break;
case CREATE_PROCESS_DEBUG_EVENT:
@@ -1567,8 +1584,7 @@ windows_nat_target::get_windows_debug_event
ourstatus->set_exited (exit_status);
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
-
- thread_id = current_event->dwThreadId;
+ return ptid_t (current_event->dwProcessId);
}
break;
@@ -1658,17 +1674,22 @@ windows_nat_target::get_windows_debug_event
if (!thread_id || windows_process.saw_create != 1)
{
- CHECK (windows_continue (continue_status,
- windows_process.desired_stop_thread_id, 0));
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status);
+ ourstatus->set_ignore ();
+ return null_ptid;
}
- else if (windows_process.desired_stop_thread_id != -1
- && windows_process.desired_stop_thread_id != thread_id)
+
+ const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+
+ if (th->suspended)
{
/* Pending stop. See the comment by the definition of
"pending_stops" for details on why this is needed. */
DEBUG_EVENTS ("get_windows_debug_event - "
- "unexpected stop in 0x%x (expecting 0x%x)",
- thread_id, windows_process.desired_stop_thread_id);
+ "unexpected stop in suspended thread 0x%x",
+ thread_id);
if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
&& ((current_event->u.Exception.ExceptionRecord.ExceptionCode
@@ -1677,24 +1698,19 @@ windows_nat_target::get_windows_debug_event
== STATUS_WX86_BREAKPOINT))
&& windows_process.windows_initialization_done)
{
- ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
- windows_thread_info *th = windows_process.find_thread (ptid);
th->stopped_at_software_breakpoint = true;
th->pc_adjusted = false;
}
- windows_process.pending_stops.push_back
- ({thread_id, *ourstatus, windows_process.current_event});
- thread_id = 0;
- CHECK (windows_continue (continue_status,
- windows_process.desired_stop_thread_id, 0));
- }
-
- if (thread_id == 0)
- {
+ th->pending_stop.status = *ourstatus;
+ th->pending_stop.event = *current_event;
ourstatus->set_ignore ();
+
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status);
return null_ptid;
}
- return ptid_t (windows_process.current_event.dwProcessId, thread_id, 0);
+
+ return ptid;
}
/* Wait for interesting events to occur in the target process. */
@@ -1720,8 +1736,8 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
if (ourstatus->kind () == TARGET_WAITKIND_SPURIOUS)
{
- CHECK (windows_continue (DBG_CONTINUE,
- windows_process.desired_stop_thread_id, 0));
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), DBG_CONTINUE);
}
else if (ourstatus->kind () != TARGET_WAITKIND_IGNORE)
{
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index a23a41b9c02..32464024722 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -408,9 +408,15 @@ continue_one_thread (thread_info *thread, int thread_id)
static BOOL
child_continue (DWORD continue_status, int thread_id)
{
- windows_process.desired_stop_thread_id = thread_id;
- if (windows_process.matching_pending_stop (debug_threads))
- return TRUE;
+ process_info *proc = find_process_pid (windows_process.process_id);
+ for (thread_info &thread : proc->thread_list ())
+ {
+ auto *th = static_cast<windows_thread_info *> (thread.target_data ());
+ if ((thread_id == -1 || thread_id == (int) th->tid)
+ && !th->suspended
+ && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ return TRUE;
+ }
/* The inferior will only continue after the ContinueDebugEvent
call. */
@@ -980,15 +986,21 @@ get_child_debug_event (DWORD *continue_status,
windows_process.attaching = 0;
{
- std::optional<pending_stop> stop
- = windows_process.fetch_pending_stop (debug_threads);
- if (stop.has_value ())
+ process_info *proc = find_process_pid (windows_process.process_id);
+ for (thread_info &thread : proc->thread_list ())
{
- *ourstatus = stop->status;
- windows_process.current_event = stop->event;
- ptid = debug_event_ptid (&windows_process.current_event);
- switch_to_thread (find_thread_ptid (ptid));
- return 1;
+ auto *th = static_cast<windows_thread_info *> (thread.target_data ());
+
+ if (!th->suspended
+ && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ {
+ *ourstatus = th->pending_stop.status;
+ th->pending_stop.status.set_ignore ();
+ windows_process.current_event = th->pending_stop.event;
+ ptid = debug_event_ptid (&windows_process.current_event);
+ switch_to_thread (find_thread_ptid (ptid));
+ return 1;
+ }
}
/* Keep the wait time low enough for comfortable remote
@@ -1080,7 +1092,7 @@ get_child_debug_event (DWORD *continue_status,
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
}
- child_continue (DBG_CONTINUE, windows_process.desired_stop_thread_id);
+ continue_last_debug_event (DBG_CONTINUE, debug_threads);
break;
case LOAD_DLL_DEBUG_EVENT:
@@ -1137,17 +1149,19 @@ get_child_debug_event (DWORD *continue_status,
ptid = debug_event_ptid (&windows_process.current_event);
- if (windows_process.desired_stop_thread_id != -1
- && windows_process.desired_stop_thread_id != ptid.lwp ())
+ windows_thread_info *th = windows_process.find_thread (ptid);
+
+ if (th != nullptr && th->suspended)
{
/* Pending stop. See the comment by the definition of
- "pending_stops" for details on why this is needed. */
+ "windows_thread_info::pending_stop" for details on why this
+ is needed. */
OUTMSG2 (("get_windows_debug_event - "
- "unexpected stop in 0x%lx (expecting 0x%x)\n",
- ptid.lwp (), windows_process.desired_stop_thread_id));
+ "unexpected stop in suspended thread 0x%x\n",
+ th->tid));
maybe_adjust_pc ();
- windows_process.pending_stops.push_back
- ({(DWORD) ptid.lwp (), *ourstatus, *current_event});
+ th->pending_stop.status = *ourstatus;
+ th->pending_stop.event = *current_event;
ourstatus->set_spurious ();
}
else
@@ -1207,8 +1221,7 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
[[fallthrough]];
case TARGET_WAITKIND_SPURIOUS:
/* do nothing, just continue */
- child_continue (continue_status,
- windows_process.desired_stop_thread_id);
+ continue_last_debug_event (continue_status, debug_threads);
break;
}
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (19 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:21 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread Pedro Alves
` (26 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
This will be used in subsequent patches to avoid using
DBG_EXCEPTION_NOT_HANDLED on the wrong thread.
Change-Id: I32915623b5036fb902f9830ce2d6f0b1ccf1f5cf
---
gdb/nat/windows-nat.c | 8 ++++++++
gdb/nat/windows-nat.h | 5 +++++
2 files changed, 13 insertions(+)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index fbb48f8e3ee..fe707fff905 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -637,6 +637,14 @@ windows_process_info::add_all_dlls ()
/* See nat/windows-nat.h. */
+ptid_t
+get_last_debug_event_ptid ()
+{
+ return ptid_t (last_wait_event.dwProcessId, last_wait_event.dwThreadId, 0);
+}
+
+/* See nat/windows-nat.h. */
+
BOOL
continue_last_debug_event (DWORD continue_status, bool debug_events)
{
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index fec6becb594..8ac16311650 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -278,6 +278,11 @@ struct windows_process_info
extern BOOL continue_last_debug_event (DWORD continue_status,
bool debug_events);
+/* Return the ptid_t of the thread that the last waited-for event was
+ for. */
+
+extern ptid_t get_last_debug_event_ptid ();
+
/* A simple wrapper for WaitForDebugEvent that also sets the internal
'last_wait_event' on success. */
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (20 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:22 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking Pedro Alves
` (25 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
Passing a signal to a thread other than the one that last reported an
event will be later possible with DBG_REPLY_LATER and the Windows
backend working in non-stop mode.
With an all-stop backend that isn't possible, so at least don't
incorrectly consider passing DBG_EXCEPTION_NOT_HANDLED if the thread
that we're going to call ContinueDebugEvent for is not the one that
the user issued "signal SIG" on.
Change-Id: I27092ecfbf0904ebce02dff07d9104d22f3d8f0e
---
gdb/windows-nat.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index f86e6ae9a28..1e518f46c49 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1331,10 +1331,21 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
if (sig != GDB_SIGNAL_0)
{
- if (windows_process.current_event.dwDebugEventCode
+ /* 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 ())
+ {
+ /* ContinueDebugEvent will be for a different thread. */
+ DEBUG_EXCEPT ("Cannot continue with signal %d here. "
+ "Not last-event thread", sig);
+ }
+ else if (windows_process.current_event.dwDebugEventCode
!= EXCEPTION_DEBUG_EVENT)
{
- DEBUG_EXCEPT ("Cannot continue with signal %d here.", sig);
+ DEBUG_EXCEPT ("Cannot continue with signal %d here. "
+ "Not stopped for EXCEPTION_DEBUG_EVENT", sig);
}
else if (sig == windows_process.last_sig)
continue_status = DBG_EXCEPTION_NOT_HANDLED;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (21 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-30 20:37 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 24/47] Windows gdb: Enable "set scheduler-locking on" Pedro Alves
` (24 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
This rewrites win32_process_target::resume such that scheduler-locking
is implemented properly.
It also uses the new get_last_debug_event_ptid function to avoid
considering passing a signal to the wrong thread, like done for the
native side in a previous patch.
Note this code/comment being removed:
- /* Yes, we're ignoring resume_info[0].thread. It'd be tricky to make
- the Windows resume code do the right thing for thread switching. */
- tid = windows_process.current_event.dwThreadId;
This meant that scheduler-locking currently is broken badly unless you
stay in the thread that last reported an event. If you switch to a
different thread from the one that last reported an event and step,
you get a spurious SIGTRAP in the thread that last reported a stop,
not the one that you tried to step:
(gdb) t 1
[Switching to thread 1 (Thread 3908)]
#0 0x00007fffc768d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
(gdb) set scheduler-locking on
(gdb) set disassemble-next-line on
(gdb) frame
#0 0x00007fffc768d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
=> 0x00007fffc768d6e4 <ntdll!ZwDelayExecution+20>: c3 ret
(gdb) si
Thread 3 received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 2660]
0x00007fffc4e4e92e in KERNELBASE!EncodeRemotePointer () from target:C:/Windows/System32/KernelBase.dll
=> 0x00007fffc4e4e92e <KERNELBASE!EncodeRemotePointer+8254>: eb 78 jmp 0x7fffc4e4e9a8 <KERNELBASE!EncodeRemotePointer+8376>
(gdb)
Note how we switched to thread 1, stepped, and GDBserver still stepped
thread 3... This is fixed by this patch. We now get:
(gdb) info threads
Id Target Id Frame
1 Thread 920 0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
2 Thread 8528 0x00007ffe03730ad4 in ntdll!ZwWaitForWorkViaWorkerFactory () from target:C:/Windows/SYSTEM32/ntdll.dll
3 Thread 3128 0x00007ffe03730ad4 in ntdll!ZwWaitForWorkViaWorkerFactory () from target:C:/Windows/SYSTEM32/ntdll.dll
* 4 Thread 7164 0x00007ffe0102e929 in KERNELBASE!EncodeRemotePointer () from target:C:/Windows/System32/KernelBase.dll
5 Thread 8348 0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
6 Thread 2064 0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
(gdb) t 1
[Switching to thread 1 (Thread 920)]
#0 0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
(gdb) set scheduler-locking on
(gdb) si
0x00007ffe0372d6e4 in ntdll!ZwDelayExecution () from target:C:/Windows/SYSTEM32/ntdll.dll
(gdb) si
0x00007ffe00f9b44e in SleepEx () from target:C:/Windows/System32/KernelBase.dll
(gdb) si
0x00007ffe00f9b453 in SleepEx () from target:C:/Windows/System32/KernelBase.dll
I.e., we kept stepping the right thread, thread 1.
Note we stopped again at 0x00007ffe0372d6e4 the first time (same PC
the thread already was at before the first stepi) because the thread
had been stopped at a syscall, so that's normal:
(gdb) disassemble
Dump of assembler code for function ntdll!ZwDelayExecution:
0x00007ffe0372d6d0 <+0>: mov %rcx,%r10
0x00007ffe0372d6d3 <+3>: mov $0x34,%eax
0x00007ffe0372d6d8 <+8>: testb $0x1,0x7ffe0308
0x00007ffe0372d6e0 <+16>: jne 0x7ffe0372d6e5 <ntdll!ZwDelayExecution+21>
0x00007ffe0372d6e2 <+18>: syscall
=> 0x00007ffe0372d6e4 <+20>: ret
Change-Id: I44f4fe4cb98592517569c6716b9d189f42db25a0
---
gdbserver/win32-low.cc | 158 ++++++++++++++++++++++-------------------
1 file changed, 85 insertions(+), 73 deletions(-)
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 32464024722..2e3e4442012 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -405,19 +405,13 @@ continue_one_thread (thread_info *thread, int thread_id)
}
}
+/* Helper for win32_process_target::kill. Resume all suspended
+ threads that match THREAD_ID, and continue the last debug event
+ with CONTINUE_STATUS. */
+
static BOOL
-child_continue (DWORD continue_status, int thread_id)
+child_continue_for_kill (DWORD continue_status, int thread_id)
{
- process_info *proc = find_process_pid (windows_process.process_id);
- for (thread_info &thread : proc->thread_list ())
- {
- auto *th = static_cast<windows_thread_info *> (thread.target_data ());
- if ((thread_id == -1 || thread_id == (int) th->tid)
- && !th->suspended
- && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
- return TRUE;
- }
-
/* The inferior will only continue after the ContinueDebugEvent
call. */
for_each_thread ([&] (thread_info *thread)
@@ -676,7 +670,7 @@ win32_process_target::kill (process_info *process)
TerminateProcess (windows_process.handle, 0);
for (;;)
{
- if (!child_continue (DBG_CONTINUE, -1))
+ if (!child_continue_for_kill (DBG_CONTINUE, -1))
break;
if (!wait_for_debug_event (&windows_process.current_event, INFINITE))
break;
@@ -743,90 +737,108 @@ win32_process_target::thread_alive (ptid_t ptid)
return find_thread_ptid (ptid) != NULL;
}
-/* Resume the inferior process. RESUME_INFO describes how we want
- to resume. */
-void
-win32_process_target::resume (thread_resume *resume_info, size_t n)
-{
- DWORD tid;
- enum gdb_signal sig;
- int step;
- windows_thread_info *th;
- DWORD continue_status = DBG_CONTINUE;
- ptid_t ptid;
-
- /* This handles the very limited set of resume packets that GDB can
- currently produce. */
-
- if (n == 1 && resume_info[0].thread == minus_one_ptid)
- tid = -1;
- else if (n > 1)
- tid = -1;
- else
- /* Yes, we're ignoring resume_info[0].thread. It'd be tricky to make
- the Windows resume code do the right thing for thread switching. */
- tid = windows_process.current_event.dwThreadId;
+/* Helper for win32_process_target::resume. Resume THREAD, with
+ signal SIG, and stepping if STEP. If resuming the thread that last
+ reported an event, sets CONTINUE_STATUS appropriately to the status
+ that should be passed to ContinueDebugEvent. */
- if (resume_info[0].thread != minus_one_ptid)
- {
- sig = gdb_signal_from_host (resume_info[0].sig);
- step = resume_info[0].kind == resume_step;
- }
- else
- {
- sig = GDB_SIGNAL_0;
- step = 0;
- }
+static void
+resume_one_thread (thread_info *thread, bool step, gdb_signal sig,
+ DWORD *continue_status)
+{
+ auto *th = static_cast<windows_thread_info *> (thread->target_data ());
if (sig != GDB_SIGNAL_0)
{
- if (windows_process.current_event.dwDebugEventCode
- != EXCEPTION_DEBUG_EVENT)
+ /* Allow continuing with the same signal that interrupted us.
+ Otherwise complain. */
+ if (thread->id != get_last_debug_event_ptid ())
+ {
+ /* ContinueDebugEvent will be for a different thread. */
+ OUTMSG (("Cannot continue with signal %d here. "
+ "Not last-event thread", sig));
+ }
+ else if (windows_process.current_event.dwDebugEventCode
+ != EXCEPTION_DEBUG_EVENT)
{
- OUTMSG (("Cannot continue with signal %s here.\n",
+ OUTMSG (("Cannot continue with signal %s here. "
+ "Not stopped for EXCEPTION_DEBUG_EVENT.\n",
gdb_signal_to_string (sig)));
}
else if (sig == windows_process.last_sig)
- continue_status = DBG_EXCEPTION_NOT_HANDLED;
+ *continue_status = DBG_EXCEPTION_NOT_HANDLED;
else
OUTMSG (("Can only continue with received signal %s.\n",
gdb_signal_to_string (windows_process.last_sig)));
}
- windows_process.last_sig = GDB_SIGNAL_0;
+ win32_prepare_to_resume (th);
- /* Get context for the currently selected thread. */
- ptid = debug_event_ptid (&windows_process.current_event);
- th = windows_process.find_thread (ptid);
- if (th)
+ DWORD *context_flags = windows_process.context_flags_ptr (th);
+ if (*context_flags)
{
- win32_prepare_to_resume (th);
+ /* Move register values from the inferior into the thread
+ context structure. */
+ regcache_invalidate ();
- DWORD *context_flags = windows_process.context_flags_ptr (th);
- if (*context_flags)
+ if (step)
{
- /* Move register values from the inferior into the thread
- context structure. */
- regcache_invalidate ();
+ if (the_low_target.single_step != NULL)
+ (*the_low_target.single_step) (th);
+ else
+ error ("Single stepping is not supported "
+ "in this configuration.\n");
+ }
+
+ win32_set_thread_context (th);
+ *context_flags = 0;
+ }
+
+ continue_one_thread (thread, th->tid);
+}
+
+/* Resume the inferior process. RESUME_INFO describes how we want
+ to resume. */
+void
+win32_process_target::resume (thread_resume *resume_info, size_t n)
+{
+ DWORD continue_status = DBG_CONTINUE;
+ bool any_pending = false;
+
+ for_each_thread ([&] (thread_info *thread)
+ {
+ auto *th = static_cast<windows_thread_info *> (thread->target_data ());
- if (step)
+ for (int ndx = 0; ndx < n; ndx++)
+ {
+ thread_resume &r = resume_info[ndx];
+ ptid_t ptid = r.thread;
+ gdb_signal sig = gdb_signal_from_host (r.sig);
+ bool step = r.kind == resume_step;
+
+ if (ptid == minus_one_ptid || ptid == thread->id
+ /* Handle both 'pPID' and 'pPID.-1' as meaning 'all threads
+ of PID'. */
+ || (ptid.pid () == thread->id.pid ()
+ && (ptid.is_pid () || ptid.lwp () == -1)))
{
- if (the_low_target.single_step != NULL)
- (*the_low_target.single_step) (th);
- else
- error ("Single stepping is not supported "
- "in this configuration.\n");
- }
+ /* Ignore (wildcard) resume requests for already-resumed
+ threads. */
+ if (!th->suspended)
+ continue;
- win32_set_thread_context (th);
- *context_flags = 0;
+ resume_one_thread (thread, step, sig, &continue_status);
+ break;
+ }
}
- }
- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ if (!th->suspended
+ && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ any_pending = true;
+ });
- child_continue (continue_status, tid);
+ if (!any_pending)
+ continue_last_debug_event (continue_status, debug_threads);
}
/* See nat/windows-nat.h. */
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 24/47] Windows gdb: Enable "set scheduler-locking on"
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (22 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 25/47] Windows gdbserver: Eliminate soft-interrupt mechanism Pedro Alves
` (23 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Surprisingly (to me), enabling scheduler locking on Windows currently
fails:
(gdb)
set scheduler-locking on
Target 'native' cannot support this command.
The backend itself does support scheduler-locking. This patch
implements windows_nat_target::get_thread_control_capabilities so that
the core knows schedlocking works for this target.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Ie762d3768fd70e4ac398c8bcc03c3213bfa26a6a
---
gdb/windows-nat.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 1e518f46c49..1197c2f8fe1 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -254,6 +254,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void close () override;
+ thread_control_capabilities get_thread_control_capabilities () override
+ { return tc_schedlock; }
+
void attach (const char *, int) override;
bool attach_no_wait () override
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 25/47] Windows gdbserver: Eliminate soft-interrupt mechanism
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (23 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 24/47] Windows gdb: Enable "set scheduler-locking on" Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state Pedro Alves
` (22 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
I noticed that faked_breakpoint is write only. And then I hacked
win32_process_target::request_interrupt to force it to stop threads
using the soft_interrupt_requested mechanism (which suspends threads,
and then fakes a breakpoint event in the main thread), and saw that it
no longer works -- gdbserver crashes accessing a NULL current_thread,
because fake_breakpoint_event does not switch to a thread.
This code was originally added for Windows CE, as neither
GenerateConsoleCtrlEvent nor DebugBreakProcess worked there. Windows
CE support has since been removed.
We nowadays require Windows XP or later, and XP has DebugBreakProcess.
The soft_interrupt_requested mechanism has other problems, like for
example faking the event in the main thread, even if that thread was
previously stopped, due to scheduler-locking.
A following patch will add a similar mechanism stopping all threads
with SuspendThread to native GDB, for non-stop mode, which doesn't
have these problems. It's different enough from this old code that I
think we should just rip the old code out, and reimplement it from
scratch (based on gdb's version) when we need it.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I89e98233a9c40c6dcba7c8e1dacee08603843fb1
---
gdbserver/win32-low.cc | 32 +-------------------------------
gdbserver/win32-low.h | 8 --------
2 files changed, 1 insertion(+), 39 deletions(-)
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 2e3e4442012..ebce67cd72d 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -294,8 +294,6 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
windows_process.process_id = pid;
windows_process.main_thread_id = 0;
- windows_process.soft_interrupt_requested = 0;
- windows_process.faked_breakpoint = 0;
windows_process.open_process_used = true;
memset (&windows_process.current_event, 0,
@@ -418,7 +416,6 @@ child_continue_for_kill (DWORD continue_status, int thread_id)
{
continue_one_thread (thread, thread_id);
});
- windows_process.faked_breakpoint = 0;
return continue_last_debug_event (continue_status, debug_threads);
}
@@ -918,23 +915,6 @@ suspend_one_thread (thread_info *thread)
th->suspend ();
}
-static void
-fake_breakpoint_event (void)
-{
- OUTMSG2(("fake_breakpoint_event\n"));
-
- windows_process.faked_breakpoint = 1;
-
- memset (&windows_process.current_event, 0,
- sizeof (windows_process.current_event));
- windows_process.current_event.dwThreadId = windows_process.main_thread_id;
- windows_process.current_event.dwDebugEventCode = EXCEPTION_DEBUG_EVENT;
- windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
- = EXCEPTION_BREAKPOINT;
-
- for_each_thread (suspend_one_thread);
-}
-
/* See nat/windows-nat.h. */
bool
@@ -989,13 +969,6 @@ get_child_debug_event (DWORD *continue_status,
DEBUG_EVENT *current_event = &windows_process.current_event;
- if (windows_process.soft_interrupt_requested)
- {
- windows_process.soft_interrupt_requested = 0;
- fake_breakpoint_event ();
- goto gotevent;
- }
-
windows_process.attaching = 0;
{
process_info *proc = find_process_pid (windows_process.process_id);
@@ -1036,8 +1009,6 @@ get_child_debug_event (DWORD *continue_status,
}
}
- gotevent:
-
switch (current_event->dwDebugEventCode)
{
case CREATE_THREAD_DEBUG_EVENT:
@@ -1291,8 +1262,7 @@ win32_process_target::request_interrupt ()
if (DebugBreakProcess (windows_process.handle))
return;
- /* Last resort, suspend all threads manually. */
- windows_process.soft_interrupt_requested = 1;
+ OUTMSG2 (("Could not interrupt.\n"));
}
bool
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index 3c49ca7de9b..b170eb898f6 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -194,14 +194,6 @@ struct gdbserver_windows_process : public windows_nat::windows_process_info
debug event off the win32 API. */
struct target_waitstatus cached_status;
- /* Non zero if an interrupt request is to be satisfied by suspending
- all threads. */
- int soft_interrupt_requested = 0;
-
- /* Non zero if the inferior is stopped in a simulated breakpoint done
- by suspending all the threads. */
- int faked_breakpoint = 0;
-
/* True if current_process_handle needs to be closed. */
bool open_process_used = false;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (24 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 25/47] Windows gdbserver: Eliminate soft-interrupt mechanism Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:30 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig " Pedro Alves
` (21 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
With non-stop mode, each thread is controlled independently of the
others, and each thread has its own independent reason for its last
stop.
Thus, any thread-specific state that is currently per-process must be
converted to per-thread state.
This patch converts windows_process_info::current_event, moving it to
windows_thread_info instead, renamed to last_event.
Since each thread will have its own copy of its last Windows debug
event, we no longer need the same information stored in struct
pending_stop.
Since windows_process.current_event no longer exists, we need to pass
the current event as parameter to a number of methods.
This adjusts both native gdb and gdbserver.
Change-Id: Ice09a5d932c912210608d5af25e1898f823e3c99
---
gdb/nat/windows-nat.c | 13 +++--
gdb/nat/windows-nat.h | 24 ++++-----
gdb/windows-nat.c | 120 ++++++++++++++++++++++-------------------
gdbserver/win32-low.cc | 70 ++++++++++++------------
gdbserver/win32-low.h | 5 +-
5 files changed, 121 insertions(+), 111 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index fe707fff905..53d891f65d0 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -300,8 +300,10 @@ get_image_name (HANDLE h, void *address, int unicode)
/* See nat/windows-nat.h. */
bool
-windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
+windows_process_info::handle_ms_vc_exception (const DEBUG_EVENT ¤t_event)
{
+ const EXCEPTION_RECORD *rec = ¤t_event.u.Exception.ExceptionRecord;
+
if (rec->NumberParameters >= 3
&& (rec->ExceptionInformation[0] & 0xffffffff) == 0x1000)
{
@@ -342,7 +344,8 @@ windows_process_info::handle_ms_vc_exception (const EXCEPTION_RECORD *rec)
#define MS_VC_EXCEPTION 0x406d1388
handle_exception_result
-windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
+windows_process_info::handle_exception (DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus,
bool debug_exceptions)
{
#define DEBUG_EXCEPTION_SIMPLE(x) if (debug_exceptions) \
@@ -470,7 +473,7 @@ windows_process_info::handle_exception (struct target_waitstatus *ourstatus,
break;
case MS_VC_EXCEPTION:
DEBUG_EXCEPTION_SIMPLE ("MS_VC_EXCEPTION");
- if (handle_ms_vc_exception (rec))
+ if (handle_ms_vc_exception (current_event))
{
ourstatus->set_stopped (GDB_SIGNAL_TRAP);
result = HANDLE_EXCEPTION_IGNORED;
@@ -605,11 +608,11 @@ windows_process_info::add_dll (LPVOID load_addr)
/* See nat/windows-nat.h. */
void
-windows_process_info::dll_loaded_event ()
+windows_process_info::dll_loaded_event (const DEBUG_EVENT ¤t_event)
{
gdb_assert (current_event.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT);
- LOAD_DLL_DEBUG_INFO *event = ¤t_event.u.LoadDll;
+ const LOAD_DLL_DEBUG_INFO *event = ¤t_event.u.LoadDll;
const char *dll_name;
/* Try getting the DLL name via the lpImageName field of the event.
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 8ac16311650..0b89c751e89 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -46,10 +46,6 @@ struct pending_stop
/* The target waitstatus we computed. TARGET_WAITKIND_IGNORE if the
thread does not have a pending stop. */
target_waitstatus status;
-
- /* The event. A few fields of this can be referenced after a stop,
- and it seemed simplest to store the entire event. */
- DEBUG_EVENT event;
};
@@ -104,6 +100,10 @@ struct windows_thread_info
process them once the step has completed. See PR gdb/22992. */
struct pending_stop pending_stop {};
+ /* The last Windows event returned by WaitForDebugEvent for this
+ thread. */
+ DEBUG_EVENT last_event {};
+
/* The context of the thread, including any manipulations. */
union
{
@@ -149,10 +149,6 @@ struct windows_process_info
DWORD main_thread_id = 0;
enum gdb_signal last_sig = GDB_SIGNAL_0;
- /* The current debug event from WaitForDebugEvent or from a pending
- stop. */
- DEBUG_EVENT current_event {};
-
/* Contents of $_siginfo */
EXCEPTION_RECORD siginfo_er {};
@@ -176,7 +172,8 @@ struct windows_process_info
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 (struct target_waitstatus *ourstatus) = 0;
+ virtual DWORD handle_output_debug_string (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus) = 0;
/* Handle a DLL load event.
@@ -197,7 +194,7 @@ struct windows_process_info
This function must be supplied by the embedding application. */
- virtual void handle_unload_dll () = 0;
+ virtual void handle_unload_dll (const DEBUG_EVENT ¤t_event) = 0;
/* When EXCEPTION_ACCESS_VIOLATION is processed, we give the embedding
application a chance to change it to be considered "unhandled".
@@ -207,11 +204,12 @@ struct windows_process_info
virtual bool handle_access_violation (const EXCEPTION_RECORD *rec) = 0;
handle_exception_result handle_exception
- (struct target_waitstatus *ourstatus, bool debug_exceptions);
+ (DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus, bool debug_exceptions);
/* Call to indicate that a DLL was loaded. */
- void dll_loaded_event ();
+ void dll_loaded_event (const DEBUG_EVENT ¤t_event);
/* Iterate over all DLLs currently mapped by our inferior, and
add them to our list of solibs. */
@@ -247,7 +245,7 @@ struct windows_process_info
Return true if the exception was handled; return false otherwise. */
- bool handle_ms_vc_exception (const EXCEPTION_RECORD *rec);
+ bool handle_ms_vc_exception (const DEBUG_EVENT ¤t_event);
/* Iterate over all DLLs currently mapped by our inferior, looking for
a DLL which is loaded at LOAD_ADDR. If found, add the DLL to our
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 1197c2f8fe1..8ccb7048665 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -110,9 +110,10 @@ DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (struct target_waitstatus *ourstatus) override;
+ DWORD 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 () override;
+ void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
void invalidate_context (windows_thread_info *th);
@@ -314,7 +315,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
const char *thread_name (struct thread_info *) override;
ptid_t get_windows_debug_event (int pid, struct target_waitstatus *ourstatus,
- target_wait_flags options);
+ target_wait_flags options,
+ DEBUG_EVENT *current_event);
void do_initial_windows_stuff (DWORD pid, bool attaching);
@@ -345,7 +347,7 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
windows_thread_info *add_thread (ptid_t ptid, HANDLE h, void *tlb,
bool main_thread_p);
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
- DWORD fake_create_process ();
+ DWORD fake_create_process (const DEBUG_EVENT ¤t_event);
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);
@@ -951,7 +953,7 @@ windows_per_inferior::handle_load_dll (const char *dll_name, LPVOID base)
/* See nat/windows-nat.h. */
void
-windows_per_inferior::handle_unload_dll ()
+windows_per_inferior::handle_unload_dll (const DEBUG_EVENT ¤t_event)
{
LPVOID lpBaseOfDll = current_event.u.UnloadDll.lpBaseOfDll;
@@ -1014,7 +1016,8 @@ signal_event_command (const char *args, int from_tty)
DWORD
windows_per_inferior::handle_output_debug_string
- (struct target_waitstatus *ourstatus)
+ (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus)
{
DWORD thread_id = 0;
@@ -1297,11 +1300,11 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
/* Called in pathological case where Windows fails to send a
CREATE_PROCESS_DEBUG_EVENT after an attach. */
DWORD
-windows_nat_target::fake_create_process ()
+windows_nat_target::fake_create_process (const DEBUG_EVENT ¤t_event)
{
windows_process.handle
= OpenProcess (PROCESS_ALL_ACCESS, FALSE,
- windows_process.current_event.dwProcessId);
+ current_event.dwProcessId);
if (windows_process.handle != NULL)
windows_process.open_process_used = 1;
else
@@ -1310,12 +1313,11 @@ windows_nat_target::fake_create_process ()
throw_winerror_with_name (_("OpenProcess call failed"), err);
/* We can not debug anything in that case. */
}
- add_thread (ptid_t (windows_process.current_event.dwProcessId,
- windows_process.current_event.dwThreadId, 0),
- windows_process.current_event.u.CreateThread.hThread,
- windows_process.current_event.u.CreateThread.lpThreadLocalBase,
+ add_thread (ptid_t (current_event.dwProcessId, current_event.dwThreadId, 0),
+ current_event.u.CreateThread.hThread,
+ current_event.u.CreateThread.lpThreadLocalBase,
true /* main_thread_p */);
- return windows_process.current_event.dwThreadId;
+ return current_event.dwThreadId;
}
void
@@ -1332,6 +1334,13 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
if (resume_all)
ptid = inferior_ptid;
+ DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
+ ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+
+ /* Get currently selected thread. */
+ th = windows_process.find_thread (inferior_ptid);
+ gdb_assert (th != nullptr);
+
if (sig != GDB_SIGNAL_0)
{
/* Note it is OK to call get_last_debug_event_ptid() from the
@@ -1344,8 +1353,7 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not last-event thread", sig);
}
- else if (windows_process.current_event.dwDebugEventCode
- != EXCEPTION_DEBUG_EVENT)
+ else if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
{
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not stopped for EXCEPTION_DEBUG_EVENT", sig);
@@ -1362,7 +1370,7 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
for (const xlate_exception &x : xlate)
if (x.us == sig)
{
- current_event.u.Exception.ExceptionRecord.ExceptionCode
+ th->last_event.u.Exception.ExceptionRecord.ExceptionCode
= x.them;
continue_status = DBG_EXCEPTION_NOT_HANDLED;
break;
@@ -1379,25 +1387,17 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
windows_process.last_sig = GDB_SIGNAL_0;
- DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
- ptid.pid (), (unsigned) ptid.lwp (), step, sig);
-
- /* Get context for currently selected thread. */
- th = windows_process.find_thread (inferior_ptid);
- if (th)
+ windows_process.with_context (th, [&] (auto *context)
{
- windows_process.with_context (th, [&] (auto *context)
+ if (step)
{
- if (step)
- {
- /* Single step by setting t bit. */
- regcache *regcache = get_thread_regcache (inferior_thread ());
- struct gdbarch *gdbarch = regcache->arch ();
- fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
- context->EFlags |= FLAG_TRACE_BIT;
- }
- });
- }
+ /* Single step by setting t bit. */
+ regcache *regcache = get_thread_regcache (inferior_thread ());
+ struct gdbarch *gdbarch = regcache->arch ();
+ fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
+ context->EFlags |= FLAG_TRACE_BIT;
+ }
+ });
/* Allow continuing with the same signal that interrupted us.
Otherwise complain. */
@@ -1459,7 +1459,8 @@ windows_nat_target::pass_ctrlc ()
ptid_t
windows_nat_target::get_windows_debug_event
- (int pid, struct target_waitstatus *ourstatus, target_wait_flags options)
+ (int pid, struct target_waitstatus *ourstatus, target_wait_flags options,
+ DEBUG_EVENT *current_event)
{
DWORD continue_status, event_code;
DWORD thread_id = 0;
@@ -1477,7 +1478,7 @@ windows_nat_target::get_windows_debug_event
thread_id = th->tid;
*ourstatus = th->pending_stop.status;
th->pending_stop.status.set_ignore ();
- windows_process.current_event = th->pending_stop.event;
+ *current_event = th->last_event;
ptid_t ptid (windows_process.process_id, thread_id);
windows_process.invalidate_context (th.get ());
@@ -1486,7 +1487,6 @@ windows_nat_target::get_windows_debug_event
}
windows_process.last_sig = GDB_SIGNAL_0;
- DEBUG_EVENT *current_event = &windows_process.current_event;
if ((options & TARGET_WNOHANG) != 0 && !m_debug_event_pending)
{
@@ -1494,11 +1494,11 @@ windows_nat_target::get_windows_debug_event
return minus_one_ptid;
}
- wait_for_debug_event_main_thread (&windows_process.current_event);
+ wait_for_debug_event_main_thread (current_event);
continue_status = DBG_CONTINUE;
- event_code = windows_process.current_event.dwDebugEventCode;
+ event_code = current_event->dwDebugEventCode;
ourstatus->set_spurious ();
switch (event_code)
@@ -1516,7 +1516,7 @@ windows_nat_target::get_windows_debug_event
/* Kludge around a Windows bug where first event is a create
thread event. Caused when attached process does not have
a main thread. */
- thread_id = fake_create_process ();
+ thread_id = fake_create_process (*current_event);
if (thread_id)
windows_process.saw_create++;
}
@@ -1613,7 +1613,7 @@ windows_nat_target::get_windows_debug_event
break;
try
{
- windows_process.dll_loaded_event ();
+ windows_process.dll_loaded_event (*current_event);
}
catch (const gdb_exception &ex)
{
@@ -1633,7 +1633,7 @@ windows_nat_target::get_windows_debug_event
break;
try
{
- windows_process.handle_unload_dll ();
+ windows_process.handle_unload_dll (*current_event);
}
catch (const gdb_exception &ex)
{
@@ -1650,7 +1650,8 @@ windows_nat_target::get_windows_debug_event
"EXCEPTION_DEBUG_EVENT");
if (windows_process.saw_create != 1)
break;
- switch (windows_process.handle_exception (ourstatus, debug_exceptions))
+ switch (windows_process.handle_exception (*current_event,
+ ourstatus, debug_exceptions))
{
case HANDLE_EXCEPTION_UNHANDLED:
default:
@@ -1672,7 +1673,8 @@ windows_nat_target::get_windows_debug_event
"OUTPUT_DEBUG_STRING_EVENT");
if (windows_process.saw_create != 1)
break;
- thread_id = windows_process.handle_output_debug_string (ourstatus);
+ thread_id = windows_process.handle_output_debug_string (*current_event,
+ ourstatus);
break;
default:
@@ -1697,6 +1699,8 @@ windows_nat_target::get_windows_debug_event
const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
windows_thread_info *th = windows_process.find_thread (ptid);
+ th->last_event = *current_event;
+
if (th->suspended)
{
/* Pending stop. See the comment by the definition of
@@ -1715,8 +1719,8 @@ windows_nat_target::get_windows_debug_event
th->stopped_at_software_breakpoint = true;
th->pc_adjusted = false;
}
+
th->pending_stop.status = *ourstatus;
- th->pending_stop.event = *current_event;
ourstatus->set_ignore ();
continue_last_debug_event_main_thread
@@ -1742,7 +1746,10 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
while (1)
{
- ptid_t result = get_windows_debug_event (pid, ourstatus, options);
+ DEBUG_EVENT current_event;
+
+ ptid_t result = get_windows_debug_event (pid, ourstatus, options,
+ ¤t_event);
if ((options & TARGET_WNOHANG) != 0
&& ourstatus->kind () == TARGET_WAITKIND_IGNORE)
@@ -1761,11 +1768,11 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
windows_thread_info *th = windows_process.find_thread (result);
th->stopped_at_software_breakpoint = false;
- if (windows_process.current_event.dwDebugEventCode
+ if (current_event.dwDebugEventCode
== EXCEPTION_DEBUG_EVENT
- && ((windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ && ((current_event.u.Exception.ExceptionRecord.ExceptionCode
== EXCEPTION_BREAKPOINT)
- || (windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ || (current_event.u.Exception.ExceptionRecord.ExceptionCode
== STATUS_WX86_BREAKPOINT))
&& windows_process.windows_initialization_done)
{
@@ -1806,8 +1813,6 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
windows_process.cygwin_load_end = 0;
#endif
windows_process.process_id = pid;
- memset (&windows_process.current_event, 0,
- sizeof (windows_process.current_event));
inf = current_inferior ();
if (!inf->target_is_pushed (this))
inf->push_target (this);
@@ -1853,7 +1858,10 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
&& status.kind () != TARGET_WAITKIND_SPURIOUS)
break;
- this->resume (minus_one_ptid, 0, GDB_SIGNAL_0);
+ /* 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);
}
switch_to_thread (this->find_thread (last_ptid));
@@ -2129,7 +2137,7 @@ windows_nat_target::detach (inferior *inf, int from_tty)
if (process_alive)
do_synchronously ([&] ()
{
- if (!DebugActiveProcessStop (windows_process.current_event.dwProcessId))
+ if (!DebugActiveProcessStop (windows_process.process_id))
err = (unsigned) GetLastError ();
else
DebugSetProcessKillOnExit (FALSE);
@@ -2140,7 +2148,7 @@ windows_nat_target::detach (inferior *inf, int from_tty)
{
std::string msg
= string_printf (_("Can't detach process %u"),
- (unsigned) windows_process.current_event.dwProcessId);
+ windows_process.process_id);
throw_winerror_with_name (msg.c_str (), *err);
}
@@ -2937,9 +2945,9 @@ windows_nat_target::kill ()
{
if (!windows_continue (DBG_CONTINUE, -1, WCONT_KILLED))
break;
- wait_for_debug_event_main_thread (&windows_process.current_event);
- if (windows_process.current_event.dwDebugEventCode
- == EXIT_PROCESS_DEBUG_EVENT)
+ DEBUG_EVENT current_event;
+ wait_for_debug_event_main_thread (¤t_event);
+ if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
}
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index ebce67cd72d..a4000980f67 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -296,9 +296,6 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
windows_process.open_process_used = true;
- memset (&windows_process.current_event, 0,
- sizeof (windows_process.current_event));
-
#ifdef __x86_64__
BOOL wow64;
if (!IsWow64Process (proch, &wow64))
@@ -601,7 +598,8 @@ win32_process_target::attach (unsigned long pid)
DWORD
gdbserver_windows_process::handle_output_debug_string
- (struct target_waitstatus *ourstatus)
+ (const DEBUG_EVENT ¤t_event,
+ struct target_waitstatus *ourstatus)
{
#define READ_BUFFER_LEN 1024
CORE_ADDR addr;
@@ -669,14 +667,13 @@ win32_process_target::kill (process_info *process)
{
if (!child_continue_for_kill (DBG_CONTINUE, -1))
break;
- if (!wait_for_debug_event (&windows_process.current_event, INFINITE))
+ DEBUG_EVENT current_event;
+ if (!wait_for_debug_event (¤t_event, INFINITE))
break;
- if (windows_process.current_event.dwDebugEventCode
- == EXIT_PROCESS_DEBUG_EVENT)
+ if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
- else if (windows_process.current_event.dwDebugEventCode
- == OUTPUT_DEBUG_STRING_EVENT)
- windows_process.handle_output_debug_string (nullptr);
+ else if (current_event.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
+ windows_process.handle_output_debug_string (current_event, nullptr);
}
win32_clear_process ();
@@ -755,8 +752,7 @@ resume_one_thread (thread_info *thread, bool step, gdb_signal sig,
OUTMSG (("Cannot continue with signal %d here. "
"Not last-event thread", sig));
}
- else if (windows_process.current_event.dwDebugEventCode
- != EXCEPTION_DEBUG_EVENT)
+ else if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
{
OUTMSG (("Cannot continue with signal %s here. "
"Not stopped for EXCEPTION_DEBUG_EVENT.\n",
@@ -895,7 +891,7 @@ gdbserver_windows_process::handle_load_dll (const char *name, LPVOID base)
/* See nat/windows-nat.h. */
void
-gdbserver_windows_process::handle_unload_dll ()
+gdbserver_windows_process::handle_unload_dll (const DEBUG_EVENT ¤t_event)
{
CORE_ADDR load_addr =
(CORE_ADDR) (uintptr_t) current_event.u.UnloadDll.lpBaseOfDll;
@@ -929,7 +925,7 @@ gdbserver_windows_process::handle_access_violation
PC. */
static void
-maybe_adjust_pc ()
+maybe_adjust_pc (const DEBUG_EVENT ¤t_event)
{
regcache *regcache = get_thread_regcache (current_thread);
child_fetch_inferior_registers (regcache, -1);
@@ -938,10 +934,10 @@ maybe_adjust_pc ()
= windows_process.find_thread (current_thread->id);
th->stopped_at_software_breakpoint = false;
- if (windows_process.current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
- && ((windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ if (current_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
+ && ((current_event.u.Exception.ExceptionRecord.ExceptionCode
== EXCEPTION_BREAKPOINT)
- || (windows_process.current_event.u.Exception.ExceptionRecord.ExceptionCode
+ || (current_event.u.Exception.ExceptionRecord.ExceptionCode
== STATUS_WX86_BREAKPOINT))
&& windows_process.child_initialization_done)
{
@@ -956,7 +952,8 @@ maybe_adjust_pc ()
static int
get_child_debug_event (DWORD *continue_status,
- struct target_waitstatus *ourstatus)
+ struct target_waitstatus *ourstatus,
+ DEBUG_EVENT *current_event)
{
ptid_t ptid;
@@ -967,8 +964,6 @@ get_child_debug_event (DWORD *continue_status,
/* Check if GDB sent us an interrupt request. */
check_remote_input_interrupt_request ();
- DEBUG_EVENT *current_event = &windows_process.current_event;
-
windows_process.attaching = 0;
{
process_info *proc = find_process_pid (windows_process.process_id);
@@ -981,8 +976,8 @@ get_child_debug_event (DWORD *continue_status,
{
*ourstatus = th->pending_stop.status;
th->pending_stop.status.set_ignore ();
- windows_process.current_event = th->pending_stop.event;
- ptid = debug_event_ptid (&windows_process.current_event);
+ *current_event = th->last_event;
+ ptid = debug_event_ptid (current_event);
switch_to_thread (find_thread_ptid (ptid));
return 1;
}
@@ -991,7 +986,7 @@ get_child_debug_event (DWORD *continue_status,
/* Keep the wait time low enough for comfortable remote
interruption, but high enough so gdbserver doesn't become a
bottleneck. */
- if (!wait_for_debug_event (&windows_process.current_event, 250))
+ if (!wait_for_debug_event (current_event, 250))
{
DWORD e = GetLastError();
@@ -1086,7 +1081,7 @@ get_child_debug_event (DWORD *continue_status,
CloseHandle (current_event->u.LoadDll.hFile);
if (! windows_process.child_initialization_done)
break;
- windows_process.dll_loaded_event ();
+ windows_process.dll_loaded_event (*current_event);
ourstatus->set_loaded ();
break;
@@ -1098,7 +1093,7 @@ get_child_debug_event (DWORD *continue_status,
(unsigned) current_event->dwThreadId));
if (! windows_process.child_initialization_done)
break;
- windows_process.handle_unload_dll ();
+ windows_process.handle_unload_dll (*current_event);
ourstatus->set_loaded ();
break;
@@ -1107,7 +1102,8 @@ get_child_debug_event (DWORD *continue_status,
"for pid=%u tid=%x\n",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId));
- if (windows_process.handle_exception (ourstatus, debug_threads)
+ if (windows_process.handle_exception (*current_event,
+ ourstatus, debug_threads)
== HANDLE_EXCEPTION_UNHANDLED)
*continue_status = DBG_EXCEPTION_NOT_HANDLED;
break;
@@ -1118,7 +1114,7 @@ get_child_debug_event (DWORD *continue_status,
"for pid=%u tid=%x\n",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId));
- windows_process.handle_output_debug_string (nullptr);
+ windows_process.handle_output_debug_string (*current_event, nullptr);
break;
default:
@@ -1130,10 +1126,12 @@ get_child_debug_event (DWORD *continue_status,
break;
}
- ptid = debug_event_ptid (&windows_process.current_event);
+ ptid = debug_event_ptid (current_event);
windows_thread_info *th = windows_process.find_thread (ptid);
+ th->last_event = *current_event;
+
if (th != nullptr && th->suspended)
{
/* Pending stop. See the comment by the definition of
@@ -1142,9 +1140,8 @@ get_child_debug_event (DWORD *continue_status,
OUTMSG2 (("get_windows_debug_event - "
"unexpected stop in suspended thread 0x%x\n",
th->tid));
- maybe_adjust_pc ();
+ maybe_adjust_pc (*current_event);
th->pending_stop.status = *ourstatus;
- th->pending_stop.event = *current_event;
ourstatus->set_spurious ();
}
else
@@ -1168,13 +1165,16 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
fails). Report it now. */
*ourstatus = windows_process.cached_status;
windows_process.cached_status.set_ignore ();
- return debug_event_ptid (&windows_process.current_event);
+ return ptid_t (windows_process.process_id,
+ windows_process.main_thread_id, 0);
}
while (1)
{
DWORD continue_status;
- if (!get_child_debug_event (&continue_status, ourstatus))
+ DEBUG_EVENT current_event;
+ if (!get_child_debug_event (&continue_status, ourstatus,
+ ¤t_event))
continue;
switch (ourstatus->kind ())
@@ -1183,20 +1183,20 @@ win32_process_target::wait (ptid_t ptid, target_waitstatus *ourstatus,
OUTMSG2 (("Child exited with retcode = %x\n",
ourstatus->exit_status ()));
win32_clear_process ();
- return ptid_t (windows_process.current_event.dwProcessId);
+ return ptid_t (windows_process.process_id);
case TARGET_WAITKIND_STOPPED:
case TARGET_WAITKIND_SIGNALLED:
case TARGET_WAITKIND_LOADED:
{
OUTMSG2 (("Child Stopped with signal = %d \n",
ourstatus->sig ()));
- maybe_adjust_pc ();
+ maybe_adjust_pc (current_event);
/* All-stop, suspend all threads until they are explicitly
resumed. */
for_each_thread (suspend_one_thread);
- return debug_event_ptid (&windows_process.current_event);
+ return debug_event_ptid (¤t_event);
}
default:
OUTMSG (("Ignoring unknown internal event, %d\n",
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index b170eb898f6..1891f82b046 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -182,9 +182,10 @@ 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 (struct target_waitstatus *ourstatus) override;
+ DWORD 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 () override;
+ void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
int attaching = 0;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig per-thread state
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (25 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:31 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er " Pedro Alves
` (20 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
With non-stop mode, each thread is controlled independently of the
others, and each thread has its own independent reason for its last
stop.
Thus, any thread-specific state that is currently per-process must be
converted to per-thread state.
This patch converts windows_process_info::last_sig to per-thread
state, moving it to windows_thread_info instead.
This adjusts both native gdb and gdbserver.
Change-Id: Ie8c673a819be445753d967afd3a6084565648448
---
gdb/nat/windows-nat.c | 8 +++++---
gdb/nat/windows-nat.h | 5 ++++-
gdb/windows-nat.c | 10 +++-------
gdbserver/win32-low.cc | 7 +++----
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 53d891f65d0..864788ec125 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -359,8 +359,6 @@ windows_process_info::handle_exception (DEBUG_EVENT ¤t_event,
memcpy (&siginfo_er, rec, sizeof siginfo_er);
- last_sig = GDB_SIGNAL_0;
-
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION:
@@ -494,7 +492,11 @@ windows_process_info::handle_exception (DEBUG_EVENT ¤t_event,
}
if (ourstatus->kind () == TARGET_WAITKIND_STOPPED)
- last_sig = ourstatus->sig ();
+ {
+ ptid_t ptid (current_event.dwProcessId, current_event.dwThreadId, 0);
+ windows_thread_info *th = find_thread (ptid);
+ th->last_sig = ourstatus->sig ();
+ }
return result;
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 0b89c751e89..71cb118f5d6 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -104,6 +104,10 @@ struct windows_thread_info
thread. */
DEBUG_EVENT last_event {};
+ /* The last signal reported for this thread, extracted out of
+ last_event. */
+ enum gdb_signal last_sig = GDB_SIGNAL_0;
+
/* The context of the thread, including any manipulations. */
union
{
@@ -147,7 +151,6 @@ struct windows_process_info
HANDLE handle = 0;
DWORD process_id = 0;
DWORD main_thread_id = 0;
- enum gdb_signal last_sig = GDB_SIGNAL_0;
/* Contents of $_siginfo */
EXCEPTION_RECORD siginfo_er {};
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 8ccb7048665..87cf5e1a4e8 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1258,6 +1258,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
});
th->resume ();
+ th->last_sig = GDB_SIGNAL_0;
}
/* Resume thread specified by ID, or all artificially suspended
@@ -1358,7 +1359,7 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not stopped for EXCEPTION_DEBUG_EVENT", sig);
}
- else if (sig == windows_process.last_sig)
+ else if (sig == th->last_sig)
continue_status = DBG_EXCEPTION_NOT_HANDLED;
else
#if 0
@@ -1382,11 +1383,9 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
}
#endif
DEBUG_EXCEPT ("Can only continue with received signal %d.",
- windows_process.last_sig);
+ th->last_sig);
}
- windows_process.last_sig = GDB_SIGNAL_0;
-
windows_process.with_context (th, [&] (auto *context)
{
if (step)
@@ -1486,8 +1485,6 @@ windows_nat_target::get_windows_debug_event
}
}
- windows_process.last_sig = GDB_SIGNAL_0;
-
if ((options & TARGET_WNOHANG) != 0 && !m_debug_event_pending)
{
ourstatus->set_ignore ();
@@ -1806,7 +1803,6 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
{
struct inferior *inf;
- windows_process.last_sig = GDB_SIGNAL_0;
windows_process.open_process_used = 0;
#ifdef __CYGWIN__
windows_process.cygwin_load_start = 0;
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index a4000980f67..c3e35a6f602 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -289,7 +289,6 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
{
struct process_info *proc;
- windows_process.last_sig = GDB_SIGNAL_0;
windows_process.handle = proch;
windows_process.process_id = pid;
windows_process.main_thread_id = 0;
@@ -396,6 +395,7 @@ continue_one_thread (thread_info *thread, int thread_id)
}
th->resume ();
+ th->last_sig = GDB_SIGNAL_0;
}
}
}
@@ -758,11 +758,11 @@ resume_one_thread (thread_info *thread, bool step, gdb_signal sig,
"Not stopped for EXCEPTION_DEBUG_EVENT.\n",
gdb_signal_to_string (sig)));
}
- else if (sig == windows_process.last_sig)
+ else if (sig == th->last_sig)
*continue_status = DBG_EXCEPTION_NOT_HANDLED;
else
OUTMSG (("Can only continue with received signal %s.\n",
- gdb_signal_to_string (windows_process.last_sig)));
+ gdb_signal_to_string (th->last_sig)));
}
win32_prepare_to_resume (th);
@@ -957,7 +957,6 @@ get_child_debug_event (DWORD *continue_status,
{
ptid_t ptid;
- windows_process.last_sig = GDB_SIGNAL_0;
ourstatus->set_spurious ();
*continue_status = DBG_CONTINUE;
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er per-thread state
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (26 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig " Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:33 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 29/47] Add backpointer from windows_thread_info to windows_process_info Pedro Alves
` (19 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
With non-stop mode support, each thread has its own "last event", and
so printing $_siginfo should print the siginfo of the selected thread.
Likewise, with all-stop and scheduler-locking.
This patch reworks the siginfo functions in gdb/windows-nat.c and
gdbserver/win32-low.cc to reuse the exception record already saved
within each thread's 'last_event' field.
Here's an example of what you'll see after the whole non-stop series:
(gdb) thread apply all p -pretty -- $_siginfo
Thread 3 (Thread 2612.0x1470):
$1 = {
ExceptionCode = DBG_CONTROL_C,
ExceptionFlags = 0,
ExceptionRecord = 0x0,
ExceptionAddress = 0x7ffd0583e929 <KERNELBASE!EncodeRemotePointer+8249>,
NumberParameters = 0,
{
ExceptionInformation = {0 <repeats 15 times>},
AccessViolationInformation = {
Type = READ_ACCESS_VIOLATION,
Address = 0x0
}
}
}
Thread 2 (Thread 2612.0x1704):
$2 = {
ExceptionCode = SINGLE_STEP,
ExceptionFlags = 0,
ExceptionRecord = 0x0,
ExceptionAddress = 0x7ffd080ad6e4 <ntdll!ZwDelayExecution+20>,
NumberParameters = 0,
{
ExceptionInformation = {0 <repeats 15 times>},
AccessViolationInformation = {
Type = READ_ACCESS_VIOLATION,
Address = 0x0
}
}
}
Thread 1 (Thread 2612.0x434):
$3 = {
ExceptionCode = BREAKPOINT,
ExceptionFlags = 0,
ExceptionRecord = 0x0,
ExceptionAddress = 0x7ff6f691174c <main+185>,
NumberParameters = 1,
{
ExceptionInformation = {0 <repeats 15 times>},
AccessViolationInformation = {
Type = READ_ACCESS_VIOLATION,
Address = 0x0
}
}
}
(gdb)
This was in non-stop mode, and the program originally had two threads.
Thread 1 stopped for a breakpoint, then thread 2 was manually
interrupted/paused and then single-stepped. And then I typed Ctrl-C
in the inferior's terminal, which made Windows inject thread 3 in the
inferior, and report a DBG_CONTROL_C exception for it.
Change-Id: I5d4f1b62f59e8aef3606642c6524df2362b0fb7d
---
gdb/nat/windows-nat.c | 2 --
gdb/nat/windows-nat.h | 3 ---
gdb/windows-nat.c | 33 ++++++++++++++++-----------------
gdbserver/win32-low.cc | 27 ++++++++++++++-------------
4 files changed, 30 insertions(+), 35 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 864788ec125..6128b83bea4 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -357,8 +357,6 @@ windows_process_info::handle_exception (DEBUG_EVENT ¤t_event,
DWORD code = rec->ExceptionCode;
handle_exception_result result = HANDLE_EXCEPTION_HANDLED;
- memcpy (&siginfo_er, rec, sizeof siginfo_er);
-
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION:
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 71cb118f5d6..8da9ca6e78f 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -152,9 +152,6 @@ struct windows_process_info
DWORD process_id = 0;
DWORD main_thread_id = 0;
- /* Contents of $_siginfo */
- EXCEPTION_RECORD siginfo_er {};
-
#ifdef __x86_64__
/* The target is a WOW64 process */
bool wow64_process = false;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 87cf5e1a4e8..09c2d224fd0 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -2888,7 +2888,6 @@ windows_nat_target::mourn_inferior ()
CHECK (CloseHandle (windows_process.handle));
windows_process.open_process_used = 0;
}
- windows_process.siginfo_er.ExceptionCode = 0;
inf_child_target::mourn_inferior ();
}
@@ -3005,8 +3004,15 @@ static enum target_xfer_status
windows_xfer_siginfo (gdb_byte *readbuf, ULONGEST offset, ULONGEST len,
ULONGEST *xfered_len)
{
- char *buf = (char *) &windows_process.siginfo_er;
- size_t bufsize = sizeof (windows_process.siginfo_er);
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+
+ if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ return TARGET_XFER_E_IO;
+
+ EXCEPTION_RECORD &er = th->last_event.u.Exception.ExceptionRecord;
+
+ char *buf = (char *) &er;
+ size_t bufsize = sizeof (er);
#ifdef __x86_64__
EXCEPTION_RECORD32 er32;
@@ -3015,23 +3021,16 @@ windows_xfer_siginfo (gdb_byte *readbuf, ULONGEST offset, ULONGEST len,
buf = (char *) &er32;
bufsize = sizeof (er32);
- er32.ExceptionCode = windows_process.siginfo_er.ExceptionCode;
- er32.ExceptionFlags = windows_process.siginfo_er.ExceptionFlags;
- er32.ExceptionRecord
- = (uintptr_t) windows_process.siginfo_er.ExceptionRecord;
- er32.ExceptionAddress
- = (uintptr_t) windows_process.siginfo_er.ExceptionAddress;
- er32.NumberParameters = windows_process.siginfo_er.NumberParameters;
- int i;
- for (i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
- er32.ExceptionInformation[i]
- = windows_process.siginfo_er.ExceptionInformation[i];
+ er32.ExceptionCode = er.ExceptionCode;
+ er32.ExceptionFlags = er.ExceptionFlags;
+ er32.ExceptionRecord = (uintptr_t) er.ExceptionRecord;
+ er32.ExceptionAddress = (uintptr_t) er.ExceptionAddress;
+ er32.NumberParameters = er.NumberParameters;
+ for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
+ er32.ExceptionInformation[i] = er.ExceptionInformation[i];
}
#endif
- if (windows_process.siginfo_er.ExceptionCode == 0)
- return TARGET_XFER_E_IO;
-
if (readbuf == nullptr)
return TARGET_XFER_E_IO;
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index c3e35a6f602..c95ec112bf7 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -654,7 +654,6 @@ win32_clear_process ()
}
for_each_thread (delete_thread_info);
- windows_process.siginfo_er.ExceptionCode = 0;
}
/* Implementation of target_ops::kill. */
@@ -1284,14 +1283,18 @@ win32_process_target::qxfer_siginfo (const char *annex,
unsigned const char *writebuf,
CORE_ADDR offset, int len)
{
- if (windows_process.siginfo_er.ExceptionCode == 0)
+ windows_thread_info *th = windows_process.find_thread (current_thread->id);
+
+ if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
return -1;
if (readbuf == nullptr)
return -1;
- char *buf = (char *) &windows_process.siginfo_er;
- size_t bufsize = sizeof (windows_process.siginfo_er);
+ EXCEPTION_RECORD &er = th->last_event.u.Exception.ExceptionRecord;
+
+ char *buf = (char *) &er;
+ size_t bufsize = sizeof (er);
#ifdef __x86_64__
EXCEPTION_RECORD32 er32;
@@ -1300,17 +1303,15 @@ win32_process_target::qxfer_siginfo (const char *annex,
buf = (char *) &er32;
bufsize = sizeof (er32);
- er32.ExceptionCode = windows_process.siginfo_er.ExceptionCode;
- er32.ExceptionFlags = windows_process.siginfo_er.ExceptionFlags;
+ er32.ExceptionCode = er.ExceptionCode;
+ er32.ExceptionFlags = er.ExceptionFlags;
er32.ExceptionRecord
- = (uintptr_t) windows_process.siginfo_er.ExceptionRecord;
+ = (uintptr_t) er.ExceptionRecord;
er32.ExceptionAddress
- = (uintptr_t) windows_process.siginfo_er.ExceptionAddress;
- er32.NumberParameters = windows_process.siginfo_er.NumberParameters;
- int i;
- for (i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
- er32.ExceptionInformation[i]
- = windows_process.siginfo_er.ExceptionInformation[i];
+ = (uintptr_t) er.ExceptionAddress;
+ er32.NumberParameters = er.NumberParameters;
+ for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
+ er32.ExceptionInformation[i] = er.ExceptionInformation[i];
}
#endif
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 29/47] Add backpointer from windows_thread_info to windows_process_info
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (27 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er " Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 30/47] Windows gdb+gdbserver: Share $_siginfo reading code Pedro Alves
` (18 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
The next patch will move some duplicated code in gdb and gdbserver to
gdb/nat/windows-nat.c, where it would be convenient to get at the
Windows process info of a given Windows thread info, from within a
windows_thread_info method.
I first thought of passing down the windows_process_info pointer as
argument to the windows_thread_info method, but that looked a bit odd.
I think it looks better to just add a back pointer, so that's what
this patch does. The following patch will then add a use of it.
I suspect this will help moving more duplicated code to
gdb/nat/windows-nat.c in the future, too.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I47fc0d3323be5b6f6fcfe912b768051a41910666
---
gdb/nat/windows-nat.h | 10 ++++++++--
gdb/windows-nat.c | 2 +-
gdbserver/win32-low.cc | 2 +-
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 8da9ca6e78f..cba8098fb39 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -48,13 +48,16 @@ struct pending_stop
target_waitstatus status;
};
+struct windows_process_info;
/* Thread information structure used to track extra information about
each thread. */
struct windows_thread_info
{
- windows_thread_info (DWORD tid_, HANDLE h_, CORE_ADDR tlb)
- : tid (tid_),
+ windows_thread_info (windows_process_info *proc_,
+ DWORD tid_, HANDLE h_, CORE_ADDR tlb)
+ : proc (proc_),
+ tid (tid_),
h (h_),
thread_local_base (tlb)
{
@@ -73,6 +76,9 @@ struct windows_thread_info
the next call. */
const char *thread_name ();
+ /* The process this thread belongs to. */
+ windows_process_info *const proc;
+
/* The Win32 thread identifier. */
DWORD tid;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 09c2d224fd0..1ced313d7b1 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -607,7 +607,7 @@ 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 (ptid.lwp (), h, base);
+ th = new windows_thread_info (&windows_process, ptid.lwp (), h, base);
windows_process.thread_list.emplace_back (th);
/* Add this new thread to the list of threads.
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index c95ec112bf7..4e2d95bd059 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -149,7 +149,7 @@ child_add_thread (DWORD pid, DWORD tid, HANDLE h, void *tlb)
if (windows_process.wow64_process)
base += 2 * 4096; /* page size = 4096 */
#endif
- th = new windows_thread_info (tid, h, base);
+ th = new windows_thread_info (&windows_process, tid, h, base);
find_process_pid (pid)->add_thread (ptid, th);
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 30/47] Windows gdb+gdbserver: Share $_siginfo reading code
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (28 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 29/47] Add backpointer from windows_thread_info to windows_process_info Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop Pedro Alves
` (17 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Both GDB and GDBserver have similar code to read the $_siginfo data.
This patch moves the bulk of it to gdb/nat/windows-nat.c so it can be
shared.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I58f9074caf6362274453c78ed1fc9e31249f6854
---
gdb/nat/windows-nat.c | 49 ++++++++++++++++++++++++++++++++++++++++++
gdb/nat/windows-nat.h | 9 ++++++++
gdb/windows-nat.c | 40 +++-------------------------------
gdbserver/win32-low.cc | 42 ++++--------------------------------
4 files changed, 65 insertions(+), 75 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 6128b83bea4..a548acc97d3 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -153,6 +153,55 @@ windows_thread_info::thread_name ()
return name.get ();
}
+/* Read Windows signal info. See nat/windows-nat.h. */
+
+bool
+windows_thread_info::xfer_siginfo (gdb_byte *readbuf,
+ ULONGEST offset, ULONGEST len,
+ ULONGEST *xfered_len)
+{
+ if (last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
+ return false;
+
+ if (readbuf == nullptr)
+ return false;
+
+ EXCEPTION_RECORD &er = last_event.u.Exception.ExceptionRecord;
+
+ char *buf = (char *) &er;
+ size_t bufsize = sizeof (er);
+
+#ifdef __x86_64__
+ EXCEPTION_RECORD32 er32;
+ if (proc->wow64_process)
+ {
+ buf = (char *) &er32;
+ bufsize = sizeof (er32);
+
+ er32.ExceptionCode = er.ExceptionCode;
+ er32.ExceptionFlags = er.ExceptionFlags;
+ er32.ExceptionRecord
+ = (uintptr_t) er.ExceptionRecord;
+ er32.ExceptionAddress
+ = (uintptr_t) er.ExceptionAddress;
+ er32.NumberParameters = er.NumberParameters;
+ for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
+ er32.ExceptionInformation[i] = er.ExceptionInformation[i];
+ }
+#endif
+
+ if (offset > bufsize)
+ return false;
+
+ if (offset + len > bufsize)
+ len = bufsize - offset;
+
+ memcpy (readbuf, buf + offset, len);
+ *xfered_len = len;
+
+ return true;
+}
+
/* Try to determine the executable filename.
EXE_NAME_RET is a pointer to a buffer whose size is EXE_NAME_MAX_LEN.
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index cba8098fb39..d0978f899fd 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -76,6 +76,15 @@ struct windows_thread_info
the next call. */
const char *thread_name ();
+ /* Read LEN bytes of the thread's $_siginfo into READBUF, starting
+ at OFFSET. Store the number of actually-read bytes in
+ XFERED_LEN. Returns true on success, false on failure. Passing
+ READBUF as NULL indicates that the caller is trying to write to
+ $_siginfo, which is a failure case. */
+ bool xfer_siginfo (gdb_byte *readbuf,
+ ULONGEST offset, ULONGEST len,
+ ULONGEST *xfered_len);
+
/* The process this thread belongs to. */
windows_process_info *const proc;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 1ced313d7b1..bd3bc3701a6 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -3006,44 +3006,10 @@ windows_xfer_siginfo (gdb_byte *readbuf, ULONGEST offset, ULONGEST len,
{
windows_thread_info *th = windows_process.find_thread (inferior_ptid);
- if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
- return TARGET_XFER_E_IO;
-
- EXCEPTION_RECORD &er = th->last_event.u.Exception.ExceptionRecord;
-
- char *buf = (char *) &er;
- size_t bufsize = sizeof (er);
-
-#ifdef __x86_64__
- EXCEPTION_RECORD32 er32;
- if (windows_process.wow64_process)
- {
- buf = (char *) &er32;
- bufsize = sizeof (er32);
-
- er32.ExceptionCode = er.ExceptionCode;
- er32.ExceptionFlags = er.ExceptionFlags;
- er32.ExceptionRecord = (uintptr_t) er.ExceptionRecord;
- er32.ExceptionAddress = (uintptr_t) er.ExceptionAddress;
- er32.NumberParameters = er.NumberParameters;
- for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
- er32.ExceptionInformation[i] = er.ExceptionInformation[i];
- }
-#endif
-
- if (readbuf == nullptr)
- return TARGET_XFER_E_IO;
-
- if (offset > bufsize)
+ if (th->xfer_siginfo (readbuf, offset, len, xfered_len))
+ return TARGET_XFER_OK;
+ else
return TARGET_XFER_E_IO;
-
- if (offset + len > bufsize)
- len = bufsize - offset;
-
- memcpy (readbuf, buf + offset, len);
- *xfered_len = len;
-
- return TARGET_XFER_OK;
}
enum target_xfer_status
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 4e2d95bd059..834af706786 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -1285,45 +1285,11 @@ win32_process_target::qxfer_siginfo (const char *annex,
{
windows_thread_info *th = windows_process.find_thread (current_thread->id);
- if (th->last_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
- return -1;
-
- if (readbuf == nullptr)
- return -1;
-
- EXCEPTION_RECORD &er = th->last_event.u.Exception.ExceptionRecord;
-
- char *buf = (char *) &er;
- size_t bufsize = sizeof (er);
-
-#ifdef __x86_64__
- EXCEPTION_RECORD32 er32;
- if (windows_process.wow64_process)
- {
- buf = (char *) &er32;
- bufsize = sizeof (er32);
-
- er32.ExceptionCode = er.ExceptionCode;
- er32.ExceptionFlags = er.ExceptionFlags;
- er32.ExceptionRecord
- = (uintptr_t) er.ExceptionRecord;
- er32.ExceptionAddress
- = (uintptr_t) er.ExceptionAddress;
- er32.NumberParameters = er.NumberParameters;
- for (int i = 0; i < EXCEPTION_MAXIMUM_PARAMETERS; i++)
- er32.ExceptionInformation[i] = er.ExceptionInformation[i];
- }
-#endif
-
- if (offset > bufsize)
+ ULONGEST xfered_len;
+ if (th->xfer_siginfo (readbuf, offset, len, &xfered_len))
+ return xfered_len;
+ else
return -1;
-
- if (offset + len > bufsize)
- len = bufsize - offset;
-
- memcpy (readbuf, buf + offset, len);
-
- return len;
}
bool
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (29 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 30/47] Windows gdb+gdbserver: Share $_siginfo reading code Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:36 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 32/47] Windows gdb: Change serial_event management Pedro Alves
` (16 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
After the previous patches, struct pending_stop only contains one
field. So move that field into the windows_thread_info structure
directly, and eliminate struct pending_stop.
Change-Id: I7955884b3f378d8b39b908f6252d215f6568b367
---
gdb/nat/windows-nat.h | 17 +++++------------
gdb/windows-nat.c | 14 +++++++-------
gdbserver/win32-low.cc | 12 ++++++------
3 files changed, 18 insertions(+), 25 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index d0978f899fd..4ec2f4575be 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -38,16 +38,6 @@
namespace windows_nat
{
-/* Info about a potential pending stop. Each thread holds one of
- these. See "windows_thread_info::pending_stop" for more
- information. */
-struct pending_stop
-{
- /* The target waitstatus we computed. TARGET_WAITKIND_IGNORE if the
- thread does not have a pending stop. */
- target_waitstatus status;
-};
-
struct windows_process_info;
/* Thread information structure used to track extra information about
@@ -112,8 +102,11 @@ struct windows_thread_info
effect of trying to single step thread A -- leaving all other
threads suspended -- and then seeing a stop in thread B. To handle
this scenario, we queue all such "pending" stops here, and then
- process them once the step has completed. See PR gdb/22992. */
- struct pending_stop pending_stop {};
+ process them once the step has completed. See PR gdb/22992.
+
+ TARGET_WAITKIND_IGNORE if the thread does not have a pending
+ stop. */
+ target_waitstatus pending_status;
/* The last Windows event returned by WaitForDebugEvent for this
thread. */
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index bd3bc3701a6..d5112e2e2e4 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1273,7 +1273,7 @@ windows_nat_target::windows_continue (DWORD continue_status, int id,
{
if ((id == -1 || id == (int) th->tid)
&& !th->suspended
- && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
DEBUG_EVENTS ("got matching pending stop event "
"for 0x%x, not resuming",
@@ -1465,18 +1465,18 @@ windows_nat_target::get_windows_debug_event
DWORD thread_id = 0;
/* If there is a relevant pending stop, report it now. See the
- comment by the definition of "windows_thread_info::pending_stop"
+ comment by the definition of "windows_thread_info::pending_status"
for details on why this is needed. */
for (auto &th : windows_process.thread_list)
{
if (!th->suspended
- && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
thread_id = th->tid;
- *ourstatus = th->pending_stop.status;
- th->pending_stop.status.set_ignore ();
+ *ourstatus = th->pending_status;
+ th->pending_status.set_ignore ();
*current_event = th->last_event;
ptid_t ptid (windows_process.process_id, thread_id);
@@ -1701,7 +1701,7 @@ windows_nat_target::get_windows_debug_event
if (th->suspended)
{
/* Pending stop. See the comment by the definition of
- "pending_stops" for details on why this is needed. */
+ "pending_status" for details on why this is needed. */
DEBUG_EVENTS ("get_windows_debug_event - "
"unexpected stop in suspended thread 0x%x",
thread_id);
@@ -1717,7 +1717,7 @@ windows_nat_target::get_windows_debug_event
th->pc_adjusted = false;
}
- th->pending_stop.status = *ourstatus;
+ th->pending_status = *ourstatus;
ourstatus->set_ignore ();
continue_last_debug_event_main_thread
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 834af706786..4aaadf0d671 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -825,7 +825,7 @@ win32_process_target::resume (thread_resume *resume_info, size_t n)
}
if (!th->suspended
- && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
any_pending = true;
});
@@ -970,10 +970,10 @@ get_child_debug_event (DWORD *continue_status,
auto *th = static_cast<windows_thread_info *> (thread.target_data ());
if (!th->suspended
- && th->pending_stop.status.kind () != TARGET_WAITKIND_IGNORE)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
- *ourstatus = th->pending_stop.status;
- th->pending_stop.status.set_ignore ();
+ *ourstatus = th->pending_status;
+ th->pending_status.set_ignore ();
*current_event = th->last_event;
ptid = debug_event_ptid (current_event);
switch_to_thread (find_thread_ptid (ptid));
@@ -1133,13 +1133,13 @@ get_child_debug_event (DWORD *continue_status,
if (th != nullptr && th->suspended)
{
/* Pending stop. See the comment by the definition of
- "windows_thread_info::pending_stop" for details on why this
+ "windows_thread_info::pending_status" for details on why this
is needed. */
OUTMSG2 (("get_windows_debug_event - "
"unexpected stop in suspended thread 0x%x\n",
th->tid));
maybe_adjust_pc (*current_event);
- th->pending_stop.status = *ourstatus;
+ th->pending_status = *ourstatus;
ourstatus->set_spurious ();
}
else
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 32/47] Windows gdb: Change serial_event management
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (30 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:37 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 33/47] Windows gdb: cygwin_set_dr => windows_set_dr, etc Pedro Alves
` (15 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
windows_nat_target::windows_continue, when it finds a resumed thread
that has a pending event, does:
/* 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 we have more than one pending event ready to be consumed, and,
windows_nat_target::wait returns without calling
windows_nat_target::windows_continue, which it will with the non-stop
support in the following patch, then we will miss waking up the event
loop.
This patch makes windows-nat.c manage the serial_event similarly to
how linux-nat.c does it. Clear it on entry to
windows_nat_target::wait, and set it if there may be more events to
process. With this, there's no need to set it from
windows_nat_target::wait_for_debug_event_main_thread, so the patch
also makes us not do it.
Change-Id: I44e1682721aa4866f1dbb052b3cfb4870fb13579
---
gdb/windows-nat.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index d5112e2e2e4..d5acbc33f69 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -525,7 +525,6 @@ windows_nat_target::wait_for_debug_event_main_thread (DEBUG_EVENT *event)
{
*event = m_last_debug_event;
m_debug_event_pending = false;
- serial_event_clear (m_wait_event);
}
else
wait_for_debug_event (event, INFINITE);
@@ -1735,6 +1734,11 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
{
int pid = -1;
+ /* serial_event is a manual-reset event. Clear it first. We'll set
+ it again if we may need to wake up the event loop to get here
+ again. */
+ serial_event_clear (m_wait_event);
+
/* We loop when we get a non-standard exception rather than return
with a SPURIOUS because resume can try and step or modify things,
which needs a current_thread->h. But some of these exceptions mark
@@ -1783,6 +1787,10 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
thr->suspend ();
}
+ /* If something came out, assume there may be more. This is
+ needed because there may be pending events ready to
+ consume. */
+ serial_event_set (m_wait_event);
return result;
}
else
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 33/47] Windows gdb: cygwin_set_dr => windows_set_dr, etc.
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (31 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 32/47] Windows gdb: Change serial_event management Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending Pedro Alves
` (14 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
The Windows backend functions that manipulate the x86 debug registers
are called "cygwin_foo", which is outdated, because native MinGW gdb
also uses those functions, they are not Cygwin-specific. Rename them
to "windows_foo" to avoid confusion.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: I46df3b44f5272adadf960da398342a3cbdb98533
---
gdb/windows-nat.c | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index d5acbc33f69..2c76848df58 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -207,11 +207,11 @@ enum
debug_prefixed_printf_cond (debug_exceptions, "windows except", fmt, \
## __VA_ARGS__)
-static void cygwin_set_dr (int i, CORE_ADDR addr);
-static void cygwin_set_dr7 (unsigned long val);
-static CORE_ADDR cygwin_get_dr (int i);
-static unsigned long cygwin_get_dr6 (void);
-static unsigned long cygwin_get_dr7 (void);
+static void windows_set_dr (int i, CORE_ADDR addr);
+static void windows_set_dr7 (unsigned long val);
+static CORE_ADDR windows_get_dr (int i);
+static unsigned long windows_get_dr6 (void);
+static unsigned long windows_get_dr7 (void);
/* User options. */
static bool new_console = false;
@@ -3090,11 +3090,11 @@ void _initialize_windows_nat ();
void
_initialize_windows_nat ()
{
- x86_dr_low.set_control = cygwin_set_dr7;
- x86_dr_low.set_addr = cygwin_set_dr;
- x86_dr_low.get_addr = cygwin_get_dr;
- x86_dr_low.get_status = cygwin_get_dr6;
- x86_dr_low.get_control = cygwin_get_dr7;
+ x86_dr_low.set_control = windows_set_dr7;
+ x86_dr_low.set_addr = windows_set_dr;
+ x86_dr_low.get_addr = windows_get_dr;
+ x86_dr_low.get_status = windows_get_dr6;
+ x86_dr_low.get_control = windows_get_dr7;
/* x86_dr_low.debug_register_length field is set by
calling x86_set_debug_register_length function
@@ -3199,10 +3199,10 @@ Use \"%ps\" or \"%ps\" command to load executable/libraries directly."),
Here we just store the address in dr array, the registers will be
actually set up when windows_continue is called. */
static void
-cygwin_set_dr (int i, CORE_ADDR addr)
+windows_set_dr (int i, CORE_ADDR addr)
{
if (i < 0 || i > 3)
- internal_error (_("Invalid register %d in cygwin_set_dr.\n"), i);
+ internal_error (_("Invalid register %d in windows_set_dr.\n"), i);
for (auto &th : windows_process.thread_list)
th->debug_registers_changed = true;
@@ -3212,7 +3212,7 @@ cygwin_set_dr (int i, CORE_ADDR addr)
register. Here we just store the address in D_REGS, the watchpoint
will be actually set up in windows_wait. */
static void
-cygwin_set_dr7 (unsigned long val)
+windows_set_dr7 (unsigned long val)
{
for (auto &th : windows_process.thread_list)
th->debug_registers_changed = true;
@@ -3221,7 +3221,7 @@ cygwin_set_dr7 (unsigned long val)
/* Get the value of debug register I from the inferior. */
static CORE_ADDR
-cygwin_get_dr (int i)
+windows_get_dr (int i)
{
windows_thread_info *th = windows_process.find_thread (inferior_ptid);
@@ -3252,18 +3252,18 @@ cygwin_get_dr (int i)
inferior. */
static unsigned long
-cygwin_get_dr6 (void)
+windows_get_dr6 (void)
{
- return cygwin_get_dr (6);
+ return windows_get_dr (6);
}
/* Get the value of the DR7 debug status register from the
inferior. */
static unsigned long
-cygwin_get_dr7 (void)
+windows_get_dr7 (void)
{
- return cygwin_get_dr (7);
+ return windows_get_dr (7);
}
/* Determine if the thread referenced by "ptid" is alive
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (32 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 33/47] Windows gdb: cygwin_set_dr => windows_set_dr, etc Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-30 20:43 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 35/47] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
` (13 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
Several watchpoint-related testcases, such as
gdb.threads/watchthreads.exp for example, when tested with the backend
in non-stop mode, exposed an interesting detail of the Windows debug
API that wasn't considered before. The symptom observed is spurious
SIGTRAPs, like:
Thread 1 "watchthreads" received signal SIGTRAP, Trace/breakpoint trap.
0x00000001004010b1 in main () at .../src/gdb/testsuite/gdb.threads/watchthreads.c:48
48 args[i] = 1; usleep (1); /* Init value. */
After a good amount of staring at logs and headscratching, I realized
the problem:
#0 - It all starts with the fact that multiple threads can hit an
event at the same time. Say, a watchpoint for thread A, and a
breakpoint for thread B.
#1 - Say, WaitForDebugEvent reports the breakpoint hit for thread B
first, then GDB for some reason decides to update debug
registers, and continue. Updating debug registers means writing
the debug registers to _all_ threads, with SetThreadContext.
#2 - WaitForDebugEvent reports the watchpoint hit for thread A.
Watchpoint hits are reported as EXCEPTION_SINGLE_STEP.
#3 - windows-nat checks the Dr6 debug register to check if the step
was a watchpoint or hardware breakpoint stop, and finds that Dr6
is completely cleared. So windows-nat reports a plain SIGTRAP
(given EXCEPTION_SINGLE_STEP) to the core.
#4 - Thread A was not supposed to be stepping, so infrun reports the
SIGTRAP to the user as a random signal.
The strange part is #3 above. Why was Dr6 cleared?
Turns out that (at least in Windows 10 & 11), writing to _any_ debug
register has the side effect of clearing Dr6, even if you write the
same values the registers already had, back to the registers.
I confirmed it clearly by adding this hack to GDB:
if (th->context.ContextFlags == 0)
{
th->context.ContextFlags = CONTEXT_DEBUGGER_DR;
/* Get current values of debug registers. */
CHECK (GetThreadContext (th->h, &th->context));
DEBUG_EVENTS ("For 0x%x (once), Dr6=0x%llx", th->tid, th->context.Dr6);
/* Write debug registers back to thread, same values,
and re-read them. */
CHECK (SetThreadContext (th->h, &th->context));
CHECK (GetThreadContext (th->h, &th->context));
DEBUG_EVENTS ("For 0x%x (twice), Dr6=0x%llx", th->tid, th->context.Dr6);
}
Which showed Dr6=0 after the write + re-read:
[windows events] fill_thread_context: For 0x6a0 (once), Dr6=0xffff0ff1
[windows events] fill_thread_context: For 0x6a0 (twice), Dr6=0x0
This commit fixes the issue by detecting that a thread has a pending
watchpoint hit to report (Dr6 has interesting bits set), and if so,
avoid mofiying any debug register. Instead, let the pending
watchpoint hit be reported by WaitForDebugEvent. If infrun did want
to modify watchpoints, it will still be done when the thread is
eventually re-resumed after the pending watchpoint hit is reported.
(infrun knows how to gracefully handle the case of a watchpoint hit
for a watchpoint that has since been deleted.)
Change-Id: I21a3daa9e34eecfa054f0fea706e5ab40aabe70a
---
gdb/nat/windows-nat.h | 7 ++++
gdb/windows-nat.c | 80 ++++++++++++++++++++++++++++++++++--------
gdbserver/win32-low.cc | 8 +++++
gdbserver/win32-low.h | 2 ++
4 files changed, 82 insertions(+), 15 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 4ec2f4575be..795d6c485fe 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -211,6 +211,13 @@ struct windows_process_info
virtual bool handle_access_violation (const EXCEPTION_RECORD *rec) = 0;
+ /* Fill in the thread's CONTEXT/WOW64_CONTEXT, if it wasn't filled
+ in yet.
+
+ This function must be supplied by the embedding application. */
+
+ virtual void fill_thread_context (windows_thread_info *th) = 0;
+
handle_exception_result handle_exception
(DEBUG_EVENT ¤t_event,
struct target_waitstatus *ourstatus, bool debug_exceptions);
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 2c76848df58..d336889a251 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -117,6 +117,7 @@ struct windows_per_inferior : public windows_process_info
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
void invalidate_context (windows_thread_info *th);
+ void fill_thread_context (windows_thread_info *th) override;
void continue_one_thread (windows_thread_info *th,
windows_continue_flags cont_flags);
@@ -740,15 +741,8 @@ windows_fetch_one_register (struct regcache *regcache,
}
void
-windows_nat_target::fetch_registers (struct regcache *regcache, int r)
+windows_per_inferior::fill_thread_context (windows_thread_info *th)
{
- windows_thread_info *th = windows_process.find_thread (regcache->ptid ());
-
- /* Check if TH exists. Windows sometimes uses a non-existent
- thread id in its events. */
- if (th == NULL)
- return;
-
windows_process.with_context (th, [&] (auto *context)
{
if (context->ContextFlags == 0)
@@ -757,6 +751,19 @@ windows_nat_target::fetch_registers (struct regcache *regcache, int r)
CHECK (get_thread_context (th->h, context));
}
});
+}
+
+void
+windows_nat_target::fetch_registers (struct regcache *regcache, int r)
+{
+ windows_thread_info *th = windows_process.find_thread (regcache->ptid ());
+
+ /* Check if TH exists. Windows sometimes uses a non-existent
+ thread id in its events. */
+ if (th == nullptr)
+ return;
+
+ windows_process.fill_thread_context (th);
if (r < 0)
for (r = 0; r < gdbarch_num_regs (regcache->arch()); r++)
@@ -1231,13 +1238,56 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
{
if (th->debug_registers_changed)
{
- context->ContextFlags |= WindowsContext<decltype(context)>::debug;
- context->Dr0 = state->dr_mirror[0];
- context->Dr1 = state->dr_mirror[1];
- context->Dr2 = state->dr_mirror[2];
- context->Dr3 = state->dr_mirror[3];
- context->Dr6 = DR6_CLEAR_VALUE;
- context->Dr7 = state->dr_control_mirror;
+ windows_process.fill_thread_context (th);
+
+ gdb_assert ((context->ContextFlags & CONTEXT_DEBUG_REGISTERS) != 0);
+
+ /* Check whether the thread has Dr6 set indicating a
+ watchpoint hit, and we haven't seen the watchpoint event
+ yet (reported as
+ EXCEPTION_SINGLE_STEP/STATUS_WX86_SINGLE_STEP). In that
+ case, don't change the debug registers. Changing debug
+ registers, even if to the same values, makes the kernel
+ clear Dr6. The result would be we would lose the
+ unreported watchpoint hit. */
+ if ((context->Dr6 & ~DR6_CLEAR_VALUE) != 0)
+ {
+ if (th->last_event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
+ && (th->last_event.u.Exception.ExceptionRecord.ExceptionCode
+ == EXCEPTION_SINGLE_STEP
+ || (th->last_event.u.Exception.ExceptionRecord.ExceptionCode
+ == STATUS_WX86_SINGLE_STEP)))
+ {
+ DEBUG_EVENTS ("0x%x already reported watchpoint", th->tid);
+ }
+ else
+ {
+ DEBUG_EVENTS ("0x%x last reported something else (0x%x)",
+ th->tid,
+ th->last_event.dwDebugEventCode);
+
+ /* Don't touch debug registers. Let the pending
+ watchpoint event be reported instead. We will
+ update the debug registers later when the thread
+ is re-resumed by the core after the watchpoint
+ event. */
+ context->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
+ }
+ }
+ else
+ DEBUG_EVENTS ("0x%x has no dr6 set", th->tid);
+
+ if ((context->ContextFlags & CONTEXT_DEBUG_REGISTERS) != 0)
+ {
+ DEBUG_EVENTS ("0x%x changing dregs", th->tid);
+ context->Dr0 = state->dr_mirror[0];
+ context->Dr1 = state->dr_mirror[1];
+ context->Dr2 = state->dr_mirror[2];
+ context->Dr3 = state->dr_mirror[3];
+ context->Dr6 = DR6_CLEAR_VALUE;
+ context->Dr7 = state->dr_control_mirror;
+ }
+
th->debug_registers_changed = false;
}
if (context->ContextFlags)
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 4aaadf0d671..1a80269678e 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -123,6 +123,14 @@ win32_require_context (windows_thread_info *th)
/* See nat/windows-nat.h. */
+void
+gdbserver_windows_process::fill_thread_context (windows_thread_info *th)
+{
+ win32_require_context (th);
+}
+
+/* See nat/windows-nat.h. */
+
windows_thread_info *
gdbserver_windows_process::find_thread (ptid_t ptid)
{
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index 1891f82b046..123fbc76f57 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -188,6 +188,8 @@ struct gdbserver_windows_process : public windows_nat::windows_process_info
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
+ void fill_thread_context (windows_nat::windows_thread_info *th) override;
+
int attaching = 0;
/* A status that hasn't been reported to the core yet, and so
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 35/47] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (33 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
` (12 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 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
---
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 a548acc97d3..907d39f317a 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -912,6 +912,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 795d6c485fe..667fc832292 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -532,6 +532,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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (34 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 35/47] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:44 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
` (11 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
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.
Change-Id: Ifaa96b4a41bb83d868079af4d47633715c0e1940
---
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 20649ace942..826093f79f9 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -367,6 +367,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 b09e7c1df8a..869ae2a67a9 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -319,6 +319,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 4de4cc83cfa..94f3f6c7075 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1292,13 +1292,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;
@@ -1328,38 +1328,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)
{
@@ -1489,7 +1458,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;
@@ -1577,7 +1546,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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (35 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:52 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 38/47] Introduce windows_nat::event_code_to_string Pedro Alves
` (10 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 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/windows-nat.c | 170 ++++++++++++++++++++++++++++++++--------------
1 file changed, 118 insertions(+), 52 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index d336889a251..492d2e3f55b 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -107,6 +107,26 @@ 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_process_info *proc,
+ DWORD tid, HANDLE h, CORE_ADDR tlb)
+ : windows_thread_info (proc, tid, h, tlb)
+ {}
+
+ ~windows_private_thread_info () override
+ {}
+};
+
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
@@ -124,8 +144,6 @@ struct windows_per_inferior : public 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;
@@ -415,6 +433,82 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
bool m_continued = false;
};
+/* Get the windows_thread_info object associated with THR. */
+
+static 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. */
+ return static_cast<windows_private_thread_info *> (thr->priv.get ());
+}
+
+/* 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. */
+
+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)));
+}
+
static void
check (BOOL ok, const char *file, int line)
{
@@ -562,10 +656,11 @@ windows_nat_target::continue_last_debug_event_main_thread
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;
+ auto *win_tgt = static_cast<windows_nat_target *> (get_native_target ());
+ thread_info *thr = win_tgt->find_thread (ptid);
+ if (thr == nullptr)
+ return nullptr;
+ return as_windows_thread_info (thr);
}
/* Invalidate TH's context. */
@@ -593,12 +688,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__
@@ -607,18 +701,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. */
@@ -627,15 +721,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.
@@ -647,12 +732,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. */
@@ -660,16 +739,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);
}
/* Fetches register number R from the given windows_thread_info,
@@ -1318,7 +1387,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
@@ -1336,9 +1405,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)
- windows_process.continue_one_thread (th.get (), cont_flags);
+ windows_process.continue_one_thread (th, cont_flags);
continue_last_debug_event_main_thread
(_("Failed to resume program execution"), continue_status,
@@ -1516,7 +1585,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)
@@ -1529,7 +1598,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_context (th.get ());
+ windows_process.invalidate_context (th);
return ptid;
}
}
@@ -1833,7 +1902,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 ();
}
@@ -2003,7 +2072,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;
@@ -2756,7 +2824,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,
@@ -2885,7 +2952,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 */
@@ -3254,7 +3320,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 : windows_process.thread_list)
+ for (auto *th : all_windows_threads ())
th->debug_registers_changed = true;
}
@@ -3264,7 +3330,7 @@ windows_set_dr (int i, CORE_ADDR addr)
static void
windows_set_dr7 (unsigned long val)
{
- for (auto &th : windows_process.thread_list)
+ for (auto *th : all_windows_threads ())
th->debug_registers_changed = true;
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 38/47] Introduce windows_nat::event_code_to_string
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (36 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
@ 2025-05-19 13:22 ` Pedro Alves
2025-05-28 19:53 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 39/47] Windows gdb: Add non-stop support Pedro Alves
` (9 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:22 UTC (permalink / raw)
To: gdb-patches
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.
Change-Id: Id38b7e30df182e4742f3179538de3c643cf42668
---
gdb/nat/windows-nat.c | 23 +++++++++++++++++++++
gdb/nat/windows-nat.h | 4 ++++
gdb/windows-nat.c | 37 +++++-----------------------------
gdbserver/win32-low.cc | 45 +++++-------------------------------------
4 files changed, 37 insertions(+), 72 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 907d39f317a..7e683fd8629 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -689,6 +689,29 @@ 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);
+ }
+}
+
+/* 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 667fc832292..19403d0e623 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -284,6 +284,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 492d2e3f55b..73ddfcb3b6a 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1616,13 +1616,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);
@@ -1654,10 +1655,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,
@@ -1666,10 +1663,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;
@@ -1686,10 +1679,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 ();
@@ -1718,10 +1707,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)
@@ -1739,10 +1724,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;
@@ -1759,10 +1740,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,
@@ -1782,10 +1759,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 1a80269678e..1d3b24540d6 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -1010,13 +1010,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,
@@ -1026,10 +1027,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);
@@ -1037,10 +1034,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)
@@ -1060,10 +1053,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
@@ -1080,10 +1069,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;
@@ -1093,10 +1078,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);
@@ -1104,10 +1085,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)
@@ -1116,20 +1093,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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 39/47] Windows gdb: Add non-stop support
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (37 preceding siblings ...)
2025-05-19 13:22 ` [PATCH v2 38/47] Introduce windows_nat::event_code_to_string Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-06-05 16:21 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context Pedro Alves
` (8 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
This patch adds non-stop support to the native Windows target.
This is made possible by the ContinueDebugEvent DBG_REPLY_LATER flag:
https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-continuedebugevent
Supported in Windows 10, version 1507 or above, this flag causes
dwThreadId to replay the existing breaking event after the target
continues. By calling the SuspendThread API against dwThreadId, a
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
debugger can resume other threads in the process and later return to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
the breaking.
^^^^^^^^^^^^
The patch adds a new comment section in gdb/windows-nat.c providing an
overall picture of how all-stop / non-stop work.
Without DBG_REPLY_LATER, if we SuspendThread the thread, and then
immediately ContinueDebugThread(DBG_CONTINUE) before getting back to
the prompt, we could still have non-stop mode working, however, then
users wouldn't have a chance to decide whether to pass the signal to
the inferior the next time they resume the program, as that is done by
passing DBG_EXCEPTION_NOT_HANDLED to ContinueDebugEvent, and that has
already been called.
The patch teaches the Windows native backend to use that
DBG_REPLY_LATER flag, and also adds support for target_stop, so the
core can pause threads at its discretion. This pausing does not use
the same mechanisms used in windows_nat_target::interrupt, as that
injects a new thread in the inferior. Instead, for each thread the
core wants paused, it uses SuspendThread, and enqueues a pending
GDB_SIGNAL_0 stop on the thread.
Since DBG_REPLY_LATER only exists on Windows 10 and later, we only
enable non-stop mode on Windows 10 and later.
There is no displaced stepping support, but that's "just" a missed
optimization to be done later.
Cygwin signals handling was a major headache, but I managed to get it
working. See the "Cygwin signals" description section I added at the
top of windows-nat.c.
Another interesting bit, is that the use DBG_REPLY_LATER caused one
problem with detach. The Windows kernel re-raises any exception
previously intercepted and deferred with DBG_REPLY_LATER in the
inferior after we detach. We need to flush those events, and suppress
those which aren't meant to be seen by the inferior (e.g.,
breakpoints, single-steps, any with matching "handle SIG nopass",
etc.), otherwise the inferior dies immediately after the detach, due
to an unhandled exception.
Change-Id: Id71aef461c43c244120635b5bedc638fe77c31fb
---
gdb/nat/windows-nat.c | 17 +-
gdb/nat/windows-nat.h | 51 +-
gdb/windows-nat.c | 1134 +++++++++++++++++++++++++++++++++++-----
gdbserver/win32-low.cc | 15 +-
gdbserver/win32-low.h | 4 +-
5 files changed, 1079 insertions(+), 142 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 7e683fd8629..e1c2cd79c20 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -721,17 +721,20 @@ get_last_debug_event_ptid ()
/* See nat/windows-nat.h. */
BOOL
-continue_last_debug_event (DWORD continue_status, bool debug_events)
+continue_last_debug_event (DWORD cont_status, bool debug_events)
{
- DEBUG_EVENTS ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
- (unsigned) last_wait_event.dwProcessId,
- (unsigned) last_wait_event.dwThreadId,
- continue_status == DBG_CONTINUE ?
- "DBG_CONTINUE" : "DBG_EXCEPTION_NOT_HANDLED");
+ DEBUG_EVENTS
+ ("ContinueDebugEvent (cpid=%d, ctid=0x%x, %s)",
+ (unsigned) last_wait_event.dwProcessId,
+ (unsigned) last_wait_event.dwThreadId,
+ cont_status == DBG_CONTINUE ? "DBG_CONTINUE" :
+ cont_status == DBG_EXCEPTION_NOT_HANDLED ? "DBG_EXCEPTION_NOT_HANDLED" :
+ cont_status == DBG_REPLY_LATER ? "DBG_REPLY_LATER" :
+ "DBG_???");
return ContinueDebugEvent (last_wait_event.dwProcessId,
last_wait_event.dwThreadId,
- continue_status);
+ cont_status);
}
/* See nat/windows-nat.h. */
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 19403d0e623..fe377fcde34 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -87,12 +87,55 @@ struct windows_thread_info
/* Thread Information Block address. */
CORE_ADDR thread_local_base;
+#ifdef __CYGWIN__
+ /* These two fields are used to handle Cygwin signals. When a
+ thread is signaled, the "sig" thread inside the Cygwin runtime
+ reports the fact to us via a special OutputDebugString message.
+ In order to make stepping into a signal handler work, we can only
+ resume the "sig" thread when we also resume the target signaled
+ thread. When we intercept a Cygwin signal, we set up a cross
+ link between the two threads using the two fields below, so we
+ can always identify one from the other. See the "Cygwin signals"
+ description in gdb/windows-nat.c for more. */
+
+ /* If this thread received a signal, then 'cygwin_sig_thread' points
+ to the "sig" thread within the Cygwin runtime. */
+ windows_thread_info *cygwin_sig_thread = nullptr;
+
+ /* If this thread is the Cygwin runtime's "sig" thread, then
+ 'signaled_thread' points at the thread that received a
+ signal. */
+ windows_thread_info *signaled_thread = nullptr;
+#endif
+
+ /* If the thread had its event postponed with DBG_REPLY_LATER, when
+ we later ResumeThread this thread, WaitForDebugEvent will
+ re-report the postponed event. This field holds the continue
+ status value to be automatically passed to ContinueDebugEvent
+ when we encounter this re-reported event. 0 if the thread has
+ not had its event postponed with DBG_REPLY_LATER. */
+ DWORD reply_later = 0;
+
/* This keeps track of whether SuspendThread was called on this
thread. -1 means there was a failure or that the thread was
explicitly not suspended, 1 means it was called, and 0 means it
was not. */
int suspended = 0;
+ /* This flag indicates whether we are explicitly stopping this
+ thread in response to a target_stop request. This allows
+ distinguishing between threads that are explicitly stopped by the
+ debugger and threads that are stopped due to other reasons.
+
+ Typically, when we want to stop a thread, we suspend it, enqueue
+ a pending GDB_SIGNAL_0 stop status on the thread, and then set
+ this flag to true. However, if the thread has had its event
+ previously postponed with DBG_REPLY_LATER, it means that it
+ already has an event to report. In such case, we simply set the
+ 'stopping' flag without suspending the thread or enqueueing a
+ pending stop. See stop_one_thread. */
+ bool stopping = false;
+
/* Info about a potential pending stop.
Sometimes, Windows will report a stop on a thread that has been
@@ -173,15 +216,15 @@ struct windows_process_info
virtual windows_thread_info *find_thread (ptid_t ptid) = 0;
/* Handle OUTPUT_DEBUG_STRING_EVENT from child process. Updates
- OURSTATUS and returns the thread id if this represents a thread
- change (this is specific to Cygwin), otherwise 0.
+ OURSTATUS and returns true if this represents a Cygwin signal,
+ otherwise false.
Cygwin prepends its messages with a "cygwin:". Interpret this as
a Cygwin signal. Otherwise just print the string as a warning.
This function must be supplied by the embedding application. */
- virtual DWORD handle_output_debug_string (const DEBUG_EVENT ¤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 73ddfcb3b6a..408df2acd78 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -77,6 +77,202 @@
#include "ser-event.h"
#include "inf-loop.h"
+/* This comment documents high-level logic of this file.
+
+all-stop
+========
+
+In all-stop mode ("maint set target-non-stop off"), there is only ever
+one Windows debug event in flight. When we receive an event from
+WaitForDebugEvent, the kernel has already implicitly suspended all the
+threads of the process. We report the breaking event to the core.
+When the core decides to resume the inferior, it calls
+windows_nat_target:resume, which triggers a ContinueDebugEvent call.
+This call makes all unsuspended threads schedulable again, and we go
+back to waiting for the next event in WaitForDebugEvent.
+
+non-stop
+========
+
+For non-stop mode, we utilize the DBG_REPLY_LATER flag in the
+ContinueDebugEvent function. According to Microsoft:
+
+ "This flag causes dwThreadId to replay the existing breaking event
+ after the target continues. By calling the SuspendThread API against
+ dwThreadId, a debugger can resume other threads in the process and
+ later return to the breaking."
+
+To enable non-stop mode, windows_nat_target::wait suspends the thread,
+calls 'ContinueForDebugEvent(..., DBG_REPLY_LATER)', and sets the
+process_thread thread to wait for the next event using
+WaitForDebugEvent, all before returning the original breaking event to
+the core.
+
+When the user/core finally decides to resume the inferior thread that
+reported the event, we unsuspend it using ResumeThread. Unlike in
+all-stop mode, we don't call ContinueDebugEvent then, as it has
+already been called when the event was first encountered. By making
+the inferior thread schedulable again (by unsuspending it),
+WaitForDebugEvent re-reports the same event (due to the earlier
+DBG_REPLY_LATER). In windows_nat_target::wait, we detect this delayed
+re-report and call ContinueDebugEvent on the thread, instructing the
+"process_thread" thread (the GDB thread responsible for calling
+WaitForDebugEvents) to continue waiting for the next event.
+
+During the initial thread resumption in windows_nat_target::resume, we
+recorded the dwContinueStatus argument to be passed to the last
+ContinueDebugEvent (called when the reply-later event is re-reported).
+See windows_thread_info::reply_later for details.
+
+Note that with this setup, in non-stop mode, every stopped thread has
+its own independent last-reported Windows debug event. Therefore, we
+can decide on a per-thread basis whether to pass the thread's
+exception (DBG_EXCEPTION_NOT_HANDLED / DBG_CONTINUE) to the inferior.
+This per-thread decision is not possible in all-stop mode, where we
+only call ContinueDebugEvent for the thread that last reported a stop,
+at windows_nat_target::resume time.
+
+Thread and process exits
+========================
+
+When a process exits, Windows reports one EXIT_THREAD_DEBUG_EVENT
+event for each thread, except for the last thread that exits. That
+last thread reports a EXIT_PROCESS_DEBUG_EVENT event instead.
+
+The last thread that exits is not guaranteed to be the main thread of
+the process. In fact, it seldom is. E.g., if the main thread calls
+ExitProcess (or returns from main, which ends up calling ExitProcess),
+then we typically see a EXIT_THREAD_DEBUG_EVENT event for the main
+thread first, followed by more EXIT_THREAD_DEBUG_EVENT events for
+other threads, and then finaly the EXIT_PROCESS_DEBUG_EVENT for
+whatever thread happened to be the last one to exit.
+
+When a thread reports EXIT_THREAD_DEBUG_EVENT /
+EXIT_PROCESS_DEBUG_EVENT, our handle to the thread is still valid, and
+we can still read its registers. Windows only destroys the handle
+after ContinueDebugEvent.
+
+A thread that has exited CANNOT be suspended. So if a thread was
+previously suspended, and then something kills the whole process
+(which force-kills all threads), that suspended thread will
+automatically "unsuspend", and report a EXIT_THREAD_DEBUG_EVENT event.
+However, if we had previously used DBG_REPLY_LATER on the thread,
+Windows will first re-report the kernel-side-queued "reply-later"
+event, and only after that one is ContinueDebugEvent'ed, will we see
+the EXIT_THREAD_DEBUG_EVENT event.
+
+Detaching and DBG_REPLY_LATER
+=============================
+
+After we detach from a process that has threads that we had previously
+used DBG_REPLY_LATER on, the kernel re-raises the "reply-later"
+exceptions for those threads. This would most often kill the
+just-detached process, if we let it happen. To prevent it, we flush
+all the "reply-later" events from the kernel before detaching.
+
+Cygwin signals
+==============
+
+The Cygwin runtime always spawns a "sig" thread, which is responsible
+for receiving signal delivery requests, and hijacking the signaled
+thread's execution to make it run the signal handler. This is all
+explained here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/DevDocs/how-signals-work.txt
+
+There's a custom debug api protocol between GDB and Cygwin to be able
+to intercept Cygwin signals before they're seen by the signaled
+thread, just like the debugger intercepts signals with ptrace on
+Linux. This Cygwin debugger protocol isn't well documented, though.
+Here's what happens: when the special "sig" thread in the Cygwin
+runtime is about to deliver a signal to the target thread, it calls
+OutputDebugString with a special message:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1866
+
+OutputDebugString is a function that is part of the Windows debug API.
+It generates an OUTPUT_DEBUG_STRING_EVENT event out of
+WaitForDebugEvent in the debugger, which freezes the inferior, like
+any other event.
+
+GDB recognizes the special Cygwin signal marker string, and is able to
+report the intercepted Cygwin signal to the user.
+
+With the windows-nat backend in all-stop mode, if the user decides to
+single-step the signaled thread, GDB will set the trace flag in the
+signaled thread to force it to single-step, and then re-resume the
+program with ContinueDebugEvent. This resumes both the signaled
+thread, and the special "sig" thread. The special "sig" thread
+decides to make the signaled thread run the signal handler, so it
+suspends it with SuspendThread, does a read-modify-write operation
+with GetThreadContext/SetThreadContext, and then re-resumes it with
+ResumeThread. This is all done here:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1011
+
+That resulting register context will still have its trace flag set, so
+the signaled thread ends up single-stepping the signal handler and
+reporting the trace stop to GDB, which reports the stop where the
+thread is now stopped, inside the signal handler.
+
+That is the intended behavior; stepping into a signal handler is a
+feature that works on other ports as well, including x86 GNU/Linux,
+for example. This is exercised by the gdb.base/sigstep.exp testcase.
+
+Now, making that work with the backend in non-stop mode (the default
+on Windows 10 and above) is tricker. In that case, when GDB sees the
+magic OUTPUT_DEBUG_STRING_EVENT event mentioned above, reported for
+the "sig" thread, GDB reports the signal stop for the target signaled
+thread to the user (leaving that thread stopped), but, unlike with an
+all-stop backend, in non-stop, only the evented/signaled thread should
+be stopped, so the backend would normally want to re-resume the Cygwin
+runtime's "sig" thread after handling the OUTPUT_DEBUG_STRING_EVENT
+event, like it does with any other event out of WaitForDebugEvent that
+is not reported to the core. If it did that (resume the "sig" thread)
+however, at that point, the signaled thread would be stopped,
+suspended with SuspendThread by GDB (while the user is inspecting it),
+but, unlike in all-stop, the "sig" thread would be set running free.
+The "sig" thread would reach the code that wants to redirect the
+signaled thread's execution to the signal handler (by hacking the
+registers context, as described above), but unlike in the all-stop
+case, the "sig" thread would notice that the signaled thread is
+suspended, and so would decide to defer the signal handler until a
+later time. It's the same code as described above for the all-stop
+case, except it would take the "then" branch:
+
+ https://sourceware.org/cgit/newlib-cygwin/tree/winsup/cygwin/exceptions.cc?id=4becae7bd833e183c789821a477f25898ed0db1f#n1019
+
+ // Just set pending if thread is already suspended
+ if (res)
+ {
+ tls->unlock ();
+ ResumeThread (hth);
+ goto out;
+ }
+
+The result would be that when the GDB user later finally decides to
+step the signaled thread, the signaled thread would just single step
+the mainline code, instead of stepping into the signal handler.
+
+To avoid this difference of behavior in non-stop mode compared to
+all-stop mode, we use a trick -- whenever we see that magic
+OUTPUT_DEBUG_STRING_EVENT event reported for the "sig" thread, we
+report a stop for the target signaled thread, _and_ leave the "sig"
+thread suspended as well, for as long as the target signaled thread is
+suspended. I.e., we don't let the "sig" thread run before the user
+decides what to do with the signaled thread's signal. Only when the
+user re-resumes the signaled thread, will we resume the "sig" thread
+as well. The trick is that all this is done here in the Windows
+backend, while providing the illusion to the core of GDB (and the
+user) that the "sig" thread is "running", for as long as the core
+wants the "sig" thread to be running.
+
+This isn't ideal, since this means that with user-visible non-stop,
+the inferior will only be able to process and report one signal at a
+time (as the "sig" thread is responsible for that), but that seems
+like an acceptible compromise, better than not being able to have the
+target work in non-stop by default on Cygwin. */
+
using namespace windows_nat;
/* Maintain a linked list of "so" information. */
@@ -103,6 +299,11 @@ enum windows_continue_flag
call to continue the inferior -- we are either mourning it or
detaching. */
WCONT_LAST_CALL = 2,
+
+ /* By default, windows_continue only calls ContinueDebugEvent in
+ all-stop mode. This flag indicates that windows_continue
+ should call ContinueDebugEvent even in non-stop mode. */
+ WCONT_CONTINUE_DEBUG_EVENT = 4,
};
DEF_ENUM_FLAGS_TYPE (windows_continue_flag, windows_continue_flags);
@@ -130,8 +331,8 @@ struct windows_private_thread_info : private_thread_info, windows_thread_info
struct windows_per_inferior : public windows_process_info
{
windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT ¤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;
@@ -280,7 +481,11 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void attach (const char *, int) override;
bool attach_no_wait () override
- { return true; }
+ {
+ /* In non-stop, after attach, we leave all threads running, like
+ other targets. */
+ return !target_is_non_stop_p ();
+ }
void detach (inferior *, int) override;
@@ -323,8 +528,13 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
std::string pid_to_str (ptid_t) override;
void interrupt () override;
+ void stop (ptid_t) override;
void pass_ctrlc () override;
+ void thread_events (bool enable) override;
+
+ bool any_resumed_thread ();
+
const char *pid_to_exec_file (int pid) override;
ptid_t get_ada_task_ptid (long lwp, ULONGEST thread) override;
@@ -354,6 +564,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
return m_is_async;
}
+ bool supports_non_stop () override;
+
void async (bool enable) override;
int async_wait_fd () override
@@ -368,6 +580,15 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT ¤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);
+
BOOL windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags = 0);
@@ -431,6 +652,9 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
already returned an event, and we need to ContinueDebugEvent
again to restart the inferior. */
bool m_continued = false;
+
+ /* Whether target_thread_events is in effect. */
+ bool m_report_thread_events = false;
};
/* Get the windows_thread_info object associated with THR. */
@@ -718,6 +942,13 @@ windows_nat_target::add_thread (ptid_t ptid, HANDLE h, void *tlb,
registers. */
th->debug_registers_changed = true;
+ /* Even if we're stopping the thread for some reason internal to
+ this module, from the perspective of infrun and the
+ user/frontend, this new thread is running until it next reports a
+ stop. */
+ set_state (this, ptid, THREAD_RUNNING);
+ set_internal_state (this, ptid, THREAD_INT_RUNNING);
+
return th;
}
@@ -1089,12 +1320,17 @@ signal_event_command (const char *args, int from_tty)
/* See nat/windows-nat.h. */
-DWORD
+bool
windows_per_inferior::handle_output_debug_string
(const DEBUG_EVENT ¤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
@@ -1131,15 +1367,37 @@ windows_per_inferior::handle_output_debug_string
int sig = strtol (s.get () + sizeof (_CYGWIN_SIGNAL_STRING) - 1, &p, 0);
gdb_signal gotasig = gdb_signal_from_host (sig);
LPCVOID x = 0;
+ DWORD thread_id = 0;
- if (gotasig)
+ if (gotasig != GDB_SIGNAL_0)
{
- ourstatus->set_stopped (gotasig);
thread_id = strtoul (p, &p, 0);
- if (thread_id == 0)
- thread_id = current_event.dwThreadId;
- else
- x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+ if (thread_id != 0)
+ {
+ x = (LPCVOID) (uintptr_t) strtoull (p, NULL, 0);
+
+ ptid_t ptid (current_event.dwProcessId, thread_id, 0);
+ windows_thread_info *th = find_thread (ptid);
+
+ /* Suspend the signaled thread, and leave the signal as
+ a pending event. It will be picked up by
+ windows_nat_target::wait. */
+ th->suspend ();
+ th->stopping = true;
+ th->last_event = {};
+ th->pending_status.set_stopped (gotasig);
+
+ /* Link the "sig" thread and the signaled threads, so we
+ can keep the "sig" thread suspended until we resume
+ the signaled thread. See "Cygwin signals" at the
+ top. */
+ event_thr->signaled_thread = th;
+ th->cygwin_sig_thread = event_thr;
+
+ /* Leave the "sig" thread suspended. */
+ event_thr->suspend ();
+ return true;
+ }
}
DEBUG_EVENTS ("gdb: cygwin signal %d, thread 0x%x, CONTEXT @ %p",
@@ -1147,7 +1405,7 @@ windows_per_inferior::handle_output_debug_string
}
#endif
- return thread_id;
+ return false;
}
static int
@@ -1303,6 +1561,15 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
{
struct x86_debug_reg_state *state = x86_debug_reg_state (process_id);
+ /* If this thread is already gone, but the core doesn't know about
+ it yet, there's really nothing to resume. Such a thread will
+ have a pending exit status, so we won't try to resume it in the
+ normal resume path. But, we can still end up here in the
+ kill/detach/mourn paths, trying to resume the whole process to
+ collect the last debug event. */
+ if (th->h == nullptr)
+ return;
+
windows_process.with_context (th, [&] (auto *context)
{
if (th->debug_registers_changed)
@@ -1376,6 +1643,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
});
th->resume ();
+ th->stopping = false;
th->last_sig = GDB_SIGNAL_0;
}
@@ -1387,31 +1655,77 @@ BOOL
windows_nat_target::windows_continue (DWORD continue_status, int id,
windows_continue_flags cont_flags)
{
- for (auto *th : all_windows_threads ())
- {
- if ((id == -1 || id == (int) th->tid)
- && !th->suspended
- && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
- {
- DEBUG_EVENTS ("got matching pending stop event "
- "for 0x%x, not resuming",
- th->tid);
-
- /* There's no need to really continue, because there's already
- another event pending. However, we do need to inform the
- event loop of this. */
- serial_event_set (m_wait_event);
- return TRUE;
- }
- }
+ if ((cont_flags & (WCONT_LAST_CALL | WCONT_KILLED)) == 0)
+ for (auto *th : all_windows_threads ())
+ {
+ if ((id == -1 || id == (int) th->tid)
+ && th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("got matching pending stop event "
+ "for 0x%x, not resuming",
+ th->tid);
+
+ /* There's no need to really continue, because there's already
+ another event pending. However, we do need to inform the
+ event loop of this. */
+ serial_event_set (m_wait_event);
+ return TRUE;
+ }
+ }
+ /* Resume any suspended thread whose ID matches "ID". Skip the
+ Cygwin "sig" thread in the main iteration, though. That one is
+ only resumed when the target signaled thread is resumed. See
+ "Cygwin signals" in the intro section. */
for (auto *th : all_windows_threads ())
- if (id == -1 || id == (int) th->tid)
- windows_process.continue_one_thread (th, cont_flags);
+ if (th->suspended
+#ifdef __CYGWIN__
+ && th->signaled_thread == nullptr
+#endif
+ && (id == -1 || id == (int) th->tid))
+ {
+ windows_process.continue_one_thread (th, cont_flags);
+
+#ifdef __CYGWIN__
+ /* See if we're resuming a thread that caught a Cygwin signal.
+ If so, also resume the Cygwin runtime's "sig" thread. */
+ if (th->cygwin_sig_thread != nullptr)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) blocked by "
+ "just-resumed thread %d (0x%x)",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid,
+ th->tid, th->tid);
+
+ inferior *inf = find_inferior_pid (this,
+ windows_process.process_id);
+ thread_info *sig_thr
+ = inf->find_thread (ptid_t (windows_process.process_id,
+ th->cygwin_sig_thread->tid));
+ if (sig_thr->internal_state () == THREAD_INT_RUNNING)
+ {
+ DEBUG_EVENTS ("\"sig\" thread %d (0x%x) meant to be running, "
+ "continuing it now",
+ th->cygwin_sig_thread->tid,
+ th->cygwin_sig_thread->tid);
+ windows_process.continue_one_thread (th->cygwin_sig_thread,
+ cont_flags);
+ }
+ /* Break the chain. */
+ th->cygwin_sig_thread->signaled_thread = nullptr;
+ th->cygwin_sig_thread = nullptr;
+ }
+#endif
+ }
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status,
- cont_flags & WCONT_LAST_CALL);
+ if (!target_is_non_stop_p ()
+ || (cont_flags & WCONT_CONTINUE_DEBUG_EVENT) != 0)
+ {
+ DEBUG_EVENTS ("windows_continue -> continue_last_debug_event");
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status,
+ cont_flags & WCONT_LAST_CALL);
+ }
return TRUE;
}
@@ -1439,36 +1753,46 @@ windows_nat_target::fake_create_process (const DEBUG_EVENT ¤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;
-
- /* A specific PTID means `step only this thread id'. */
- int resume_all = ptid == minus_one_ptid;
-
- /* If we're continuing all threads, it's the current inferior that
- should be handled specially. */
- if (resume_all)
- ptid = inferior_ptid;
+/* Prepare TH to be resumed. TH and TP must point at the same thread.
+ Records the right dwContinueStatus for SIG in th->reply_later if we
+ used DBG_REPLY_LATER before on this thread, and sets of clears the
+ trace flag according to STEP. Also returns the dwContinueStatus
+ argument to pass to ContinueDebugEvent. The thread is still left
+ suspended -- a subsequent windows_continue/continue_one_thread call
+ is needed to flush the thread's register context and unsuspend. */
- DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
- ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+DWORD
+windows_nat_target::prepare_resume (windows_thread_info *th,
+ thread_info *tp,
+ int step, gdb_signal sig)
+{
+ gdb_assert (th->tid == tp->ptid.lwp ());
- /* Get currently selected thread. */
- th = windows_process.find_thread (inferior_ptid);
- gdb_assert (th != nullptr);
+ DWORD continue_status = DBG_CONTINUE;
if (sig != GDB_SIGNAL_0)
{
+ /* Allow continuing with the same signal that interrupted us.
+ Otherwise complain. */
+
/* Note it is OK to call get_last_debug_event_ptid() from the
- main thread here, because we know the process_thread thread
- isn't waiting for an event at this point, so there's no data
- race. */
- if (inferior_ptid != get_last_debug_event_ptid ())
+ main thread here in all-stop, because we know the
+ process_thread thread is not waiting for an event at this
+ point, so there is no data race. We cannot call it in
+ non-stop mode, as the process_thread thread _is_ waiting for
+ events right now in that case. However, the restriction does
+ not exist in non-stop mode, so we don't even call it in that
+ mode. */
+ if (!target_is_non_stop_p ()
+ && tp->ptid != get_last_debug_event_ptid ())
{
- /* ContinueDebugEvent will be for a different thread. */
+ /* In all-stop, ContinueDebugEvent will be for a different
+ thread. For non-stop, we've called ContinueDebugEvent
+ with DBG_REPLY_LATER for this thread, so we just set the
+ intended continue status in 'reply_later', which is later
+ passed to ContinueDebugEvent in windows_nat_target::wait
+ after we resume the thread and we get the replied-later
+ (repeated) event out of WaitForDebugEvent. */
DEBUG_EXCEPT ("Cannot continue with signal %d here. "
"Not last-event thread", sig);
}
@@ -1504,20 +1828,58 @@ windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
th->last_sig);
}
+ /* If DBG_REPLY_LATER was used on the thread, we override the
+ continue status that will be passed to ContinueDebugEvent later
+ with the continue status we've just determined fulfils the
+ caller's resumption request. Note that DBG_REPLY_LATER is only
+ used in non-stop mode, and in that mode, windows_continue (called
+ below) does not call ContinueDebugEvent. */
+ if (th->reply_later != 0)
+ th->reply_later = continue_status;
+
+ /* Single step by setting t bit (trap flag). The trap flag is
+ automatically reset as soon as the single-step exception arrives,
+ however, it's possible to suspend/stop a thread before it
+ executes any instruction, leaving the trace flag set. If we
+ subsequently decide to continue such a thread instead of stepping
+ it, and we didn't clear the trap flag, the thread would step, and
+ we'd end up reporting a SIGTRAP to the core which the core
+ couldn't explain (because the thread wasn't supposed to be
+ stepping), and end up reporting a spurious SIGTRAP to the
+ user. */
+ regcache *regcache = get_thread_regcache (tp);
+ fetch_registers (regcache, gdbarch_ps_regnum (regcache->arch ()));
+
windows_process.with_context (th, [&] (auto *context)
{
if (step)
- {
- /* Single step by setting t bit. */
- regcache *regcache = get_thread_regcache (inferior_thread ());
- struct gdbarch *gdbarch = regcache->arch ();
- fetch_registers (regcache, gdbarch_ps_regnum (gdbarch));
- context->EFlags |= FLAG_TRACE_BIT;
- }
+ context->EFlags |= FLAG_TRACE_BIT;
+ else
+ context->EFlags &= ~FLAG_TRACE_BIT;
});
- /* Allow continuing with the same signal that interrupted us.
- Otherwise complain. */
+ return continue_status;
+}
+
+void
+windows_nat_target::resume (ptid_t ptid, int step, enum gdb_signal sig)
+{
+ /* A specific PTID means `step only this thread id'. */
+ int resume_all = ptid == minus_one_ptid;
+
+ /* If we're continuing all threads, it's the current inferior that
+ should be handled specially. */
+ if (resume_all)
+ ptid = inferior_ptid;
+
+ DEBUG_EXEC ("pid=%d, tid=0x%x, step=%d, sig=%d",
+ ptid.pid (), (unsigned) ptid.lwp (), step, sig);
+
+ /* Get currently selected thread. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+ gdb_assert (th != nullptr);
+
+ DWORD continue_status = prepare_resume (th, inferior_thread (), step, sig);
if (resume_all)
windows_continue (continue_status, -1);
@@ -1566,12 +1928,134 @@ windows_nat_target::interrupt ()
"Press Ctrl-c in the program console."));
}
+/* Stop thread TH. This leaves a GDB_SIGNAL_0 pending in the thread,
+ which is later consumed by windows_nat_target::wait. */
+
+void
+windows_nat_target::stop_one_thread (windows_thread_info *th)
+{
+ ptid_t thr_ptid (windows_process.process_id, th->tid);
+
+ if (th->suspended == -1)
+ {
+ /* Already known to be stopped; and suspension failed, most
+ probably because the thread is exiting. Do nothing, and let
+ the thread exit event be reported. */
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+ }
+#ifdef __CYGWIN__
+ else if (th->suspended
+ && th->signaled_thread != nullptr
+ && th->pending_status.kind () == TARGET_WAITKIND_IGNORE)
+ {
+ DEBUG_EVENTS ("explict stop for \"sig\" thread %s held for signal",
+ thr_ptid.to_string ().c_str ());
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+#endif
+ else if (th->suspended)
+ {
+ /* Already known to be stopped; do nothing. */
+
+ DEBUG_EVENTS ("already suspended %s: suspended=%d, stopping=%d",
+ thr_ptid.to_string ().c_str (),
+ th->suspended, th->stopping);
+
+ th->stopping = true;
+ }
+ else
+ {
+ DEBUG_EVENTS ("stop request for %s", thr_ptid.to_string ().c_str ());
+
+ th->suspend ();
+
+ /* If suspension failed, it means the thread is exiting. Let
+ the thread exit event be reported instead of faking our own
+ stop. */
+ if (th->suspended == -1)
+ {
+ DEBUG_EVENTS ("suspension of %s failed, expect thread exit event",
+ thr_ptid.to_string ().c_str ());
+ return;
+ }
+
+ gdb_assert (th->suspended == 1);
+
+ th->stopping = true;
+ th->pending_status.set_stopped (GDB_SIGNAL_0);
+ th->last_event = {};
+ serial_event_set (m_wait_event);
+ }
+}
+
+/* Implementation of target_ops::stop. */
+
+void
+windows_nat_target::stop (ptid_t ptid)
+{
+ for (thread_info *thr : all_non_exited_threads (this))
+ {
+ if (thr->ptid.matches (ptid))
+ stop_one_thread (as_windows_thread_info (thr));
+ }
+}
+
void
windows_nat_target::pass_ctrlc ()
{
interrupt ();
}
+/* Implementation of the target_ops::thread_events method. */
+
+void
+windows_nat_target::thread_events (bool enable)
+{
+ DEBUG_EVENTS ("windows_nat_target::thread_events(%d)", enable);
+ m_report_thread_events = enable;
+}
+
+/* True if there is any resumed thread. */
+
+bool
+windows_nat_target::any_resumed_thread ()
+{
+ for (thread_info *thread : all_non_exited_threads (this))
+ if (thread->internal_state () == THREAD_INT_RUNNING)
+ return true;
+ return false;
+}
+
+/* Called for both EXIT_THREAD_DEBUG_EVENT and
+ EXIT_PROCESS_DEBUG_EVENT to handle the fact that the event thread
+ has exited. */
+
+static void
+handle_thread_exit (const DEBUG_EVENT ¤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
@@ -1585,24 +2069,32 @@ windows_nat_target::get_windows_debug_event
/* If there is a relevant pending stop, report it now. See the
comment by the definition of "windows_thread_info::pending_status"
for details on why this is needed. */
- for (auto *th : all_windows_threads ())
+ for (thread_info *thread : all_threads_safe ())
{
- if (!th->suspended
+ if (thread->inf->process_target () != this)
+ continue;
+
+ auto *th = as_windows_thread_info (thread);
+ if (thread->internal_state () == THREAD_INT_RUNNING
+ && th->suspended
&& th->pending_status.kind () != TARGET_WAITKIND_IGNORE)
{
- DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
-
- thread_id = th->tid;
*ourstatus = th->pending_status;
th->pending_status.set_ignore ();
*current_event = th->last_event;
-
- ptid_t ptid (windows_process.process_id, thread_id);
- windows_process.invalidate_context (th);
- return ptid;
+ DEBUG_EVENTS ("reporting pending event for 0x%x", th->tid);
+ return thread->ptid;
}
}
+ /* If there are no resumed threads left, bail. */
+ if (windows_process.windows_initialization_done
+ && !any_resumed_thread ())
+ {
+ ourstatus->set_no_resumed ();
+ return minus_one_ptid;
+ }
+
if ((options & TARGET_WNOHANG) != 0 && !m_debug_event_pending)
{
ourstatus->set_ignore ();
@@ -1616,6 +2108,78 @@ windows_nat_target::get_windows_debug_event
event_code = current_event->dwDebugEventCode;
ourstatus->set_spurious ();
+ ptid_t result_ptid (current_event->dwProcessId,
+ current_event->dwThreadId, 0);
+ windows_thread_info *result_th = windows_process.find_thread (result_ptid);
+
+ /* If we previously used DBG_REPLY_LATER on this thread, and we're
+ seeing an event for it, it means we've already processed the
+ event, and then subsequently resumed the thread [1], intending to
+ pass REPLY_LATER to ContinueDebugEvent. Do that now, before the
+ switch table below, which may have side effects that don't make
+ sense for a delayed event.
+
+ [1] - with the caveat that sometimes Windows reports an event for
+ a suspended thread. Also handled below. */
+ if (result_th != nullptr && result_th->reply_later != 0)
+ {
+ DEBUG_EVENTS ("reply-later thread 0x%x, suspended=%d, dwDebugEventCode=%s",
+ result_th->tid, result_th->suspended,
+ event_code_to_string (event_code).c_str ());
+
+ gdb_assert (dbg_reply_later_available ());
+
+ /* We never ask to DBG_REPLY_LATER these two, so we shouldn't
+ see them here. If a thread is forced-exited when a
+ DBG_REPLY_LATER is in effect, then we will still see the
+ DBG_REPLY_LATER-ed event before the thread/process exit
+ event. */
+ gdb_assert (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT);
+
+ if (result_th->suspended == 1)
+ {
+ /* Pending stop. See the comment by the definition of
+ "pending_status" for details on why this is needed. */
+ DEBUG_EVENTS ("unexpected reply-later stop in suspended thread 0x%x",
+ result_th->tid);
+
+ /* Put the event back in the kernel queue. We haven't yet
+ decided which reply to use. */
+ continue_status = DBG_REPLY_LATER;
+ }
+ else if (result_th->suspended == -1)
+ {
+ /* We resumed the thread expecting to get back a reply-later
+ event. Before we saw that event, we tried to suspend the
+ thread, but that failed, because the thread exited
+ (likely because the whole process has been killed). We
+ should get back an EXIT_THREAD_DEBUG_EVENT for this
+ thread, but only after getting past this reply-later
+ event. */
+ DEBUG_EVENTS ("reply-later stop in suspend-failed "
+ "thread 0x%x, ignoring",
+ result_th->tid);
+
+ /* Continue normally, and expect a
+ EXIT_THREAD_DEBUG_EVENT. */
+ continue_status = DBG_CONTINUE;
+ result_th->reply_later = 0;
+ }
+ else
+ {
+ continue_status = result_th->reply_later;
+ result_th->reply_later = 0;
+ }
+
+ /* Go back to waiting for the next event. */
+ continue_last_debug_event_main_thread
+ (_("Failed to continue reply-later event"), continue_status);
+
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+
DEBUG_EVENTS ("kernel event for pid=%u tid=0x%x code=%s",
(unsigned) current_event->dwProcessId,
(unsigned) current_event->dwThreadId,
@@ -1649,17 +2213,29 @@ windows_nat_target::get_windows_debug_event
current_event->u.CreateThread.lpThreadLocalBase,
false /* main_thread_p */));
- /* This updates debug registers if necessary. */
- windows_process.continue_one_thread (th, 0);
+ /* Update the debug registers if we're not reporting the stop.
+ If we are (reporting the stop), the debug registers will be
+ updated when the thread is eventually re-resumed. */
+ if (m_report_thread_events)
+ ourstatus->set_thread_created ();
+ else
+ windows_process.continue_one_thread (th, 0);
}
break;
case EXIT_THREAD_DEBUG_EVENT:
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- current_event->u.ExitThread.dwExitCode,
- false /* main_thread_p */);
- thread_id = 0;
+ {
+ ourstatus->set_thread_exited
+ (current_event->u.ExitThread.dwExitCode);
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
+
+ /* Don't decide yet whether to report the event, or delete the
+ thread immediately, because we still need to check whether
+ the event should be left pending, depending on whether the
+ thread was running or not from the core's perspective. */
+ }
break;
case CREATE_PROCESS_DEBUG_EVENT:
@@ -1688,9 +2264,6 @@ windows_nat_target::get_windows_debug_event
}
else if (windows_process.saw_create == 1)
{
- delete_thread (ptid_t (current_event->dwProcessId,
- current_event->dwThreadId, 0),
- 0, true /* main_thread_p */);
DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
/* If the exit status looks like a fatal exception, but we
don't recognize the exception's code, make the original
@@ -1702,7 +2275,10 @@ windows_nat_target::get_windows_debug_event
ourstatus->set_exited (exit_status);
else
ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
- return ptid_t (current_event->dwProcessId);
+
+ thread_id = current_event->dwThreadId;
+
+ handle_thread_exit (*current_event);
}
break;
@@ -1761,8 +2337,24 @@ windows_nat_target::get_windows_debug_event
case OUTPUT_DEBUG_STRING_EVENT: /* Message from the kernel. */
if (windows_process.saw_create != 1)
break;
- thread_id = windows_process.handle_output_debug_string (*current_event,
- ourstatus);
+ if (windows_process.handle_output_debug_string (*current_event,
+ ourstatus))
+ {
+ /* We caught a Cygwin signal for a thread. That thread now
+ has a pending event, and the "sig" thread is
+ suspended. */
+ serial_event_set (m_wait_event);
+
+ /* In all-stop, return now to avoid reaching
+ ContinueDebugEvent further below. In all-stop, it's
+ always windows_nat_target::resume that does the
+ ContinueDebugEvent call. */
+ if (!target_is_non_stop_p ())
+ {
+ ourstatus->set_ignore ();
+ return null_ptid;
+ }
+ }
break;
default:
@@ -1784,35 +2376,79 @@ windows_nat_target::get_windows_debug_event
return null_ptid;
}
- const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id, 0);
- windows_thread_info *th = windows_process.find_thread (ptid);
+ const ptid_t ptid = ptid_t (current_event->dwProcessId, thread_id);
+ thread_info *thread = this->find_thread (ptid);
+ auto *th = as_windows_thread_info (thread);
th->last_event = *current_event;
- if (th->suspended)
+ if (thread->internal_state () == THREAD_INT_STOPPED)
{
+ gdb_assert (th->suspended != 0);
+
/* Pending stop. See the comment by the definition of
"pending_status" for details on why this is needed. */
DEBUG_EVENTS ("get_windows_debug_event - "
"unexpected stop in suspended thread 0x%x",
thread_id);
- if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
- && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
- == EXCEPTION_BREAKPOINT)
- || (current_event->u.Exception.ExceptionRecord.ExceptionCode
- == STATUS_WX86_BREAKPOINT))
- && windows_process.windows_initialization_done)
+ /* Use DBG_REPLY_LATER to put the event back in the kernel queue
+ if possible. Don't do that with exit-thread or exit-process
+ events, because when a thread is dead, it can't be suspended
+ anymore, so the kernel would immediately re-report the
+ event. */
+ if (event_code != EXIT_THREAD_DEBUG_EVENT
+ && event_code != EXIT_PROCESS_DEBUG_EVENT
+ && dbg_reply_later_available ())
{
- th->stopped_at_software_breakpoint = true;
- th->pc_adjusted = false;
+ /* Thankfully, the Windows kernel doesn't immediately
+ re-report the unexpected event for a suspended thread
+ when we defer it with DBG_REPLY_LATER, otherwise this
+ would get us stuck in an infinite loop re-processing the
+ same unexpected event over and over. (Which is what
+ would happen if we used DBG_REPLY_LATER on an exit-thread
+ or exit-process event. See comment above.) */
+ continue_status = DBG_REPLY_LATER;
+ }
+ else
+ {
+ if (current_event->dwDebugEventCode == EXCEPTION_DEBUG_EVENT
+ && ((current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == EXCEPTION_BREAKPOINT)
+ || (current_event->u.Exception.ExceptionRecord.ExceptionCode
+ == STATUS_WX86_BREAKPOINT))
+ && windows_process.windows_initialization_done)
+ {
+ th->stopped_at_software_breakpoint = true;
+ th->pc_adjusted = false;
+ }
+
+ th->pending_status = *ourstatus;
+ th->last_event = {};
}
- th->pending_status = *ourstatus;
+ /* For exit-process, the debug event is continued later, at
+ mourn time. */
+ if (event_code != EXIT_PROCESS_DEBUG_EVENT)
+ {
+ continue_last_debug_event_main_thread
+ (_("Failed to resume program execution"), continue_status);
+ }
ourstatus->set_ignore ();
+ return null_ptid;
+ }
- continue_last_debug_event_main_thread
- (_("Failed to resume program execution"), continue_status);
+ gdb_assert (thread->internal_state () == THREAD_INT_RUNNING);
+
+ /* Now that we've handled exit events for suspended threads (above),
+ we can finally decide whether to report the thread exit event or
+ just delete the thread without bothering the core. */
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED
+ && !m_report_thread_events)
+ {
+ delete_thread (ptid, ourstatus->exit_status (),
+ false /* main_thread_p */);
+ ourstatus->set_spurious ();
return null_ptid;
}
@@ -1839,15 +2475,26 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
while (1)
{
- DEBUG_EVENT current_event;
+ DEBUG_EVENT current_event {};
ptid_t result = get_windows_debug_event (pid, ourstatus, options,
¤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
@@ -1873,10 +2520,40 @@ windows_nat_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
th->pc_adjusted = false;
}
+ /* If non-stop, suspend the event thread, and continue
+ it with DBG_REPLY_LATER, so the other threads go back
+ to running as soon as possible. Don't do this if
+ stopping the thread, as in that case the thread was
+ already suspended, and also there's no real Windows
+ debug event to continue in that case. */
+ if (windows_process.windows_initialization_done
+ && target_is_non_stop_p ()
+ && !fake)
+ {
+ if (ourstatus->kind () == TARGET_WAITKIND_THREAD_EXITED)
+ {
+ gdb_assert (th->suspended == -1);
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to DBG_CONTINUE after thread exit"),
+ DBG_CONTINUE);
+ }
+ else
+ {
+ th->suspend ();
+ th->reply_later = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Init: Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
+ }
+
/* All-stop, suspend all threads until they are
explicitly resumed. */
- for (auto *thr : all_windows_threads ())
- thr->suspend ();
+ if (!target_is_non_stop_p ())
+ for (auto *thr : all_windows_threads ())
+ thr->suspend ();
+
+ th->stopping = false;
}
/* If something came out, assume there may be more. This is
@@ -1942,22 +2619,31 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
ptid_t last_ptid;
+ /* Keep fetching events until we see the initial breakpoint (which
+ is planted by Windows itself) being reported. */
+
while (1)
{
struct target_waitstatus status;
last_ptid = this->wait (minus_one_ptid, &status, 0);
- /* Note windows_wait returns TARGET_WAITKIND_SPURIOUS for thread
- events. */
- if (status.kind () != TARGET_WAITKIND_LOADED
- && status.kind () != TARGET_WAITKIND_SPURIOUS)
+ /* These result in an error being thrown before we get here. */
+ gdb_assert (status.kind () != TARGET_WAITKIND_EXITED
+ && status.kind () != TARGET_WAITKIND_SIGNALLED);
+
+ /* We may also see TARGET_WAITKIND_THREAD_EXITED if
+ target_thread_events is active (because another thread was
+ stepping earlier, for example). Ignore such events until we
+ see the initial breakpoint. */
+
+ if (status.kind () == TARGET_WAITKIND_STOPPED)
break;
/* Don't use windows_nat_target::resume here because that
assumes that inferior_ptid points at a valid thread, and we
haven't switched to any thread yet. */
- windows_continue (DBG_CONTINUE, -1);
+ windows_continue (DBG_CONTINUE, -1, WCONT_CONTINUE_DEBUG_EVENT);
}
switch_to_thread (this->find_thread (last_ptid));
@@ -2101,7 +2787,29 @@ windows_nat_target::attach (const char *args, int from_tty)
#endif
do_initial_windows_stuff (pid, 1);
- target_terminal::ours ();
+
+ if (target_is_non_stop_p ())
+ {
+ /* Leave all threads running. */
+
+ continue_last_debug_event_main_thread
+ (_("Failed to DBG_CONTINUE after attach"),
+ DBG_CONTINUE);
+
+ /* The thread that reports the initial breakpoint, and thus ends
+ up as selected thread here, was injected by Windows into the
+ program for the attach, and it exits as soon as we resume it.
+ Switch to the first thread in the inferior, otherwise the
+ user will be left with an exited thread selected. */
+ switch_to_thread (first_thread_of_inferior (current_inferior ()));
+ }
+ else
+ {
+ set_state (this, minus_one_ptid, THREAD_STOPPED);
+ set_internal_state (this, minus_one_ptid, THREAD_INT_STOPPED);
+
+ target_terminal::ours ();
+ }
}
void
@@ -2202,16 +2910,77 @@ windows_nat_target::break_out_process_thread (bool &process_alive)
DEBUG_EVENTS ("got unrelated event, code %u",
current_event.dwDebugEventCode);
- windows_continue (DBG_CONTINUE, -1, 0);
+
+ DWORD continue_status
+ = continue_status_for_event_detaching (current_event);
+ windows_continue (continue_status, -1, WCONT_CONTINUE_DEBUG_EVENT);
}
if (injected_thread_handle != NULL)
CHECK (CloseHandle (injected_thread_handle));
}
+
+/* Used while detaching. Decide whether to pass the exception or not.
+ Returns the dwContinueStatus argument to pass to
+ ContinueDebugEvent. */
+
+DWORD
+windows_nat_target::continue_status_for_event_detaching
+ (const DEBUG_EVENT &event, size_t *reply_later_events_left)
+{
+ ptid_t ptid (event.dwProcessId, event.dwThreadId, 0);
+ windows_thread_info *th = windows_process.find_thread (ptid);
+
+ /* This can be a thread that we don't know about, as we're not
+ tracking thread creation events at this point. */
+ if (th != nullptr && th->reply_later != 0)
+ {
+ DWORD res = th->reply_later;
+ th->reply_later = 0;
+ if (reply_later_events_left != nullptr)
+ (*reply_later_events_left)--;
+ return res;
+ }
+ else if (event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
+ {
+ /* As the user asked to detach already, any new exception not
+ seen by infrun before, is passed down to the inferior without
+ considering "handle SIG pass/nopass". We can just pretend
+ the exception was raised after the inferior was detached. */
+ return DBG_EXCEPTION_NOT_HANDLED;
+ }
+ else
+ return DBG_CONTINUE;
+}
+
void
windows_nat_target::detach (inferior *inf, int from_tty)
{
+ DWORD continue_status = DBG_CONTINUE;
+
+ /* For any thread the core hasn't resumed, call prepare_resume with
+ the signal that the thread would be resumed with, so that we set
+ the right reply_later value, and also, so that we clear the trace
+ flag. */
+ for (thread_info *tp : inf->non_exited_threads ())
+ {
+ if (tp->internal_state () != THREAD_INT_RUNNING)
+ {
+ windows_thread_info *wth = windows_process.find_thread (tp->ptid);
+ gdb_signal signo = get_detach_signal (this, tp->ptid);
+
+ if (signo != wth->last_sig
+ || (signo != GDB_SIGNAL_0 && !signal_pass_state (signo)))
+ signo = GDB_SIGNAL_0;
+
+ DWORD cstatus = prepare_resume (wth, tp, 0, signo);
+
+ if (!m_continued && tp->ptid == get_last_debug_event_ptid ())
+ continue_status = cstatus;
+ }
+ }
+
/* If we see the process exit while unblocking the process_thread
helper thread, then we should skip the actual
DebugActiveProcessStop call. But don't report an error. Just
@@ -2219,20 +2988,76 @@ windows_nat_target::detach (inferior *inf, int from_tty)
bool process_alive = true;
/* The process_thread helper thread will be blocked in
- WaitForDebugEvent waiting for events if we've resumed the target
- before we get here, e.g., with "attach&" or "c&". We need to
- unblock it so that we can have it call DebugActiveProcessStop
- below, in the do_synchronously block. */
+ WaitForDebugEvent waiting for events if we're in non-stop mode,
+ or if in all-stop and we've resumed the target before we get
+ here, e.g., with "attach&" or "c&". We need to unblock it so
+ that we can have it call DebugActiveProcessStop below, in the
+ do_synchronously block. */
if (m_continued)
- break_out_process_thread (process_alive);
+ {
+ break_out_process_thread (process_alive);
+
+ /* We're now either stopped at a thread exit event, or a process
+ exit event. */
+ continue_status = DBG_CONTINUE;
+ }
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (continue_status, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);
std::optional<unsigned> err;
if (process_alive)
do_synchronously ([&] ()
{
- if (!DebugActiveProcessStop (windows_process.process_id))
+ /* The kernel re-raises any exception previously intercepted
+ and deferred with DBG_REPLY_LATER in the inferior after we
+ detach. We need to flush those, and suppress those which
+ aren't meant to be seen by the inferior (e.g., breakpoints,
+ single-steps, any with matching "handle SIG nopass", etc.),
+ otherwise the inferior dies immediately after the detach,
+ due to an unhandled exception. */
+ DEBUG_EVENT event;
+
+ /* Count how many threads have pending reply-later events. */
+ size_t reply_later_events_left = 0;
+ for (auto *th : all_windows_threads ())
+ if (th->reply_later != 0)
+ reply_later_events_left++;
+
+ DEBUG_EVENTS ("flushing %zu reply-later events",
+ reply_later_events_left);
+
+ /* Note we have to use a blocking wait (hence the need for the
+ counter). Just polling (timeout=0) until WaitForDebugEvent
+ returns false would be racy -- the kernel may take a little
+ bit to put the events in the pending queue. That has been
+ observed on Windows 11, where detaching would still very
+ occasionally result in the inferior dying after the detach
+ due to a reply-later event. */
+ while (reply_later_events_left > 0
+ && wait_for_debug_event (&event, INFINITE))
+ {
+ DEBUG_EVENTS ("flushed kernel event code %u",
+ event.dwDebugEventCode);
+
+ DWORD cstatus = (continue_status_for_event_detaching
+ (event, &reply_later_events_left));
+ if (!continue_last_debug_event (cstatus, debug_events))
+ {
+ err = (unsigned) GetLastError ();
+ return false;
+ }
+
+ if (event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
+ {
+ DEBUG_EVENTS ("got EXIT_PROCESS_DEBUG_EVENT, skipping detach");
+ process_alive = false;
+ break;
+ }
+ }
+
+ if (process_alive
+ && !DebugActiveProcessStop (windows_process.process_id))
err = (unsigned) GetLastError ();
else
DebugSetProcessKillOnExit (FALSE);
@@ -2972,13 +3797,32 @@ windows_nat_target::create_inferior (const char *exec_file,
do_initial_windows_stuff (pi.dwProcessId, 0);
- /* windows_continue (DBG_CONTINUE, -1); */
+ /* Present the initial thread as stopped to the core. */
+ windows_thread_info *th = windows_process.find_thread (inferior_ptid);
+
+ th->suspend ();
+ set_state (this, inferior_ptid, THREAD_STOPPED);
+ set_internal_state (this, inferior_ptid, THREAD_INT_STOPPED);
+
+ if (target_is_non_stop_p ())
+ {
+ /* In non-stop mode, we always immediately use DBG_REPLY_LATER
+ on threads as soon as they report an event. However, during
+ the initial startup, windows_nat_target::wait does not do
+ this, so we need to handle it here for the initial
+ thread. */
+ th->reply_later = DBG_CONTINUE;
+ continue_last_debug_event_main_thread
+ (_("Failed to defer event with DBG_REPLY_LATER"),
+ DBG_REPLY_LATER);
+ }
}
void
windows_nat_target::mourn_inferior ()
{
- windows_continue (DBG_CONTINUE, -1, WCONT_LAST_CALL);
+ windows_continue (DBG_CONTINUE, -1,
+ WCONT_LAST_CALL | WCONT_CONTINUE_DEBUG_EVENT);
x86_cleanup_dregs();
if (windows_process.open_process_used)
{
@@ -3028,19 +3872,55 @@ windows_xfer_memory (gdb_byte *readbuf, const gdb_byte *writebuf,
return success ? TARGET_XFER_OK : TARGET_XFER_E_IO;
}
+/* Return true if all the threads of the process have already
+ exited. */
+
+static bool
+already_dead ()
+{
+ for (windows_thread_info *th : all_windows_threads ())
+ if (th->h != nullptr)
+ return false;
+ return true;
+}
+
void
windows_nat_target::kill ()
{
+ /* If all the threads of the process have already exited, there is
+ really nothing to kill. This can happen with e.g., scheduler
+ locking, where the thread exit events for all threads are still
+ pending to be processed by the core. */
+ if (already_dead ())
+ {
+ target_mourn_inferior (inferior_ptid);
+ return;
+ }
+
CHECK (TerminateProcess (windows_process.handle, 0));
+ /* In non-stop mode, windows_continue does not call
+ ContinueDebugEvent by default. This behavior is appropriate for
+ the first call to windows_continue because any thread that is
+ stopped has already been ContinueDebugEvent'ed with
+ DBG_REPLY_LATER. However, after the first
+ wait_for_debug_event_main_thread call in the loop, this will no
+ longer be true.
+
+ In all-stop mode, the WCONT_CONTINUE_DEBUG_EVENT flag has no
+ effect, so writing the code in this way ensures that the code is
+ the same for both modes. */
+ windows_continue_flags flags = WCONT_KILLED;
+
for (;;)
{
- if (!windows_continue (DBG_CONTINUE, -1, WCONT_KILLED))
+ if (!windows_continue (DBG_CONTINUE, -1, flags))
break;
DEBUG_EVENT current_event;
wait_for_debug_event_main_thread (¤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? */
@@ -3175,6 +4055,16 @@ windows_nat_target::thread_name (struct thread_info *thr)
}
+/* Implementation of the target_ops::supports_non_stop method. */
+
+bool
+windows_nat_target::supports_non_stop ()
+{
+ /* Non-stop support requires DBG_REPLY_LATER, which only exists on
+ Windows 10 and later. */
+ return dbg_reply_later_available ();
+}
+
void _initialize_windows_nat ();
void
_initialize_windows_nat ()
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 1d3b24540d6..7f5641bd265 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -344,8 +344,9 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
the_target->wait (minus_one_ptid, &status, 0);
- /* Note win32_wait doesn't return thread events. */
- if (status.kind () != TARGET_WAITKIND_LOADED)
+ if (status.kind () == TARGET_WAITKIND_EXITED
+ || status.kind () == TARGET_WAITKIND_SIGNALLED
+ || status.kind () == TARGET_WAITKIND_STOPPED)
{
windows_process.cached_status = status;
break;
@@ -604,7 +605,7 @@ win32_process_target::attach (unsigned long pid)
/* See nat/windows-nat.h. */
-DWORD
+bool
gdbserver_windows_process::handle_output_debug_string
(const DEBUG_EVENT ¤t_event,
struct target_waitstatus *ourstatus)
@@ -615,7 +616,7 @@ gdbserver_windows_process::handle_output_debug_string
DWORD nbytes = current_event.u.DebugString.nDebugStringLength;
if (nbytes == 0)
- return 0;
+ return false;
if (nbytes > READ_BUFFER_LEN)
nbytes = READ_BUFFER_LEN;
@@ -634,7 +635,7 @@ gdbserver_windows_process::handle_output_debug_string
else
{
if (read_inferior_memory (addr, (unsigned char *) s, nbytes) != 0)
- return 0;
+ return false;
}
if (!startswith (s, "cYg"))
@@ -642,14 +643,14 @@ gdbserver_windows_process::handle_output_debug_string
if (!server_waiting)
{
OUTMSG2(("%s", s));
- return 0;
+ return false;
}
monitor_output (s);
}
#undef READ_BUFFER_LEN
- return 0;
+ return false;
}
static void
diff --git a/gdbserver/win32-low.h b/gdbserver/win32-low.h
index 123fbc76f57..60ad00b9c0a 100644
--- a/gdbserver/win32-low.h
+++ b/gdbserver/win32-low.h
@@ -182,8 +182,8 @@ class win32_process_target : public process_stratum_target
struct gdbserver_windows_process : public windows_nat::windows_process_info
{
windows_nat::windows_thread_info *find_thread (ptid_t ptid) override;
- DWORD handle_output_debug_string (const DEBUG_EVENT ¤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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (38 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 39/47] Windows gdb: Add non-stop support Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-28 19:54 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
` (7 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
After the previous patch, windows_per_inferior::invalidate_context
isn't used anymore. Delete it.
Change-Id: I89839c2aaf3a7b0852be596feb1c345944dc8866
---
gdb/windows-nat.c | 14 --------------
1 file changed, 14 deletions(-)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 408df2acd78..4f7828beb36 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -337,7 +337,6 @@ struct windows_per_inferior : public windows_process_info
void handle_unload_dll (const DEBUG_EVENT ¤t_event) override;
bool handle_access_violation (const EXCEPTION_RECORD *rec) override;
- void invalidate_context (windows_thread_info *th);
void fill_thread_context (windows_thread_info *th) override;
void continue_one_thread (windows_thread_info *th,
@@ -887,19 +886,6 @@ windows_per_inferior::find_thread (ptid_t ptid)
return as_windows_thread_info (thr);
}
-/* Invalidate TH's context. */
-
-void
-windows_per_inferior::invalidate_context (windows_thread_info *th)
-{
-#ifdef __x86_64__
- if (windows_process.wow64_process)
- th->wow64_context.ContextFlags = 0;
- else
-#endif
- th->context.ContextFlags = 0;
-}
-
/* Add a thread to the thread list.
PTID is the ptid of the thread to be added.
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops)
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (39 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-30 20:50 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl Pedro Alves
` (6 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
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 actualy writes to ::wait avoids a bunch of
SuspendThread/ResumeThread sequences, so in principle should be
faster.
Change-Id: I39c2492c7aac06d23ef8f287f4afe3747b7bc53f
---
gdb/nat/windows-nat.h | 27 ++++++++--
gdb/windows-nat.c | 118 +++++++++++++++++++++++++++++++++++-------
2 files changed, 121 insertions(+), 24 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index fe377fcde34..568c7c46a51 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 4f7828beb36..71ef26501de 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -304,6 +304,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);
@@ -572,6 +576,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
return serial_event_fd (m_wait_event);
}
+ void debug_registers_changed_all_threads ();
+
private:
windows_thread_info *add_thread (ptid_t ptid, HANDLE h, void *tlb,
@@ -579,7 +585,8 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
void delete_thread (ptid_t ptid, DWORD exit_code, bool main_thread_p);
DWORD fake_create_process (const DEBUG_EVENT ¤t_event);
- void stop_one_thread (windows_thread_info *th);
+ void stop_one_thread (windows_thread_info *th,
+ enum stopping_kind stopping_kind);
DWORD continue_status_for_event_detaching
(const DEBUG_EVENT &event, size_t *reply_later_events_left = nullptr);
@@ -1369,7 +1376,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);
@@ -1629,7 +1636,7 @@ windows_per_inferior::continue_one_thread (windows_thread_info *th,
});
th->resume ();
- th->stopping = false;
+ th->stopping = SK_NOT_STOPPING;
th->last_sig = GDB_SIGNAL_0;
}
@@ -1704,8 +1711,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
@@ -1914,11 +1932,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);
@@ -1934,12 +1954,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);
@@ -1953,7 +1979,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
{
@@ -1968,14 +1996,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);
}
}
@@ -1988,7 +2022,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);
}
}
@@ -2493,6 +2527,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
@@ -2539,7 +2584,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
@@ -4158,6 +4203,41 @@ 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. */
+
+static void
+debug_registers_changed_all_threads ()
+{
+ auto *win_tgt = static_cast<windows_nat_target *> (get_native_target ());
+ win_tgt->debug_registers_changed_all_threads ();
+}
+
/* Hardware watchpoint support, adapted from go32-nat.c code. */
/* Pass the address ADDR to the inferior in the I'th debug register.
@@ -4169,8 +4249,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;
+ debug_registers_changed_all_threads ();
}
/* Pass the value VAL to the inferior in the DR7 debug control
@@ -4179,8 +4258,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;
+ debug_registers_changed_all_threads ();
}
/* Get the value of debug register I from the inferior. */
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (40 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-21 15:19 ` Tom de Vries
2025-05-19 13:23 ` [PATCH v2 43/47] Windows gdb: extra thread info => show exiting Pedro Alves
` (5 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
I wrote a test like this:
gdb_test_multiple "command" "" -lbl {
-re "^\r\nprefix foo(?=\r\n)" {
exp_continue
}
-re "^\r\nprefix bar(?=\r\n)" {
exp_continue
}
-re "^\r\prefix (?=\r\n)" {
exp_continue
}
-re "^\r\n$::gdb_prompt $" {
pass $gdb_test_name
}
}
Anchors are needed in my case to avoid too-eager matching due to the
common prefix.
The intent is for the prompt match above to override the built-in
prompt match. However, it doesn't and the test fails with output like
this:
(gdb) command
prefix foo
prefix bar
meant-to-be-matched-by-lbl
(gdb)
That's because the built-in match for the prompt matches before the
-lbl pattern for this expect buffer:
\r\nmeant-to-be-matched-by-lbl\r\n(gdb)
This this by anchoring the built-in prompt match if -lbl was
requested.
Change-Id: Ic2571ec793d856a89ee0d533ec363e2ac6036ea2
---
gdb/testsuite/lib/gdb.exp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index c51cea86a9d..6d64344bc0a 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1124,6 +1124,7 @@ proc gdb_test_multiple { command message args } {
global any_spawn_id
set line_by_line 0
+ set lbl_anchor_re ""
set prompt_regexp ""
set prompt_anchor 1
for {set i 0} {$i < [llength $args]} {incr i} {
@@ -1133,6 +1134,7 @@ proc gdb_test_multiple { command message args } {
set prompt_regexp [lindex $args $i]
} elseif { $arg == "-lbl" } {
set line_by_line 1
+ set lbl_anchor_re "^"
} elseif { $arg == "-no-prompt-anchor" } {
set prompt_anchor 0
} else {
@@ -1391,7 +1393,7 @@ proc gdb_test_multiple { command message args } {
fail "$errmsg"
set result -1
}
- -re "\r\n$prompt_regexp" {
+ -re "${lbl_anchor_re}\r\n$prompt_regexp" {
if {![string match "" $message]} {
fail "$message"
}
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 43/47] Windows gdb: extra thread info => show exiting
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (41 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-28 19:58 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
` (4 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
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.
Change-Id: I493b7ea3e14574dc972b1341eb5062fbbfda1521
---
gdb/windows-nat.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 71ef26501de..830ed0e381f 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -528,6 +528,8 @@ struct windows_nat_target final : public x86_nat_target<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;
@@ -4085,6 +4087,26 @@ 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. */
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (42 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 43/47] Windows gdb: extra thread info => show exiting Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-29 16:09 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 45/47] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
` (3 subsequent siblings)
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
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.
Change-Id: I52fb8de5e72bc12195ffb8bedd1d8070464332d3
---
.../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..25492f6c0a7
--- /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 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..a2745a22613
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/leader-exit-schedlock.exp
@@ -0,0 +1,215 @@
+# Copyright (C) 2025 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 $::binfile
+ }
+
+ 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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 45/47] infrun: with AS+NS, prefer process exit over thread exit
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (43 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-19 13:23 ` [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
` (2 subsequent siblings)
47 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 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 826093f79f9..81d0b1eb210 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -110,6 +110,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;
@@ -4767,7 +4769,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 *tp : inf->non_exited_threads ())
+ {
+ if (tp->has_pending_waitstatus ()
+ && ((tp->pending_waitstatus ().kind ()
+ == TARGET_WAITKIND_EXITED)
+ || (tp->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 = tp->pending_waitstatus ();
+ tp->clear_pending_waitstatus ();
+ ecs.event_thread = nullptr;
+ ecs.ptid = tp->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);
@@ -6091,6 +6154,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. 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;
+ 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.
@@ -6300,74 +6438,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..8244f7002b9
--- /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 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..ea0a429e49a
--- /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 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.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on")
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (44 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 45/47] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-29 16:02 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS Pedro Alves
2025-06-05 17:57 ` [PATCH v2 00/47] Windows non-stop mode Tom Tromey
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
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.
Change-Id: I83d23dbb1edc7692d5d8b37f5b9e0264c74d4940
---
gdb/windows-nat.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 830ed0e381f..bc574377ca5 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -570,6 +570,7 @@ struct windows_nat_target final : public x86_nat_target<inf_child_target>
}
bool supports_non_stop () override;
+ bool always_non_stop_p () override;
void async (bool enable) override;
@@ -4118,6 +4119,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 ();
+}
+
void _initialize_windows_nat ();
void
_initialize_windows_nat ()
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (45 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
@ 2025-05-19 13:23 ` Pedro Alves
2025-05-19 14:07 ` Eli Zaretskii
2025-06-05 17:57 ` [PATCH v2 00/47] Windows non-stop mode Tom Tromey
47 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-19 13:23 UTC (permalink / raw)
To: gdb-patches
Add a couple notes to gdb/NEWS mentioning:
- Windows native target scheduler-locking support.
- Windows native target non-stop support.
Change-Id: Id0e28525c06e57120c47b07f978581d1627d70f3
---
gdb/NEWS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/gdb/NEWS b/gdb/NEWS
index e0313bb37df..534ae60a877 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -48,6 +48,12 @@
* Add record full support for rv64gc architectures
+* 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 commands
maintenance check psymtabs
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes
2025-05-19 13:22 ` [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes Pedro Alves
@ 2025-05-19 13:56 ` Andrew Burgess
2025-06-06 13:56 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Andrew Burgess @ 2025-05-19 13:56 UTC (permalink / raw)
To: Pedro Alves, gdb-patches
Pedro Alves <pedro@palves.net> writes:
> For some reason, when testing GDB on Cygwin, I get:
>
> child process exited abnormally
> while executing
> "exec sh -c "exec > /dev/null 2>&1 && (kill -2 -$spid || kill -2 $spid)""
> (procedure "close_wait_program" line 20)
> invoked from within
> "close_wait_program $shell_id $pid"
> (procedure "standard_close" line 23)
> invoked from within
> "standard_close "Windows-ROCm""
> ("eval" body line 1)
> invoked from within
> "eval ${try}_${proc} \"$dest\" $args"
> (procedure "call_remote" line 42)
> invoked from within
> "call_remote "" close $host"
> (procedure "remote_close" line 3)
> invoked from within
> "remote_close host"
> (procedure "log_and_exit" line 30)
> invoked from within
> "log_and_exit"
>
> When that happens from within clean_restart, clean_restart doesn't
> clear the gdb_spawn_id variable, and then when clean_restart starts up
> a new GDB, that sees that gdb_spawn_id is already set, so it doesn't
> actually spawn a new GDB, and so clean_restart happens to reuse the
> same GDB (!). Many tests happen to actually work OK with this, but
> some don't, and the failure modes can be head-scratching.
>
> Of course, the failure to close GDB should be fixed, but when it
> happens, I think it's good to not end up with the current weird state.
> Connecting the "child process exit abnormally" errors at the end of a
> testcase run with weird FAILs in other testcases took me a while (as
> in, weeks!), it wasn't obvious to me immediately.
>
> Thus, this patch makes default_gdb_exit more resilient to failed
> closes, so that gdb_spawn_id is unset even is closing GDB fails, and
> we move on to start a new GDB.
>
> Change-Id: I9ec95aa61872a40095775534743525e0ad2097d2
Makes sense to me.
Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks,
Andrew
> ---
> gdb/testsuite/lib/gdb.exp | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 49ef0d54654..c51cea86a9d 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -2315,7 +2315,9 @@ proc default_gdb_exit {} {
> }
>
> if ![is_remote host] {
> - remote_close host
> + if {[catch { remote_close host } message]} {
> + warning "closing gdb failed with: $message"
> + }
> }
> unset gdb_spawn_id
> unset ::gdb_tty_name
> --
> 2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS
2025-05-19 13:23 ` [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS Pedro Alves
@ 2025-05-19 14:07 ` Eli Zaretskii
0 siblings, 0 replies; 97+ messages in thread
From: Eli Zaretskii @ 2025-05-19 14:07 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 19 May 2025 14:23:08 +0100
>
> Add a couple notes to gdb/NEWS mentioning:
>
> - Windows native target scheduler-locking support.
> - Windows native target non-stop support.
>
> Change-Id: Id0e28525c06e57120c47b07f978581d1627d70f3
> ---
> gdb/NEWS | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index e0313bb37df..534ae60a877 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -48,6 +48,12 @@
>
> * Add record full support for rv64gc architectures
>
> +* 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 commands
Thanks, this is okay.
Approved-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-19 13:23 ` [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl Pedro Alves
@ 2025-05-21 15:19 ` Tom de Vries
2025-05-27 22:41 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Tom de Vries @ 2025-05-21 15:19 UTC (permalink / raw)
To: Pedro Alves, gdb-patches
[-- Attachment #1: Type: text/plain, Size: 1944 bytes --]
On 5/19/25 15:23, Pedro Alves wrote:
> I wrote a test like this:
>
> gdb_test_multiple "command" "" -lbl {
> -re "^\r\nprefix foo(?=\r\n)" {
> exp_continue
> }
> -re "^\r\nprefix bar(?=\r\n)" {
> exp_continue
> }
> -re "^\r\prefix (?=\r\n)" {
Hi Pedro,
Missing n in "\r\n".
> exp_continue
> }
> -re "^\r\n$::gdb_prompt $" {
> pass $gdb_test_name
> }
> }
>
> Anchors are needed in my case to avoid too-eager matching due to the
> common prefix.
I'm not saying you don't need anchors, maybe you do or maybe you don't,
but I don't understand how a common prefix is a reason to need one.
Also, if you need anchors, you need to match the command as well, so you
need a -re "^command(?=\r\n)" clause.
> The intent is for the prompt match above to override the built-in
> prompt match. However, it doesn't and the test fails with output like
> this:
>
> (gdb) command
> prefix foo
> prefix bar
> meant-to-be-matched-by-lbl
> (gdb)
>
> That's because the built-in match for the prompt matches before the
> -lbl pattern for this expect buffer:
>
> \r\nmeant-to-be-matched-by-lbl\r\n(gdb)
>
You can fix that by dropping the anchor and using "\r\n$::gdb_prompt $"
instead. Even more simple, use -wrap "".
> This this by anchoring the built-in prompt match if -lbl was
> requested.
>
This might be a good idea indeed.
FWIW, the intent of -lbl was to handle except buffers overflows, so the
-lbl pattern is meant to match lines in absence of a prompt.
But AFAIU, your patch will make matching more predictable, so that's a
good thing.
I created a mockup test-case to emulate the case you describe above.
I've left the anchoring in place for the prefix lines, but for the gdb
output printed by gdb.sh, it doesn't look necessary. [ The test-case has
hardcoded paths in it. ]
Thanks,
- Tom
[-- Attachment #2: 0001-try.patch --]
[-- Type: text/x-patch, Size: 2376 bytes --]
From 91363128b5918a16a8426ca31b43927004d1535d Mon Sep 17 00:00:00 2001
From: Tom de Vries <tdevries@suse.de>
Date: Wed, 21 May 2025 17:13:55 +0200
Subject: [PATCH] try
---
.../gdb.testsuite/gdb-test-multiple.exp | 25 ++++++++++
gdb/testsuite/gdb.testsuite/gdb.sh | 47 +++++++++++++++++++
2 files changed, 72 insertions(+)
create mode 100644 gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
create mode 100755 gdb/testsuite/gdb.testsuite/gdb.sh
diff --git a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
new file mode 100644
index 00000000000..ac0674ef20f
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
@@ -0,0 +1,25 @@
+set GDB $srcdir/$subdir/gdb.sh
+clean_restart
+
+gdb_test_multiple "command" "" -lbl {
+ -re "^command(?=\r\n)" {
+ verbose -log <COMMAND>
+ exp_continue
+ }
+ -re "^\r\nprefix foo(?=\r\n)" {
+ verbose -log <PREFIX-FOO>
+ exp_continue
+ }
+ -re "^\r\nprefix bar(?=\r\n)" {
+ verbose -log <PREFIX-BAR>
+ exp_continue
+ }
+ -re "^\r\nprefix (?=\r\n)" {
+ verbose -log <PREFIX>
+ exp_continue
+ }
+ -re -wrap "" {
+ verbose -log "<PROMPT>"
+ pass $gdb_test_name
+ }
+}
diff --git a/gdb/testsuite/gdb.testsuite/gdb.sh b/gdb/testsuite/gdb.testsuite/gdb.sh
new file mode 100755
index 00000000000..9c358f1a204
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+while [ $# -gt 0 ]; do
+ case $1 in
+ --version)
+ echo "gdb.sh"
+ exit 0
+ ;;
+ *)
+ ;;
+ esac
+
+ shift
+done
+
+while true; do
+ echo -n "(gdb) "
+ read line
+
+ if [ "$line" = "set height 0" ]; then
+ continue
+ fi
+
+ if [ "$line" = "set width 0" ]; then
+ continue
+ fi
+
+ if [ "$line" = "dir" ]; then
+ echo -n "Reinitialize source path to empty? (y or n) "
+ read response
+ echo 'Source directories searched: $cdir:$cwd'
+ continue
+ fi
+
+ if [ "$line" = "dir /data/vries/gdb/src/gdb/testsuite/gdb.testsuite" ]; then
+ echo "Source directories searched: /data/vries/gdb/src/gdb/testsuite/gdb.testsuite:$cdir:$cwd"
+ continue
+ fi
+
+ if [ "$line" = "command" ]; then
+ echo "prefix foo"
+ echo "prefix bar"
+ for n in $(seq 1 1); do
+ echo "meant-to-be-matched-by-lbl-$n"
+ done
+ fi
+done
base-commit: 14dd98b0f7f04d25bc7bc63de3ab958bdd51641d
--
2.43.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 02/47] Add test for continuing with some threads running
2025-05-19 13:22 ` [PATCH v2 02/47] Add test for continuing with some threads running Pedro Alves
@ 2025-05-21 19:36 ` Kevin Buettner
2026-04-02 13:07 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Kevin Buettner @ 2025-05-21 19:36 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
On Mon, 19 May 2025 14:22:23 +0100
Pedro Alves <pedro@palves.net> wrote:
> This testcase would have helped catch some issues I ran into while
> working on the Windows non-stop support.
>
> It tests continuing all threads in all-stop mode when at least one
> thread is already running.
LGTM.
Approved-by: Kevin Buettner <kevinb@redhat.com>
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call
2025-05-19 13:22 ` [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call Pedro Alves
@ 2025-05-21 19:44 ` Kevin Buettner
2026-04-02 13:17 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Kevin Buettner @ 2025-05-21 19:44 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
On Mon, 19 May 2025 14:22:24 +0100
Pedro Alves <pedro@palves.net> wrote:
> There's one unnecessary check for currently_stepping in
> handle_signal_stop that can be removed. It is unnecessary because
> currently_stepping is only ever called if
> ecs->event_thread->control.trap_expected is true, and then if it is
> true, then currently_stepping always returns true too.
After looking at the definition of currently_stepping(), I agree
with your reasoning (and your patch).
Approved-by: Kevin Buettner <kevinb@redhat.com>
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-21 15:19 ` Tom de Vries
@ 2025-05-27 22:41 ` Pedro Alves
2025-05-27 23:20 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-27 22:41 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
[-- Attachment #1: Type: text/plain, Size: 4988 bytes --]
Hi!
On 2025-05-21 16:19, Tom de Vries wrote:
> On 5/19/25 15:23, Pedro Alves wrote:
>> I wrote a test like this:
>>
>> gdb_test_multiple "command" "" -lbl {
>> -re "^\r\nprefix foo(?=\r\n)" {
>> exp_continue
>> }
>> -re "^\r\nprefix bar(?=\r\n)" {
>> exp_continue
>> }
>> -re "^\r\prefix (?=\r\n)" {
>
> Hi Pedro,
>
> Missing n in "\r\n".
>
>> exp_continue
>> }
>> -re "^\r\n$::gdb_prompt $" {
>> pass $gdb_test_name
>> }
>> }
>>
>> Anchors are needed in my case to avoid too-eager matching due to the
>> common prefix.
> I'm not saying you don't need anchors, maybe you do or maybe you don't, but I don't understand how a common prefix is a reason to need one.
>
> Also, if you need anchors, you need to match the command as well, so you need a -re "^command(?=\r\n)" clause.
Without anchors, then if the fake GDB outputs this:
echo "prefix "
echo "prefix foo"
echo "prefix bar"
echo "prefix "
and the expect buffer happens to contain at least:
"prefix \r\nprefix foo\r\n"
... then the "prefix foo" pattern consumes the first "prefix " line inadvertently.
E.g., the version of the test that I'm attaching fails exactly like that, with:
(gdb) command
prefix
prefix foo
prefix bar
prefix
meant-to-be-matched-by-lbl-1
(gdb) <COMMAND>
<PREFIX-FOO>
<PREFIX-BAR>
<PREFIX>
<PROMPT>
PASS: gdb.testsuite/gdb-test-multiple.exp: command
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_command == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prompt == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix == 2
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_foo == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_bar == 1
saw_prompt: 1
saw_prefix: 1
saw_prefix_foo: 1
saw_prefix_bar: 1
This version is counting the matches, which notices that we only
saw "prefix" once, while we should have seen it twice.
That is patch #1 in the series I've attached.
>
>> The intent is for the prompt match above to override the built-in
>> prompt match. However, it doesn't and the test fails with output like
>> this:
>>
>> (gdb) command
>> prefix foo
>> prefix bar
>> meant-to-be-matched-by-lbl
>> (gdb)
>>
>> That's because the built-in match for the prompt matches before the
>> -lbl pattern for this expect buffer:
>>
>> \r\nmeant-to-be-matched-by-lbl\r\n(gdb)
>>
>
> You can fix that by dropping the anchor and using "\r\n$::gdb_prompt $" instead. Even more simple, use -wrap "".
That doesn't work properly. See the commit logs of the patches I've attached. The series is meant to show the
incremental results as we get to the final fully-passing testcase. One of the steps adds a prompt match like that,
and the slightly tweaked testcase in the same patch fails. Take a look at the commit logs of the patches, in
sequence, please. It is easier to see it incrementally like that, I think.
>
>> This this by anchoring the built-in prompt match if -lbl was
>> requested.
>>
>
> This might be a good idea indeed.
>
> FWIW, the intent of -lbl was to handle except buffers overflows, so the -lbl pattern is meant to match lines in absence of a prompt.
FYI, I needed this for an earlier version of the testcase in patch #44 of this series:
https://inbox.sourceware.org/gdb-patches/20250519132308.3553663-45-pedro@palves.net/T/#u
That is doing "info threads", and extracting the output of every thread. If you search
for ${common_prefix} in there, you'll find it.
I can't trigger the original issue with that version anymore without the gdb_test_multiple fix,
though, I think because the patterns in the current version consume all lines, so there's
no "meant-to-be-matched-by-lbl" anymore. I guess I don't really need -lbl there anymore at all.
Still, this new testcase based on what yours does need it. I can try to combine all of
this in a single patch, including the new testcase and a better description. WDYT?
This idea of replacing GDB with a script is super interesting. Did you consider writing that script
in tcl/expect, though? Seems like it would be a perfect fit. Read input, match against pattern, print
output. :-P Another alternative would be to write a bit of GDB python to register a new command, and
run the real GDB with that. But a script like you came up with mocking GDB completely is nice, it's
very light and more decoupled.
>
> But AFAIU, your patch will make matching more predictable, so that's a good thing.
>
> I created a mockup test-case to emulate the case you describe above. I've left the anchoring in place for the prefix lines, but for the gdb output printed by gdb.sh, it doesn't look necessary. [ The test-case has hardcoded paths in it. ]
[ The version I attached does not hardcode paths any more. ]
Thanks,
Pedro Alves
[-- Attachment #2: 0001-With-no-anchors-in-testcase-patterns-eat-too-much.patch --]
[-- Type: text/x-patch, Size: 7008 bytes --]
From c422a58d258e1a9a14a911c31a492b84518e300c Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 27 May 2025 20:01:36 +0100
Subject: [PATCH 1/4] With no anchors in testcase, patterns eat too much
Without anchors, then if the fake GDB outputs this:
echo "prefix "
echo "prefix foo"
echo "prefix bar"
echo "prefix "
and the expect buffer happens to contain at least:
"prefix \r\nprefix foo\r\n"
... then the "prefix foo" pattern match may well consume the first "prefix " line.
Full relevant parts of the log:
(gdb) command
prefix
prefix foo
prefix bar
prefix
<COMMAND>
<PREFIX-FOO>
<PREFIX-BAR>
<PREFIX>
meant-to-be-matched-by-lbl-1
(gdb) <PROMPT>
PASS: gdb.testsuite/gdb-test-multiple.exp: command
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_command == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prompt == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix == 3
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_foo == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_bar == 1
saw_prompt: 1
saw_prefix: 1
saw_prefix_foo: 1
saw_prefix_bar: 1
Change-Id: Ib59084bad199f3fe945c14fbe1970e730104d3f4
---
.../gdb.testsuite/gdb-test-multiple.exp | 47 ++++++++++++++++++
gdb/testsuite/gdb.testsuite/gdb.sh | 49 +++++++++++++++++++
2 files changed, 96 insertions(+)
create mode 100644 gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
create mode 100755 gdb/testsuite/gdb.testsuite/gdb.sh
diff --git a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
new file mode 100644
index 00000000000..a1c9d95a71c
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
@@ -0,0 +1,47 @@
+set GDB $srcdir/$subdir/gdb.sh
+clean_restart
+
+set saw_prompt 0
+set saw_prefix 0
+set saw_command 0
+set saw_prefix_foo 0
+set saw_prefix_bar 0
+
+gdb_test_multiple "command" "" -lbl {
+ -re "command(?=\r\n)" {
+ verbose -log <COMMAND>
+ incr saw_command
+ exp_continue
+ }
+ -re "\r\nprefix foo(?=\r\n)" {
+ verbose -log <PREFIX-FOO>
+ incr saw_prefix_foo
+ exp_continue
+ }
+ -re "\r\nprefix bar(?=\r\n)" {
+ verbose -log <PREFIX-BAR>
+ incr saw_prefix_bar
+ exp_continue
+ }
+ -re "\r\nprefix (?=\r\n)" {
+ verbose -log <PREFIX>
+ incr saw_prefix
+ exp_continue
+ }
+ -re -wrap "" {
+ verbose -log "<PROMPT>"
+ incr saw_prompt
+ pass $gdb_test_name
+ }
+}
+
+gdb_assert {$saw_command == 1}
+gdb_assert {$saw_prompt == 1}
+gdb_assert {$saw_prefix == 3}
+gdb_assert {$saw_prefix_foo == 1}
+gdb_assert {$saw_prefix_bar == 1}
+
+verbose -log "saw_prompt: $saw_prompt"
+verbose -log "saw_prefix: $saw_prefix"
+verbose -log "saw_prefix_foo: $saw_prefix_foo"
+verbose -log "saw_prefix_bar: $saw_prefix_bar"
diff --git a/gdb/testsuite/gdb.testsuite/gdb.sh b/gdb/testsuite/gdb.testsuite/gdb.sh
new file mode 100755
index 00000000000..1e9ceee8e23
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+while [ $# -gt 0 ]; do
+ case $1 in
+ --version)
+ echo "gdb.sh"
+ exit 0
+ ;;
+ *)
+ ;;
+ esac
+
+ shift
+done
+
+while true; do
+ echo -n "(gdb) "
+ read line
+
+ if [ "$line" = "set height 0" ]; then
+ continue
+ fi
+
+ if [ "$line" = "set width 0" ]; then
+ continue
+ fi
+
+ if [ "$line" = "dir" ]; then
+ echo -n "Reinitialize source path to empty? (y or n) "
+ read response
+ echo 'Source directories searched: $cdir:$cwd'
+ continue
+ fi
+ case "$line" in
+ dir\ */gdb/testsuite/gdb.testsuite)
+ echo "Source directories searched: ${line#dir }:\$cdir:\$cwd"
+ continue
+ ;;
+ esac
+ if [ "$line" = "command" ]; then
+ echo "prefix "
+ echo "prefix foo"
+ echo "prefix bar"
+ echo "prefix "
+ for n in $(seq 1 1); do
+ echo "meant-to-be-matched-by-lbl-$n"
+ done
+ fi
+done
base-commit: e1ec485cfa29f9c45370b9d0c12480ebb0a0be06
prerequisite-patch-id: 474281ed1de7dcad710a9f1415fe4866fe3e36cb
prerequisite-patch-id: db67eaaae54588f294023b4e09c2b07a2c11bfd0
prerequisite-patch-id: a222a1d72a184652ea37e9dd59e71ff17393ef19
prerequisite-patch-id: bfa3ae2badc22d752f6212df53e48d4bfb1efb6e
prerequisite-patch-id: 8b73964c2d816ebad0bfd0abb7601da7c81f466c
prerequisite-patch-id: b69ea29a21a9839f6e95f297466366237e6edbf1
prerequisite-patch-id: eb9f42ffabf999eedc30abd73e09cf3fd459ba91
prerequisite-patch-id: 8e06340fe19a29e06ffe8d90a9dd19795ce64e27
prerequisite-patch-id: 135991e668bb3fa937e744f3c07e63fa05e878dc
prerequisite-patch-id: 32acbb917d8f602933a20e38dea8b3987769e39f
prerequisite-patch-id: 46e4fac4e94089a99d11209b6ad630a806a2e4d3
prerequisite-patch-id: cbb2bb32051e83248c3825a961678ddc14e6c239
prerequisite-patch-id: e679fb7c98029f6b92b68777ec9a110fc75b593d
prerequisite-patch-id: ef122d379928672ae2fab1d7ee706b90c02c4750
prerequisite-patch-id: 65b0845df2d94f26861ee3357bab6ce92c96c25c
prerequisite-patch-id: a103df53b3ef1a97fdffd820b012bd1c72c82180
prerequisite-patch-id: bd527aeb586e434f7357e5a593bb8dba2cd0878c
prerequisite-patch-id: 832ccaad1c09afe161108daca97458d37bd67dc2
prerequisite-patch-id: 933ca66366195b76de3a95716fcd252e0ccc9ca2
prerequisite-patch-id: 2545f5482ce0a05046a100e3c6bf86f761a06a8d
prerequisite-patch-id: acc9895410e57a61f20c2fcd3d90afa9bd0a0270
prerequisite-patch-id: 783f3bb7bb51ed1edfe331f58f165c49bda913a2
prerequisite-patch-id: 3c6509d96965cb05c145d47e379dcf3de138296c
prerequisite-patch-id: 47c465a5027e5e3f1435868b86995162c3da3667
prerequisite-patch-id: ae032bc2dae31818bc1a3deb0896c896cb6f26f6
prerequisite-patch-id: 5d600a3390848d8f61694d2c54e452f18024c82c
prerequisite-patch-id: 7b0639be8b8cb109e7adc39d1c7cb62d9cd010f9
prerequisite-patch-id: 55c35cb6d45485452f8ceef9cd89de133d8b06ef
prerequisite-patch-id: fdd325cf0e778f4002b70fdfe2750056fd19f183
prerequisite-patch-id: 9c91a5ecdf8cc2a65b44b00605e5ae3b657fd8d6
prerequisite-patch-id: 4af4e9882c7fd57c12179acaac1cae06771840ba
prerequisite-patch-id: 95bd3ddc6c3d9f67601a936bb7ee54611c50eefa
prerequisite-patch-id: 574177f4d00b4112cb32b16d946786fb85fce03e
prerequisite-patch-id: de0addc21c3c5b69f8f2a7bdf6ce40895be33e99
prerequisite-patch-id: 52ec46c9db5b40182ac5594e27c57f967cfff534
prerequisite-patch-id: f0448115f10b26fa6bfdc05f27054b1d1a2eeaca
prerequisite-patch-id: 308662b1ecab531416d03772e121ad70f530fd2e
prerequisite-patch-id: 0dd3962a60a8455b3f29d2be874f89b22613c6e2
prerequisite-patch-id: b1db95b11d47464a51f4dd9341305a1fefbe261a
prerequisite-patch-id: 8e472ea6227d5fe8cfb278475eba364ab349c921
prerequisite-patch-id: 4b0e0479048fa69b9236726c31d4d3a1c4eb8d25
prerequisite-patch-id: 385c15c31aa8164af6d45e1a667448ae34a33eab
prerequisite-patch-id: 54baec47a1e16993889d013307fa76712e1c70fd
prerequisite-patch-id: cb790ccd633dcd7c802038eddf8ee41aa45b91a3
prerequisite-patch-id: 49703c9edd4a081dc8287e87ac0c6badb762e147
prerequisite-patch-id: 72d1db10cd1fa53fe070141bcfbe084d201b96e3
prerequisite-patch-id: 2245f4e6ca9330f3eb216d32e413d7079f3ae1c2
prerequisite-patch-id: 9e823607f8c00be8d064a1206f6f5d3fa996322a
--
2.49.0
[-- Attachment #3: 0002-Testcase-showing-need-for-prompt-anchor-too.patch --]
[-- Type: text/x-patch, Size: 3588 bytes --]
From c6a0aacd51a4f5d72b46a313eba1789aedfc883e Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 27 May 2025 20:01:36 +0100
Subject: [PATCH 2/4] Testcase showing need for prompt anchor too
Let's add some interspersed lines meant to be consumed by -lbl, an
<LBL> tag for when the built-in -lbl match triggers, and add anchors
to the testcase's "prefix" matches.
With that, the prefix matches no longer each too much, but the
testcase now fails like so:
(gdb) command
prefix
meant-to-be-matched-by-lbl-1
prefix foo
prefix bar
meant-to-be-matched-by-lbl-2
prefix
prefix
meant-to-be-matched-by-lbl-3
(gdb) <COMMAND>
<PREFIX>
<PROMPT>
PASS: gdb.testsuite/gdb-test-multiple.exp: command
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_command == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prompt == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix == 3
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_foo == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_bar == 1
saw_prompt: 1
saw_prefix: 1
saw_prefix_foo: 0
saw_prefix_bar: 0
What happened was that expect managed to put all the output up to and
including the prompt in the expect buffer, and since the testcase's
prompt match does not have an ^\r\n anchor, the built-in prompt match
consumed all the output in the expected buffer (since the previous
<PREFIX> match).
Change-Id: Ib59084bad199f3fe945c14fbe1970e730104d3f4
---
gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp | 8 ++++----
gdb/testsuite/gdb.testsuite/gdb.sh | 7 ++++---
gdb/testsuite/lib/gdb.exp | 1 +
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
index a1c9d95a71c..d593d5dc81a 100644
--- a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
+++ b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
@@ -8,22 +8,22 @@ set saw_prefix_foo 0
set saw_prefix_bar 0
gdb_test_multiple "command" "" -lbl {
- -re "command(?=\r\n)" {
+ -re "^command(?=\r\n)" {
verbose -log <COMMAND>
incr saw_command
exp_continue
}
- -re "\r\nprefix foo(?=\r\n)" {
+ -re "^\r\nprefix foo(?=\r\n)" {
verbose -log <PREFIX-FOO>
incr saw_prefix_foo
exp_continue
}
- -re "\r\nprefix bar(?=\r\n)" {
+ -re "^\r\nprefix bar(?=\r\n)" {
verbose -log <PREFIX-BAR>
incr saw_prefix_bar
exp_continue
}
- -re "\r\nprefix (?=\r\n)" {
+ -re "^\r\nprefix (?=\r\n)" {
verbose -log <PREFIX>
incr saw_prefix
exp_continue
diff --git a/gdb/testsuite/gdb.testsuite/gdb.sh b/gdb/testsuite/gdb.testsuite/gdb.sh
index 1e9ceee8e23..55bd6473bf8 100755
--- a/gdb/testsuite/gdb.testsuite/gdb.sh
+++ b/gdb/testsuite/gdb.testsuite/gdb.sh
@@ -39,11 +39,12 @@ while true; do
esac
if [ "$line" = "command" ]; then
echo "prefix "
+ echo "meant-to-be-matched-by-lbl-1"
echo "prefix foo"
echo "prefix bar"
+ echo "meant-to-be-matched-by-lbl-2"
echo "prefix "
- for n in $(seq 1 1); do
- echo "meant-to-be-matched-by-lbl-$n"
- done
+ echo "prefix "
+ echo "meant-to-be-matched-by-lbl-3"
fi
done
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index c51cea86a9d..3515d88bae5 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1431,6 +1431,7 @@ proc gdb_test_multiple { command message args } {
if {$line_by_line} {
append code {
-re "\r\n\[^\r\n\]*(?=\r\n)" {
+ verbose -log "<LBL>"
exp_continue
}
}
--
2.49.0
[-- Attachment #4: 0003-Anchor-prompt-in-testcase.patch --]
[-- Type: text/x-patch, Size: 2151 bytes --]
From 7579b5e08a4d1a0370a5551353e782da15dd936c Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 27 May 2025 22:46:40 +0100
Subject: [PATCH 3/4] Anchor prompt in testcase
If we anchor the prompt in the testcase, so that it doesn't consume
too much, we now see:
(gdb) command
prefix
meant-to-be-matched-by-lbl-1
prefix foo
prefix bar
meant-to-be-matched-by-lbl-2
prefix
prefix
meant-to-be-matched-by-lbl-3
(gdb) <COMMAND>
<PREFIX>
<BUILT-IN PROMPT>
FAIL: gdb.testsuite/gdb-test-multiple.exp: command
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_command == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prompt == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix == 3
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_foo == 1
FAIL: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_bar == 1
saw_prompt: 0
saw_prefix: 1
saw_prefix_foo: 0
saw_prefix_bar: 0
I.e., the built-in prompt match was not overriden, and that one
consumed too much output, like in the previous patch our prompt match
did.
Change-Id: Id0080b6b04450d6eb5043d682efdbbd67b4139b4
---
gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp | 2 +-
gdb/testsuite/lib/gdb.exp | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
index d593d5dc81a..f6f254ddf8f 100644
--- a/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
+++ b/gdb/testsuite/gdb.testsuite/gdb-test-multiple.exp
@@ -28,7 +28,7 @@ gdb_test_multiple "command" "" -lbl {
incr saw_prefix
exp_continue
}
- -re -wrap "" {
+ -re "^\r\n$gdb_prompt $" {
verbose -log "<PROMPT>"
incr saw_prompt
pass $gdb_test_name
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 3515d88bae5..cd920d2624c 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1392,6 +1392,7 @@ proc gdb_test_multiple { command message args } {
set result -1
}
-re "\r\n$prompt_regexp" {
+ verbose -log "<BUILT-IN PROMPT>"
if {![string match "" $message]} {
fail "$message"
}
--
2.49.0
[-- Attachment #5: 0004-gdb_test_multiple-Anchor-prompt-match-if-lbl.patch --]
[-- Type: text/x-patch, Size: 2255 bytes --]
From 05f32b5c30d52bd46dc671e923c38becdf7227b5 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Thu, 17 Apr 2025 22:55:25 +0100
Subject: [PATCH 4/4] gdb_test_multiple: Anchor prompt match if -lbl
This is the original patch (rebased on the <BUILT-IN PROMPT> thingy).
If we make the built-in prompt have an anchor as well, then it won't
match prematurely, and our own prompt match overrides it. So now we
get a clean pass:
(gdb) command
prefix
meant-to-be-matched-by-lbl-1
prefix foo
prefix bar
meant-to-be-matched-by-lbl-2
prefix
prefix
meant-to-be-matched-by-lbl-3
(gdb) <COMMAND>
<PREFIX>
<LBL>
<PREFIX-FOO>
<PREFIX-BAR>
<LBL>
<PREFIX>
<PREFIX>
<LBL>
<PROMPT>
PASS: gdb.testsuite/gdb-test-multiple.exp: command
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_command == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prompt == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix == 3
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_foo == 1
PASS: gdb.testsuite/gdb-test-multiple.exp: $saw_prefix_bar == 1
saw_prompt: 1
saw_prefix: 3
saw_prefix_foo: 1
saw_prefix_bar: 1
Change-Id: Ic2571ec793d856a89ee0d533ec363e2ac6036ea2
---
gdb/testsuite/lib/gdb.exp | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index cd920d2624c..6e97d7a35d9 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1124,6 +1124,7 @@ proc gdb_test_multiple { command message args } {
global any_spawn_id
set line_by_line 0
+ set lbl_anchor_re ""
set prompt_regexp ""
set prompt_anchor 1
for {set i 0} {$i < [llength $args]} {incr i} {
@@ -1133,6 +1134,7 @@ proc gdb_test_multiple { command message args } {
set prompt_regexp [lindex $args $i]
} elseif { $arg == "-lbl" } {
set line_by_line 1
+ set lbl_anchor_re "^"
} elseif { $arg == "-no-prompt-anchor" } {
set prompt_anchor 0
} else {
@@ -1391,7 +1393,7 @@ proc gdb_test_multiple { command message args } {
fail "$errmsg"
set result -1
}
- -re "\r\n$prompt_regexp" {
+ -re "${lbl_anchor_re}\r\n$prompt_regexp" {
verbose -log "<BUILT-IN PROMPT>"
if {![string match "" $message]} {
fail "$message"
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-27 22:41 ` Pedro Alves
@ 2025-05-27 23:20 ` Pedro Alves
2025-05-28 11:59 ` [PATCH v2] of " Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-27 23:20 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
Ever stepped away from the computer for a few minutes, and had a D'oh! moment? Just happened here.
On 2025-05-27 23:41, Pedro Alves wrote:
> This idea of replacing GDB with a script is super interesting. Did you consider writing that script
> in tcl/expect, though? Seems like it would be a perfect fit. Read input, match against pattern, print
> output. :-P Another alternative would be to write a bit of GDB python to register a new command, and
> run the real GDB with that. But a script like you came up with mocking GDB completely is nice, it's
> very light and more decoupled.
Somehow I didn't recall that we can define commands in the CLI with "define foo". :-)
I think the best & simplest in this case is to do just that. Like, put this in a gdb script (*):
define command
echo prefix \n
echo prefix foo\n
echo prefix bar\n
echo prefix \n
end
then run the testcase with GDB as normal, load the script, and use gdb_test_multiple, all as usual.
I can take care of that.
* - or invoke "define" manually with gdb_test/gdb_test_multiple like some testcases do.
Pedro Alves
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v2] of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-27 23:20 ` Pedro Alves
@ 2025-05-28 11:59 ` Pedro Alves
2025-06-05 16:37 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-05-28 11:59 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
Hi!
On 2025-05-28 00:20, Pedro Alves wrote:
> On 2025-05-27 23:41, Pedro Alves wrote:
>
> Somehow I didn't recall that we can define commands in the CLI with "define foo". :-)
>
> I think the best & simplest in this case is to do just that. Like, put this in a gdb script (*):
>
> define command
> echo prefix \n
> echo prefix foo\n
> echo prefix bar\n
> echo prefix \n
> end
>
> then run the testcase with GDB as normal, load the script, and use gdb_test_multiple, all as usual.
>
> I can take care of that.
I went ahead and did this. Here's the new patch, now with a testcase, and a new commit log,
with a better explanation.
Let me know what you think of this one.
--- 8< ---
From d3a75f89b701d32958cc3f54642204054984c17c Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 28 May 2025 10:40:46 +0100
Subject: [PATCH] gdb_test_multiple: Anchor prompt match if -lbl
The testcase added by this patch has a gdb_test_multiple call that
wants to match different lines of output that all have a common
prefix, and do different actions on each. Instead of a single regular
expression with alternatives, its clearer code if the different
expressions are handled with different "-re", like so:
gdb_test_multiple "command" "" -lbl {
-re "^command(?=\r\n)" {
exp_continue
}
-re "^\r\nprefix foo(?=\r\n)" {
# some action
exp_continue
}
-re "^\r\nprefix bar(?=\r\n)" {
# some other action
exp_continue
}
-re "^\r\nprefix (?=\r\n)" {
# yet another action
exp_continue
}
-re "^\r\n$::gdb_prompt $" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
}
Above, the leading anchors in the "^\r\nprefix..." matches are needed
to avoid too-eager matching due to the common prefix. Without the
anchors, if the expect output buffer happens to contain at least:
"\r\nprefix \r\nprefix foo\r\n"
... then the "prefix foo" pattern match inadvertently consumes the
first "prefix " line.
Without the anchor in the prompt match, like:
-re "\r\n$::gdb_prompt $" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
Or the equivalent:
-re -wrap "" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
... then if the expect buffer contains:
"\r\nmeant-to-be-matched-by-lbl\r\nprefix foo\r\n$gdb_prompt "
... then the prompt regexp matches this, consuming the "prefix" line
inadvertently, and we get a FAIL. The built-in regexp matcher for
-lbl doesn't get a chance to match the
"\r\nmeant-to-be-matched-by-lbl\r\n" part, because the built-in prompt
match appears first within gdb_test_multiple.
By adding the anchor to the prompt regexp, then we avoid that problem.
However, the same expect output buffer contents will still match the
built-in prompt match. That is what is fixed by this patch. It makes
it so that if -lbl is specified, the built-in prompt regexp has a
leading anchor.
Original idea for turning this into a gdb.testsuite/ testcase by Tom
de Vries <tdevries@suse.de>.
Change-Id: Ic2571ec793d856a89ee0d533ec363e2ac6036ea2
---
.../gdb.testsuite/gdb_test_multiple-lbl.exp | 84 +++++++++++++++++++
.../gdb.testsuite/gdb_test_multiple-lbl.gdb | 25 ++++++
gdb/testsuite/lib/gdb.exp | 4 +-
3 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
create mode 100755 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
new file mode 100644
index 00000000000..1ed352ddad7
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
@@ -0,0 +1,84 @@
+# Copyright 2025 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 gdb_test_multiple -lbl, particularly with patterns that share a
+# common prefix.
+
+standard_testfile
+
+clean_restart
+
+gdb_test_no_output "source ${srcdir}/${subdir}/$testfile.gdb" \
+ "source gdb test script"
+
+set saw_prompt 0
+set saw_prefix 0
+set saw_command 0
+set saw_prefix_foo 0
+set saw_prefix_bar 0
+
+# #1 - We need anchors so that the "prefix foo" pattern below does not
+# match when the expect output buffer contains:
+#
+# "\r\nprefix \r\n\prefix foo\r\n"
+#
+# #2 - We need an anchor on the prompt match as otherwise the prompt
+# regexp would match:
+#
+# "\r\nmeant-to-be-matched-by-lbl-2\r\nprefix \r\n(gdb) "
+#
+# This test would fail if -lbl did not force the built-in prompt match
+# regexp to have an anchor as well, as without it, the built-in prompt
+# regexp would have the exact same issue as #2 above.
+
+gdb_test_multiple "command" "" -lbl {
+ -re "^command(?=\r\n)" {
+ verbose -log <COMMAND>
+ incr saw_command
+ exp_continue
+ }
+ -re "^\r\nprefix foo(?=\r\n)" {
+ verbose -log <PREFIX-FOO>
+ incr saw_prefix_foo
+ exp_continue
+ }
+ -re "^\r\nprefix bar(?=\r\n)" {
+ verbose -log <PREFIX-BAR>
+ incr saw_prefix_bar
+ exp_continue
+ }
+ -re "^\r\nprefix (?=\r\n)" {
+ verbose -log <PREFIX>
+ incr saw_prefix
+ exp_continue
+ }
+ -re "^\r\n$gdb_prompt $" {
+ verbose -log <PROMPT>
+ incr saw_prompt
+ pass $gdb_test_name
+ }
+}
+
+verbose -log "saw_command: $saw_command"
+verbose -log "saw_prefix_foo: $saw_prefix_foo"
+verbose -log "saw_prefix_bar: $saw_prefix_bar"
+verbose -log "saw_prefix: $saw_prefix"
+verbose -log "saw_prompt: $saw_prompt"
+
+gdb_assert {$saw_command == 1}
+gdb_assert {$saw_prefix_foo == 1}
+gdb_assert {$saw_prefix_bar == 1}
+gdb_assert {$saw_prefix == 3}
+gdb_assert {$saw_prompt == 1}
diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
new file mode 100755
index 00000000000..710c5e057ba
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
@@ -0,0 +1,25 @@
+# Copyright 2025 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/>.
+
+define command
+ echo prefix \n
+ echo meant-to-be-matched-by-lbl-1\n
+ echo prefix foo\n
+ echo prefix bar\n
+ echo meant-to-be-matched-by-lbl-2\n
+ echo prefix \n
+ echo prefix \n
+ echo meant-to-be-matched-by-lbl-3\n
+end
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 0caa9cccd80..eefb6382a27 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1127,6 +1127,7 @@ proc gdb_test_multiple { command message args } {
global any_spawn_id
set line_by_line 0
+ set lbl_anchor_re ""
set prompt_regexp ""
set prompt_anchor 1
for {set i 0} {$i < [llength $args]} {incr i} {
@@ -1136,6 +1137,7 @@ proc gdb_test_multiple { command message args } {
set prompt_regexp [lindex $args $i]
} elseif { $arg == "-lbl" } {
set line_by_line 1
+ set lbl_anchor_re "^"
} elseif { $arg == "-no-prompt-anchor" } {
set prompt_anchor 0
} else {
@@ -1394,7 +1396,7 @@ proc gdb_test_multiple { command message args } {
fail "$errmsg"
set result -1
}
- -re "\r\n$prompt_regexp" {
+ -re "${lbl_anchor_re}\r\n$prompt_regexp" {
if {![string match "" $message]} {
fail "$message"
}
base-commit: f601ffb52199a883f16df385b73a14e756b3e19a
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global
2025-05-19 13:22 ` [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
@ 2025-05-28 19:09 ` Tom Tromey
2026-04-06 19:44 ` Pedro Alves
1 sibling, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:09 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> current_process.dr needs to be per-thread for non-stop. Actually, it
Pedro> doesn't even need to exist at all. We have x86_debug_reg_state
Pedro> recording intent, and then the
Pedro> cygwin_get_dr/cygwin_get_dr6/cygwin_get_dr7 functions are registered
Pedro> as x86_dr_low_type vector functions, so they should return the current
Pedro> value in the inferior's registers.
This looks reasonable to me.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait
2025-05-19 13:22 ` [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait Pedro Alves
@ 2025-05-28 19:16 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:16 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This patch changes things a bit so that the code is more obvious:
Pedro> - look at the status kind, instead of ptid_t.
Pedro> - add an explicit early return case for no-event.
Pedro> - add an explicit case for TARGET_WAITKIND_SPURIOUS.
Pedro> - with those, we no longer need to handle the case of find_thread not
Pedro> finding a thread, so we can drop one indentation level.
Thanks for cleaning this up. I think this is ok. It's hard to be
really sure in this area, I feel like I've had multiple rounds of
changes here in the past.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event
2025-05-19 13:22 ` [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event Pedro Alves
@ 2025-05-28 19:17 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:17 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> The current code suspends a thread just before calling
Pedro> GetThreadContext. You can only call GetThreadContext if the thread is
Pedro> suspended. But, after WaitForDebugEvent, all threads are implicitly
Pedro> suspended. So I don't think we even needed to call SuspendThread
Pedro> explictly at all before our GetThreadContext calls.
I long suspected this code was wrong but never tried to do anything
about it.
Pedro> However, suspending threads when we're about to present a stop to gdb
Pedro> simplifies adding non-stop support later. This way, the windows
Pedro> SuspendThread state corresponds to whether a thread is suspended or
Pedro> resumed from the core's perspective. Curiously, I noticed that Wine's
Pedro> winedbg does something similar:
Pedro> https://github.com/wine-mirror/wine/blob/234943344f7495d1e072338f0e06fa2d5cbf0aa1/programs/winedbg/gdbproxy.c#L651
Pedro> This makes it much easier to reason about a thread's suspend state,
Pedro> and simplifies adding non-stop mode later on.
Thanks.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread
2025-05-19 13:22 ` [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread Pedro Alves
@ 2025-05-28 19:18 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:18 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> We have code using do_synchronously to call continue_last_debug_event,
Pedro> and later patches in the series would need to add the same code in few
Pedro> more places. Factor it out to a continue_last_debug_event_main_thread
Pedro> function so these other places in future patches can just call it.
Pedro> In v2:
Pedro> - Fix context_str not used in the body of the method.
Ok.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid
2025-05-19 13:22 ` [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid Pedro Alves
@ 2025-05-28 19:21 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:21 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This will be used in subsequent patches to avoid using
Pedro> DBG_EXCEPTION_NOT_HANDLED on the wrong thread.
Seems fine.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread
2025-05-19 13:22 ` [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread Pedro Alves
@ 2025-05-28 19:22 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:22 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Passing a signal to a thread other than the one that last reported an
Pedro> event will be later possible with DBG_REPLY_LATER and the Windows
Pedro> backend working in non-stop mode.
Pedro> With an all-stop backend that isn't possible, so at least don't
Pedro> incorrectly consider passing DBG_EXCEPTION_NOT_HANDLED if the thread
Pedro> that we're going to call ContinueDebugEvent for is not the one that
Pedro> the user issued "signal SIG" on.
Ok. Thank you.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state
2025-05-19 13:22 ` [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state Pedro Alves
@ 2025-05-28 19:30 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:30 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> With non-stop mode, each thread is controlled independently of the
Pedro> others, and each thread has its own independent reason for its last
Pedro> stop.
Pedro> Thus, any thread-specific state that is currently per-process must be
Pedro> converted to per-thread state.
Makes sense.
Pedro> Since each thread will have its own copy of its last Windows debug
Pedro> event, we no longer need the same information stored in struct
Pedro> pending_stop.
I had to go refresh my memory of what happened here, but I see a comment
in gdb/nat/windows-nat.h:
/* A vector of pending stops. Sometimes, Windows will report a stop
on a thread that has been ostensibly suspended. We believe what
happens here is that two threads hit a breakpoint simultaneously,
and the Windows kernel queues the stop events. However, this can
result in the strange effect of trying to single step thread A --
leaving all other threads suspended -- and then seeing a stop in
thread B. To handle this scenario, we queue all such "pending"
stops here, and then process them once the step has completed. See
PR gdb/22992. */
So I think this makes sense as well.
Thanks for doing this.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig per-thread state
2025-05-19 13:22 ` [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig " Pedro Alves
@ 2025-05-28 19:31 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:31 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> With non-stop mode, each thread is controlled independently of the
Pedro> others, and each thread has its own independent reason for its last
Pedro> stop.
Pedro> Thus, any thread-specific state that is currently per-process must be
Pedro> converted to per-thread state.
Pedro> This patch converts windows_process_info::last_sig to per-thread
Pedro> state, moving it to windows_thread_info instead.
Pedro> This adjusts both native gdb and gdbserver.
Looks good.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er per-thread state
2025-05-19 13:22 ` [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er " Pedro Alves
@ 2025-05-28 19:33 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:33 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> With non-stop mode support, each thread has its own "last event", and
Pedro> so printing $_siginfo should print the siginfo of the selected thread.
Pedro> Likewise, with all-stop and scheduler-locking.
Pedro> This patch reworks the siginfo functions in gdb/windows-nat.c and
Pedro> gdbserver/win32-low.cc to reuse the exception record already saved
Pedro> within each thread's 'last_event' field.
This looks good. Thank you.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop
2025-05-19 13:22 ` [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop Pedro Alves
@ 2025-05-28 19:36 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:36 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> After the previous patches, struct pending_stop only contains one
Pedro> field. So move that field into the windows_thread_info structure
Pedro> directly, and eliminate struct pending_stop.
Nice, thanks.
Pedro> @@ -112,8 +102,11 @@ struct windows_thread_info
Pedro> effect of trying to single step thread A -- leaving all other
Pedro> threads suspended -- and then seeing a stop in thread B. To handle
Pedro> this scenario, we queue all such "pending" stops here, and then
Pedro> - process them once the step has completed. See PR gdb/22992. */
Pedro> - struct pending_stop pending_stop {};
Pedro> + process them once the step has completed. See PR gdb/22992.
Pedro> +
Pedro> + TARGET_WAITKIND_IGNORE if the thread does not have a pending
Pedro> + stop. */
Pedro> + target_waitstatus pending_status;
I think this comment needs a minor update. There's no longer a queue of
pending stops, just a single potential pending stop per thread. And, I
suppose now the thread's last-event object has the relevant information
about the pending stop, so maybe the comment could mention that as well.
(I mean, I suppose it should be obvious from the context but it doesn't
hurt to be explicit.)
With that update, though:
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 32/47] Windows gdb: Change serial_event management
2025-05-19 13:22 ` [PATCH v2 32/47] Windows gdb: Change serial_event management Pedro Alves
@ 2025-05-28 19:37 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:37 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This patch makes windows-nat.c manage the serial_event similarly to
Pedro> how linux-nat.c does it. Clear it on entry to
Pedro> windows_nat_target::wait, and set it if there may be more events to
Pedro> process. With this, there's no need to set it from
Pedro> windows_nat_target::wait_for_debug_event_main_thread, so the patch
Pedro> also makes us not do it.
Looks good, thanks.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code
2025-05-19 13:22 ` [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
@ 2025-05-28 19:44 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:44 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> The Windows target backend will want to do most of what the
Pedro> get_detach_signal function in gdb/linux-nat.c does, except for the
Pedro> Linux-specific bits. This commit moves the code that is shareable to
Pedro> infrun.c, so that other targets can use it too.
Seems reasonable.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data
2025-05-19 13:22 ` [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
@ 2025-05-28 19:52 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:52 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> When that is supported, and the core wants to be notified of thread
Pedro> exit events, the target backend does not delete the thread for which
Pedro> the event is being reported. Instead, infrun takes care of that.
I did not know about this detail ... is this documented somewhere?
Pedro> Fix this by eliminating the parallel thread list from the Windows
Pedro> backend, instead making the windows_thread_info data by registered as
Pedro> the private data associated with thread_info, like other targets do.
Nice, thank you.
I was a little worried about possible conflicts with this field, but
searching shows it's either used by the thread layer (aix and linux) or
by the -nat layer (darwin and remote). So it's reasonably robust I guess.
Pedro> +/* Get the windows_thread_info object associated with THR. */
Pedro> +
Pedro> +static windows_thread_info *
Pedro> +as_windows_thread_info (thread_info *thr)
Pedro> +{
Pedro> + /* Cast to windows_private_thread_info, which inherits from
Pedro> + private_thread_info, and is implicitly convertible to
Pedro> + windows_thread_info, the return type. */
Pedro> + return static_cast<windows_private_thread_info *> (thr->priv.get ());
Should this use checked_static_cast instead?
Pedro> +/* Creates an iterator that works like all_matching_threads_iterator,
Pedro> + but that returns windows_thread_info pointers instead of
Pedro> + thread_info. This could be replaced with a std::range::transform
Pedro> + when we require C++20. */
I was going to suggest a note in the meta bug but I see there's already
a note about using the range library.
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 38/47] Introduce windows_nat::event_code_to_string
2025-05-19 13:22 ` [PATCH v2 38/47] Introduce windows_nat::event_code_to_string Pedro Alves
@ 2025-05-28 19:53 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:53 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> ... with one DEBUG_EVENTS call per event type, log the event just once
Pedro> before the switch, and introduce a new event_code_to_string function
Pedro> to handle the event code to string conversion.
Pedro> Do the same on GDB's and gdbserver's Windows backends.
Ok.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context
2025-05-19 13:23 ` [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context Pedro Alves
@ 2025-05-28 19:54 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:54 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> After the previous patch, windows_per_inferior::invalidate_context
Pedro> isn't used anymore. Delete it.
Ok, even obvious after the previous patches go in.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 43/47] Windows gdb: extra thread info => show exiting
2025-05-19 13:23 ` [PATCH v2 43/47] Windows gdb: extra thread info => show exiting Pedro Alves
@ 2025-05-28 19:58 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-28 19:58 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Now that we have easy access to each thread's last event, we can
Pedro> easily include some extra info in "info threads" output related to
Pedro> each thread's last event.
Pedro> This patch makes us show whether the thread is exiting, or causing a
Pedro> whole-process exit. This is useful when multiple threads hit events
Pedro> at the same time, and the thread/process exit events are still pending
Pedro> until the user re-resumes the program.
Pedro> This is similar to how linux-thread-db.c also shows "Exiting" in its
Pedro> target_extra_thread_info implementation.
Pedro> This will be relied on by the testcase added by the following patch.
I guess the same could be done for gdbserver?
I see remote_target::extra_thread_info.
Anyway, no need to implement it for this patch.
Maybe filing a bug report would be good though.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on")
2025-05-19 13:23 ` [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
@ 2025-05-29 16:02 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-29 16:02 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Since having the target backend work in non-stop mode adds features
Pedro> compared to old all-stop mode (signal/exception passing/suppression is
Pedro> truly per-thread), this switches the backend to do
Pedro> all-stop-on-top-of-non-stop, by having
Pedro> windows_nat_target::always_non_stop_p return true if non-stop mode is
Pedro> possible.
Pedro> To be clear, this just changes how the backend works in coordination
Pedro> with infrun. The user-visible mode default mode is still all-stop.
Pedro> The difference is that infrun is responsible for stopping all threads
Pedro> when needed, instead of the backend (actually the kernel) always doing
Pedro> that before reporting an event to infrun.
Thanks, this looks good.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp
2025-05-19 13:23 ` [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
@ 2025-05-29 16:09 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-29 16:09 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This adds a new test for letting the main thread exit the process with
Pedro> scheduler-locking on, while there are other threads live.
Thanks for the detailed explanation.
I think this is ok.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking
2025-05-19 13:22 ` [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking Pedro Alves
@ 2025-05-30 20:37 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-30 20:37 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This rewrites win32_process_target::resume such that scheduler-locking
Pedro> is implemented properly.
Pedro> It also uses the new get_last_debug_event_ptid function to avoid
Pedro> considering passing a signal to the wrong thread, like done for the
Pedro> native side in a previous patch.
Thanks. It took me a while to understand this patch somehow.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops
2025-05-19 13:22 ` [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops Pedro Alves
@ 2025-05-30 20:41 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-30 20:41 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> windows_process.desired_stop_thread_id doesn't work for non-stop, as
Pedro> in that case every thread will have its own independent
Pedro> WaitForDebugEvent event.
Pedro> Instead, detect whether we have been reported a stop that was not
Pedro> supposed to be reported by simply checking whether the thread that is
Pedro> reporting the event is suspended. This is now easilly possible since
Pedro> each thread's suspend state is kept in sync with whether infrun wants
Pedro> the thread executing or not.
Thanks, this makes sense to me.
Pedro> I considered whether it would be possible to keep the pending_stop
Pedro> handling code shared in gdb/nat/windows-nat.c, in this patch and
Pedro> throughout the series, but I conclused that it isn't worth it, until
Pedro> gdbserver is taught about async and non-stop as well.
Makes sense as well. For future tasks like this, perhaps a bug report
would be useful.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending
2025-05-19 13:22 ` [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending Pedro Alves
@ 2025-05-30 20:43 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-30 20:43 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Several watchpoint-related testcases, such as
Pedro> gdb.threads/watchthreads.exp for example, when tested with the backend
Pedro> in non-stop mode, exposed an interesting detail of the Windows debug
Pedro> API that wasn't considered before.
Thanks for the patch and explanation. I am not sure I would have
discovered that this was the problem.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops)
2025-05-19 13:23 ` [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
@ 2025-05-30 20:50 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-05-30 20:50 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> Teach the Windows target to temporarily pause all threads when we
Pedro> change the debug registers for a watchpoint. Implements the same
Pedro> logic as Linux uses:
[...]
I doubt I'll be able to understand this particular patch without
debugging it myself or something, but I read through it and didn't see
anything obviously out of wack.
Pedro> and DR7. Defering the actualy writes to ::wait avoids a bunch of
s/actualy/actual/
Pedro> +static void
Pedro> +debug_registers_changed_all_threads ()
Pedro> +{
Pedro> + auto *win_tgt = static_cast<windows_nat_target *> (get_native_target ());
Probably checked_static_cast?
Reviewed-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 39/47] Windows gdb: Add non-stop support
2025-05-19 13:23 ` [PATCH v2 39/47] Windows gdb: Add non-stop support Pedro Alves
@ 2025-06-05 16:21 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-06-05 16:21 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> This patch adds non-stop support to the native Windows target.
Pedro> This is made possible by the ContinueDebugEvent DBG_REPLY_LATER flag:
[...]
Thanks for the explanation and especially for the new comment in
windows-nat.c.
I tried a few times to review this but I think it's too complicated for
me to really understand fully. Nevertheless I think you should go
forward with it. I do think the general approach taken here makes
sense.
I mentioned this in other patches but it'd be good to have bugs filed
for any to-do items, like the gdbserver work or the displaced stepping
work.
Acked-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2] of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-05-28 11:59 ` [PATCH v2] of " Pedro Alves
@ 2025-06-05 16:37 ` Pedro Alves
2025-06-05 17:20 ` [PATCH v3] " Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-06-05 16:37 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
Hi!
On 2025-05-28 12:59, Pedro Alves wrote:
> +gdb_test_multiple "command" "" -lbl {
> + -re "^command(?=\r\n)" {
> + verbose -log <COMMAND>
> + incr saw_command
> + exp_continue
> + }
> + -re "^\r\nprefix foo(?=\r\n)" {
> + verbose -log <PREFIX-FOO>
> + incr saw_prefix_foo
> + exp_continue
> + }
> + -re "^\r\nprefix bar(?=\r\n)" {
> + verbose -log <PREFIX-BAR>
> + incr saw_prefix_bar
> + exp_continue
> + }
> + -re "^\r\nprefix (?=\r\n)" {
> + verbose -log <PREFIX>
> + incr saw_prefix
> + exp_continue
> + }
Reading back, I realized that this example looks a bit silly as is, because you could fix it by
just moving the "prefix " case earlier than the "prefix foo/bar" cases. I should make the command output
something after "prefix", like "prefix xxx", and then the regexp should match something like:
-re "^\r\nprefix [^\r\n]*(?=\r\n)" {
That's more like what the original testcase that exposed the issue does.
I'll send an updated patch soon.
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH v3] of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-06-05 16:37 ` Pedro Alves
@ 2025-06-05 17:20 ` Pedro Alves
2025-06-06 9:58 ` Tom de Vries
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-06-05 17:20 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
On 2025-06-05 17:37, Pedro Alves wrote:
> Reading back, I realized that this example looks a bit silly as is, because you could fix it by
> just moving the "prefix " case earlier than the "prefix foo/bar" cases. I should make the command output
> something after "prefix", like "prefix xxx", and then the regexp should match something like:
>
> -re "^\r\nprefix [^\r\n]*(?=\r\n)" {
>
> That's more like what the original testcase that exposed the issue does.
>
> I'll send an updated patch soon.
Here it is.
--- 8< ---
From 93ce02b9ad0aefefc5f71b6c5d23f565f3f2e87a Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Thu, 5 Jun 2025 18:09:44 +0100
Subject: [PATCH] gdb_test_multiple: Anchor prompt match if -lbl
The testcase added by this patch has a gdb_test_multiple call that
wants to match different lines of output that all have a common
prefix, and do different actions on each. Instead of a single regular
expression with alternatives, its clearer code if the different
expressions are handled with different "-re", like so:
gdb_test_multiple "command" "" -lbl {
-re "^command(?=\r\n)" {
exp_continue
}
-re "^\r\nprefix foo(?=\r\n)" {
# Some action for "foo".
exp_continue
}
-re "^\r\nprefix bar(?=\r\n)" {
# Some action for "bar".
exp_continue
}
-re "^\r\nprefix \[^\r\n\]*(?=\r\n)" {
# Some action for all others.
exp_continue
}
-re "^\r\n$::gdb_prompt $" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
}
Above, the leading anchors in the "^\r\nprefix..." matches are needed
to avoid too-eager matching due to the common prefix. Without the
anchors, if the expect output buffer happens to contain at least:
"\r\nprefix xxx\r\nprefix foo\r\n"
... then the "prefix foo" pattern match inadvertently consumes the
first "prefix xxx" line.
Without the anchor in the prompt match, like:
-re "\r\n$::gdb_prompt $" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
Or the equivalent:
-re -wrap "" {
gdb_assert {$all_prefixes_were_seen} $gdb_test_name
}
... then if the expect buffer contains:
"\r\nmeant-to-be-matched-by-lbl\r\nprefix foo\r\n$gdb_prompt "
... then the prompt regexp matches this, consuming the "prefix" line
inadvertently, and we get a FAIL. The built-in regexp matcher for
-lbl doesn't get a chance to match the
"\r\nmeant-to-be-matched-by-lbl\r\n" part, because the built-in prompt
match appears first within gdb_test_multiple.
By adding the anchor to the prompt regexp, then we avoid that problem.
However, the same expect output buffer contents will still match the
built-in prompt match. That is what is fixed by this patch. It makes
it so that if -lbl is specified, the built-in prompt regexp has a
leading anchor.
Original idea for turning this into a gdb.testsuite/ testcase by Tom
de Vries <tdevries@suse.de>.
Change-Id: Ic2571ec793d856a89ee0d533ec363e2ac6036ea2
---
.../gdb.testsuite/gdb_test_multiple-lbl.exp | 84 +++++++++++++++++++
.../gdb.testsuite/gdb_test_multiple-lbl.gdb | 25 ++++++
gdb/testsuite/lib/gdb.exp | 4 +-
3 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
create mode 100755 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
new file mode 100644
index 00000000000..a05ce614b1d
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
@@ -0,0 +1,84 @@
+# Copyright 2025 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 gdb_test_multiple -lbl, particularly with patterns that share a
+# common prefix.
+
+standard_testfile
+
+clean_restart
+
+gdb_test_no_output "source ${srcdir}/${subdir}/$testfile.gdb" \
+ "source gdb test script"
+
+set saw_prompt 0
+set saw_prefix 0
+set saw_command 0
+set saw_prefix_foo 0
+set saw_prefix_bar 0
+
+# #1 - We need anchors so that the "prefix foo" pattern below does not
+# match when the expect output buffer contains:
+#
+# "\r\nprefix xxx\r\n\prefix foo\r\n"
+#
+# #2 - We need an anchor on the prompt match as otherwise the prompt
+# regexp would match:
+#
+# "\r\nmeant-to-be-matched-by-lbl-2\r\nprefix xxx\r\n(gdb) "
+#
+# This test would fail if -lbl did not force the built-in prompt match
+# regexp to have an anchor as well, as without it, the built-in prompt
+# regexp would have the exact same issue as #2 above.
+
+gdb_test_multiple "command" "" -lbl {
+ -re "^command(?=\r\n)" {
+ verbose -log <COMMAND>
+ incr saw_command
+ exp_continue
+ }
+ -re "^\r\nprefix foo(?=\r\n)" {
+ verbose -log <PREFIX-FOO>
+ incr saw_prefix_foo
+ exp_continue
+ }
+ -re "^\r\nprefix bar(?=\r\n)" {
+ verbose -log <PREFIX-BAR>
+ incr saw_prefix_bar
+ exp_continue
+ }
+ -re "^\r\nprefix \[^\r\n\]*(?=\r\n)" {
+ verbose -log <PREFIX>
+ incr saw_prefix
+ exp_continue
+ }
+ -re "^\r\n$gdb_prompt $" {
+ verbose -log <PROMPT>
+ incr saw_prompt
+ pass $gdb_test_name
+ }
+}
+
+verbose -log "saw_command: $saw_command"
+verbose -log "saw_prefix_foo: $saw_prefix_foo"
+verbose -log "saw_prefix_bar: $saw_prefix_bar"
+verbose -log "saw_prefix: $saw_prefix"
+verbose -log "saw_prompt: $saw_prompt"
+
+gdb_assert {$saw_command == 1}
+gdb_assert {$saw_prefix_foo == 1}
+gdb_assert {$saw_prefix_bar == 1}
+gdb_assert {$saw_prefix == 3}
+gdb_assert {$saw_prompt == 1}
diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
new file mode 100755
index 00000000000..8c94dfa9211
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
@@ -0,0 +1,25 @@
+# Copyright 2025 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/>.
+
+define command
+ echo prefix xxx\n
+ echo meant-to-be-matched-by-lbl-1\n
+ echo prefix foo\n
+ echo prefix bar\n
+ echo meant-to-be-matched-by-lbl-2\n
+ echo prefix xxx\n
+ echo prefix xxx\n
+ echo meant-to-be-matched-by-lbl-3\n
+end
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 0caa9cccd80..eefb6382a27 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -1127,6 +1127,7 @@ proc gdb_test_multiple { command message args } {
global any_spawn_id
set line_by_line 0
+ set lbl_anchor_re ""
set prompt_regexp ""
set prompt_anchor 1
for {set i 0} {$i < [llength $args]} {incr i} {
@@ -1136,6 +1137,7 @@ proc gdb_test_multiple { command message args } {
set prompt_regexp [lindex $args $i]
} elseif { $arg == "-lbl" } {
set line_by_line 1
+ set lbl_anchor_re "^"
} elseif { $arg == "-no-prompt-anchor" } {
set prompt_anchor 0
} else {
@@ -1394,7 +1396,7 @@ proc gdb_test_multiple { command message args } {
fail "$errmsg"
set result -1
}
- -re "\r\n$prompt_regexp" {
+ -re "${lbl_anchor_re}\r\n$prompt_regexp" {
if {![string match "" $message]} {
fail "$message"
}
base-commit: f601ffb52199a883f16df385b73a14e756b3e19a
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 00/47] Windows non-stop mode
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
` (46 preceding siblings ...)
2025-05-19 13:23 ` [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS Pedro Alves
@ 2025-06-05 17:57 ` Tom Tromey
2025-06-11 22:06 ` [PATCH] Improve attach on Windows (was: Re: [PATCH v2 00/47] Windows non-stop mode) Pedro Alves
2025-06-11 23:51 ` [PATCH v2 00/47] Windows non-stop mode Pedro Alves
47 siblings, 2 replies; 97+ messages in thread
From: Tom Tromey @ 2025-06-05 17:57 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
Pedro> I've pushed the series to the users/palves/windows-non-stop-v2 branch
Pedro> on sourceware.org, for your convenience.
I merged this into the local tree and ran the internal AdaCore test
suite on Windows 2016.
There were three "failures".
One of them is actually an improvement, where the test works around a
current issue.
One of them seems to be a problem with the test. This test "attach"es
to a running process without a "file", and the test doesn't seem to
match the new output:
(gdb) attach 1228
Attaching to process 1228
[Switching to Thread 1228.0x580]
0x00007ffe3c756714 in ntdll!ZwDelayExecution ()
from C:\Windows\SYSTEM32\ntdll.dll
I'm not quite as sure about the last one. This test sets a couple of
breakpoints that have 'commands', where the commands delete the
breakpoint, like:
[ break main.adb:4 then ... ]
"silent",
'printf "Breakpoint hit #1.\\n"',
"clear main.adb:4",
"continue",
"end",
When running the expected output is seen but gdb says:
(gdb) run
Starting program: C:\[...]
Breakpoint hit #1.
Breakpoint hit #2.
No unwaited-for children left.
It's that last line that causes the problem. The test expects an "exit
notification", which is kind of complicated in this test suite, but I
think for native testing boils down to a message like "[Inferior
... exited ...]".
I see this "unwaited-for" text in infrun.c. But I wonder if this is
really intended.
thanks,
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v3] of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-06-05 17:20 ` [PATCH v3] " Pedro Alves
@ 2025-06-06 9:58 ` Tom de Vries
2025-06-06 13:53 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Tom de Vries @ 2025-06-06 9:58 UTC (permalink / raw)
To: Pedro Alves, gdb-patches
On 6/5/25 19:20, Pedro Alves wrote:
> On 2025-06-05 17:37, Pedro Alves wrote:
>> Reading back, I realized that this example looks a bit silly as is, because you could fix it by
>> just moving the "prefix " case earlier than the "prefix foo/bar" cases. I should make the command output
>> something after "prefix", like "prefix xxx", and then the regexp should match something like:
>>
>> -re "^\r\nprefix [^\r\n]*(?=\r\n)" {
>>
>> That's more like what the original testcase that exposed the issue does.
>>
>> I'll send an updated patch soon.
>
> Here it is.
>
Hi Pedro,
[ FWIW, the way this patch is posted doesn't work well with patchwork:
...
$ git-pw patch apply 113744
Failed to apply patch:
Applying: of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
error: No valid patches in input (allow with "--allow-empty")
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 of [PATCH v2 42/47] gdb_test_multiple: Anchor
prompt match if -lbl
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
$ git am --show-current-patch=diff
--- 8< ---
$
...
I managed to apply it by saving the email in thunderbird and using git
am, though that drops the commit message. ]
> --- 8< ---
> From 93ce02b9ad0aefefc5f71b6c5d23f565f3f2e87a Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Thu, 5 Jun 2025 18:09:44 +0100
> Subject: [PATCH] gdb_test_multiple: Anchor prompt match if -lbl
>
> The testcase added by this patch has a gdb_test_multiple call that
> wants to match different lines of output that all have a common
> prefix, and do different actions on each. Instead of a single regular
> expression with alternatives, its clearer code if the different
its -> it's
> expressions are handled with different "-re", like so:
>
> gdb_test_multiple "command" "" -lbl {
> -re "^command(?=\r\n)" {
> exp_continue
> }
> -re "^\r\nprefix foo(?=\r\n)" {
> # Some action for "foo".
> exp_continue
> }
> -re "^\r\nprefix bar(?=\r\n)" {
> # Some action for "bar".
> exp_continue
> }
> -re "^\r\nprefix \[^\r\n\]*(?=\r\n)" {
> # Some action for all others.
> exp_continue
> }
> -re "^\r\n$::gdb_prompt $" {
> gdb_assert {$all_prefixes_were_seen} $gdb_test_name
> }
> }
>
> Above, the leading anchors in the "^\r\nprefix..." matches are needed
> to avoid too-eager matching due to the common prefix. Without the
> anchors, if the expect output buffer happens to contain at least:
>
> "\r\nprefix xxx\r\nprefix foo\r\n"
>
> ... then the "prefix foo" pattern match inadvertently consumes the
> first "prefix xxx" line.
>
> Without the anchor in the prompt match, like:
>
> -re "\r\n$::gdb_prompt $" {
> gdb_assert {$all_prefixes_were_seen} $gdb_test_name
> }
>
> Or the equivalent:
>
> -re -wrap "" {
> gdb_assert {$all_prefixes_were_seen} $gdb_test_name
> }
>
> ... then if the expect buffer contains:
>
> "\r\nmeant-to-be-matched-by-lbl\r\nprefix foo\r\n$gdb_prompt "
>
> ... then the prompt regexp matches this, consuming the "prefix" line
> inadvertently, and we get a FAIL. The built-in regexp matcher for
> -lbl doesn't get a chance to match the
> "\r\nmeant-to-be-matched-by-lbl\r\n" part, because the built-in prompt
> match appears first within gdb_test_multiple.
>
> By adding the anchor to the prompt regexp, then we avoid that problem.
>
Should "then" be dropped in this line?
> However, the same expect output buffer contents will still match the
> built-in prompt match. That is what is fixed by this patch. It makes
> it so that if -lbl is specified, the built-in prompt regexp has a
> leading anchor.
>
Thanks for doing this.
LGTM.
Approved-By: Tom de Vries <tdevries@suse.de>
FWIW, I propose to apply this independently from the patch series, since
AFAICT it's unrelated.
Thanks,
- Tom
> Original idea for turning this into a gdb.testsuite/ testcase by Tom
> de Vries <tdevries@suse.de>.
>
> Change-Id: Ic2571ec793d856a89ee0d533ec363e2ac6036ea2
> ---
> .../gdb.testsuite/gdb_test_multiple-lbl.exp | 84 +++++++++++++++++++
> .../gdb.testsuite/gdb_test_multiple-lbl.gdb | 25 ++++++
> gdb/testsuite/lib/gdb.exp | 4 +-
> 3 files changed, 112 insertions(+), 1 deletion(-)
> create mode 100644 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
> create mode 100755 gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
>
> diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
> new file mode 100644
> index 00000000000..a05ce614b1d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.exp
> @@ -0,0 +1,84 @@
> +# Copyright 2025 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 gdb_test_multiple -lbl, particularly with patterns that share a
> +# common prefix.
> +
> +standard_testfile
> +
> +clean_restart
> +
> +gdb_test_no_output "source ${srcdir}/${subdir}/$testfile.gdb" \
> + "source gdb test script"
> +
> +set saw_prompt 0
> +set saw_prefix 0
> +set saw_command 0
> +set saw_prefix_foo 0
> +set saw_prefix_bar 0
> +
> +# #1 - We need anchors so that the "prefix foo" pattern below does not
> +# match when the expect output buffer contains:
> +#
> +# "\r\nprefix xxx\r\n\prefix foo\r\n"
> +#
> +# #2 - We need an anchor on the prompt match as otherwise the prompt
> +# regexp would match:
> +#
> +# "\r\nmeant-to-be-matched-by-lbl-2\r\nprefix xxx\r\n(gdb) "
> +#
> +# This test would fail if -lbl did not force the built-in prompt match
> +# regexp to have an anchor as well, as without it, the built-in prompt
> +# regexp would have the exact same issue as #2 above.
> +
> +gdb_test_multiple "command" "" -lbl {
> + -re "^command(?=\r\n)" {
> + verbose -log <COMMAND>
> + incr saw_command
> + exp_continue
> + }
> + -re "^\r\nprefix foo(?=\r\n)" {
> + verbose -log <PREFIX-FOO>
> + incr saw_prefix_foo
> + exp_continue
> + }
> + -re "^\r\nprefix bar(?=\r\n)" {
> + verbose -log <PREFIX-BAR>
> + incr saw_prefix_bar
> + exp_continue
> + }
> + -re "^\r\nprefix \[^\r\n\]*(?=\r\n)" {
> + verbose -log <PREFIX>
> + incr saw_prefix
> + exp_continue
> + }
> + -re "^\r\n$gdb_prompt $" {
> + verbose -log <PROMPT>
> + incr saw_prompt
> + pass $gdb_test_name
> + }
> +}
> +
> +verbose -log "saw_command: $saw_command"
> +verbose -log "saw_prefix_foo: $saw_prefix_foo"
> +verbose -log "saw_prefix_bar: $saw_prefix_bar"
> +verbose -log "saw_prefix: $saw_prefix"
> +verbose -log "saw_prompt: $saw_prompt"
> +
> +gdb_assert {$saw_command == 1}
> +gdb_assert {$saw_prefix_foo == 1}
> +gdb_assert {$saw_prefix_bar == 1}
> +gdb_assert {$saw_prefix == 3}
> +gdb_assert {$saw_prompt == 1}
> diff --git a/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
> new file mode 100755
> index 00000000000..8c94dfa9211
> --- /dev/null
> +++ b/gdb/testsuite/gdb.testsuite/gdb_test_multiple-lbl.gdb
> @@ -0,0 +1,25 @@
> +# Copyright 2025 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/>.
> +
> +define command
> + echo prefix xxx\n
> + echo meant-to-be-matched-by-lbl-1\n
> + echo prefix foo\n
> + echo prefix bar\n
> + echo meant-to-be-matched-by-lbl-2\n
> + echo prefix xxx\n
> + echo prefix xxx\n
> + echo meant-to-be-matched-by-lbl-3\n
> +end
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 0caa9cccd80..eefb6382a27 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -1127,6 +1127,7 @@ proc gdb_test_multiple { command message args } {
> global any_spawn_id
>
> set line_by_line 0
> + set lbl_anchor_re ""
> set prompt_regexp ""
> set prompt_anchor 1
> for {set i 0} {$i < [llength $args]} {incr i} {
> @@ -1136,6 +1137,7 @@ proc gdb_test_multiple { command message args } {
> set prompt_regexp [lindex $args $i]
> } elseif { $arg == "-lbl" } {
> set line_by_line 1
> + set lbl_anchor_re "^"
> } elseif { $arg == "-no-prompt-anchor" } {
> set prompt_anchor 0
> } else {
> @@ -1394,7 +1396,7 @@ proc gdb_test_multiple { command message args } {
> fail "$errmsg"
> set result -1
> }
> - -re "\r\n$prompt_regexp" {
> + -re "${lbl_anchor_re}\r\n$prompt_regexp" {
> if {![string match "" $message]} {
> fail "$message"
> }
>
> base-commit: f601ffb52199a883f16df385b73a14e756b3e19a
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v3] of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
2025-06-06 9:58 ` Tom de Vries
@ 2025-06-06 13:53 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-06-06 13:53 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
On 2025-06-06 10:58, Tom de Vries wrote:
> Hi Pedro,
>
> [ FWIW, the way this patch is posted doesn't work well with patchwork:
> ...
> $ git-pw patch apply 113744
> Failed to apply patch:
> Applying: of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
> error: No valid patches in input (allow with "--allow-empty")
> error: could not build fake ancestor
> hint: Use 'git am --show-current-patch=diff' to see the failed patch
> Patch failed at 0001 of [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl
> When you have resolved this problem, run "git am --continue".
> If you prefer to skip this patch, run "git am --skip" instead.
> To restore the original branch and stop patching, run "git am --abort".
>
Odd.
> $ git am --show-current-patch=diff
> --- 8< ---
> $
> ...
>
> I managed to apply it by saving the email in thunderbird and using git am, though that drops the commit message. ]
Copy/pasting to a new .patch file after the "--- 8< ---" line would have preserved it.
>
>> --- 8< ---
>> From 93ce02b9ad0aefefc5f71b6c5d23f565f3f2e87a Mon Sep 17 00:00:00 2001
>> From: Pedro Alves <pedro@palves.net>
>> Date: Thu, 5 Jun 2025 18:09:44 +0100
>> Subject: [PATCH] gdb_test_multiple: Anchor prompt match if -lbl
>>
>> The testcase added by this patch has a gdb_test_multiple call that
>> wants to match different lines of output that all have a common
>> prefix, and do different actions on each. Instead of a single regular
>> expression with alternatives, its clearer code if the different
>
> its -> it's
Fixed.
>> By adding the anchor to the prompt regexp, then we avoid that problem.
>>
>
> Should "then" be dropped in this line?
Indeed, done.
>
>> However, the same expect output buffer contents will still match the
>> built-in prompt match. That is what is fixed by this patch. It makes
>> it so that if -lbl is specified, the built-in prompt regexp has a
>> leading anchor.
>>
>
> Thanks for doing this.
>
> LGTM.
>
> Approved-By: Tom de Vries <tdevries@suse.de>
>
> FWIW, I propose to apply this independently from the patch series, since AFAICT it's unrelated.
>
Thanks. I've merged it now. It used to be needed by the testcase added later in the series,
but since it has its own independent testcase, it stands alone even better.
I've merged it now with the trivial fixes.
Thanks,
Pedro Alves
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes
2025-05-19 13:56 ` Andrew Burgess
@ 2025-06-06 13:56 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2025-06-06 13:56 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 2025-05-19 14:56, Andrew Burgess wrote:
> Makes sense to me.
>
> Approved-By: Andrew Burgess <aburgess@redhat.com>
Thanks, I've merged this one.
Pedro Alves
^ permalink raw reply [flat|nested] 97+ messages in thread
* [PATCH] Improve attach on Windows (was: Re: [PATCH v2 00/47] Windows non-stop mode)
2025-06-05 17:57 ` [PATCH v2 00/47] Windows non-stop mode Tom Tromey
@ 2025-06-11 22:06 ` Pedro Alves
2026-04-02 12:21 ` [PATCH] Improve attach on Windows Pedro Alves
2025-06-11 23:51 ` [PATCH v2 00/47] Windows non-stop mode Pedro Alves
1 sibling, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-06-11 22:06 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
Hi!
On 2025-06-05 18:57, Tom Tromey wrote:
>>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
>
> Pedro> I've pushed the series to the users/palves/windows-non-stop-v2 branch
> Pedro> on sourceware.org, for your convenience.
>
> I merged this into the local tree and ran the internal AdaCore test
> suite on Windows 2016.
Thank you very much for doing this. Much appreciated.
>
> There were three "failures".
>
> One of them is actually an improvement, where the test works around a
> current issue.
>
One step forward, two steps back, I guess.
Tackling this incrementally. I've looked at the first one:
> One of them seems to be a problem with the test. This test "attach"es
> to a running process without a "file", and the test doesn't seem to
> match the new output:
>
> (gdb) attach 1228
> Attaching to process 1228
> [Switching to Thread 1228.0x580]
> 0x00007ffe3c756714 in ntdll!ZwDelayExecution ()
> from C:\Windows\SYSTEM32\ntdll.dll
>
I guess your test wasn't expecting that the current frame is printed?
See the patch below, on top of current master. You are now seeing the current
frame being printed, unlike what I claim in the commit log of the patch below,
because with target-non-stop on, the Windows backend is a target_wait_no_wait==false
target, like Linux.
But even with the non-stop series, with "maint set target-non-stop off",
(or with Windows older than Windows 10,) then target_wait_no_wait==true, and
GDB doesn't print the frame.
IMO, this is just a preexisting bug that the non-stop series happens to hide.
Let me know what you think of the patch.
And yes, the fact that run control communicates with normal_stop via
globals is ugly and fragile to say the least.
From 0cb83571c91ba42b3de519b034509092a526dc65 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 11 Jun 2025 22:05:23 +0100
Subject: [PATCH] Improve attach on Windows
Unlike most targets, on Windows, when you attach, GDB doesn't print
the current stack frame. Vis:
On GNU/Linux:
attach 3340347
Attaching to program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.base/attach/attach, process 3340347
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
Reading symbols from /usr/lib/debug/.build-id/d5/197096f709801829b118af1b7cf6631efa2dcd.debug...
Reading symbols from /lib64/ld-linux-x86-64.so.2...
Reading symbols from /usr/lib/debug/.build-id/9c/b53985768bb99f138f48655f7b8bf7e420d13d.debug...
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
0x00005b3bf29be174 in main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/attach.c:19
19 while (! should_exit)
(gdb) PASS: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
On Cygwin:
(gdb) attach 6692
Attaching to program: /home/alves/gdb/build-cygwin-testsuite/outputs/gdb.base/attach/attach, process 6692
[New Thread 6692.0x2e60]
[New Thread 6692.0x2e9c]
[New Thread 6692.0xd6c]
[New Thread 6692.0x137c]
[New Thread 6692.0x1270]
(gdb) FAIL: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
On Linux, GDB prints the frame because after the target_attach, GDB
goes back to the event loop, to wait for an initial stop event. The
stop event arrives, and we process it, which sets the stop_print_frame
global, and then we get to normal_stop, which prints the frame iff
stop_print_frame is set, which it is.
Windows OTOH, is a target_attach_no_wait target, so after
target_attach, there is no going back to event loop. In
infcmd.c:attach_command, we go straight to attach_post_wait which
takes us to normal_stop. But this time, nothing set
stop_print_frame to true, so no frame is printed. Actually, if the
global happened to be true due to an earlier event from debugging a
previous inferior, then we will print the frame.
This patch makes GDB's behavior consistent, by making sure the globals
normal_stop looks at are in a good state in the target_attach_no_wait
path.
With that alone, GDB now prints the frame:
(gdb) attach 2915
Attaching to program: /usr/bin/sleep.exe, process 2832
[New Thread 2832.0x2a68]
[New Thread 2832.0xb1c]
[New Thread 2832.0x8ac]
[Switching to Thread 2832.0x8ac]
0x00007ffec51d4a71 in ntdll!DbgBreakPoint () from C:/Windows/SYSTEM32/ntdll.dll
This is still not ideal, IMHO, as the current thread is the thread
that Windows injects to attach:
(gdb) info threads
Id Target Id Frame
1 Thread 2832.0x2100 "sleep" 0x00007ffec51d18d7 in ntdll!ZwWaitForMultipleObjects () from C:/Windows/SYSTEM32/ntdll.dll
2 Thread 2832.0x2a68 "sig" 0x00007ffec51d0e47 in ntdll!ZwReadFile () from C:/Windows/SYSTEM32/ntdll.dll
3 Thread 2832.0xb1c 0x00007ffec51d49d7 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/Windows/SYSTEM32/ntdll.dll
* 4 Thread 2832.0x8ac 0x00007ffec51d4a71 in ntdll!DbgBreakPoint () from C:/Windows/SYSTEM32/ntdll.dll
Automatically switching to main thread is IMHO more useful. That
results in very similar output than what we see on Linux:
attach 5164
Attaching to program: /home/alves/gdb/build-cygwin-testsuite/outputs/gdb.base/attach/attach, process 5164
[New Thread 5164.0x87c]
[New Thread 5164.0x28f0]
[New Thread 5164.0x376c]
[New Thread 5164.0x2db4]
[New Thread 5164.0xce4]
main () at /home/alves/gdb/src/gdb/testsuite/gdb.base/attach.c:19
19 while (! should_exit)
(gdb)
If we do this, then we can simplify gdb.base/attach.exp a bit by
removing a couple Cygwin special cases.
The patch does all that, which results in the following
gdb.base/attach.exp progressions:
-FAIL: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
-FAIL: gdb.base/attach.exp: do_attach_tests: attach2, with no file
-FAIL: gdb.base/attach.exp: do_attach_tests: load file manually, after attach2 (re-read) (got interactive prompt)
-FAIL: gdb.base/attach.exp: do_attach_tests: attach when process' a.out not in cwd
-FAIL: gdb.base/attach.exp: do_attach_failure_tests: first attach
+PASS: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
+PASS: gdb.base/attach.exp: do_attach_tests: attach2, with no file
+PASS: gdb.base/attach.exp: do_attach_tests: attach when process' a.out not in cwd
+PASS: gdb.base/attach.exp: do_attach_failure_tests: first attach
Change-Id: I359bdb25660c9a4d5d873e8771cfd1cd2a54c97b
---
gdb/infcmd.c | 5 ++++-
gdb/infrun.c | 13 +++++++++++++
gdb/infrun.h | 3 +++
gdb/testsuite/gdb.base/attach.exp | 26 ++++++--------------------
gdb/windows-nat.c | 7 +++++++
5 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index e9b58ce5521..cbf2373193c 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -2722,7 +2722,10 @@ attach_command (const char *args, int from_tty)
return;
}
else
- attach_post_wait (from_tty, mode);
+ {
+ set_normal_stop_state_just_attached ();
+ attach_post_wait (from_tty, mode);
+ }
disable_commit_resumed.reset_and_commit ();
}
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 2e02642c52a..41ab3342ee3 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -407,6 +407,19 @@ static process_stratum_target *target_last_proc_target;
static ptid_t target_last_wait_ptid;
static struct target_waitstatus target_last_waitstatus;
+/* See infrun.h. */
+
+void
+set_normal_stop_state_just_attached ()
+{
+ stop_print_frame = true;
+ stopped_by_random_signal = 0;
+
+ target_waitstatus status;
+ status.set_ignore ();
+ set_last_target_status (nullptr, minus_one_ptid, status);
+}
+
void init_thread_stepping_state (struct thread_info *tss);
static const char follow_fork_mode_child[] = "child";
diff --git a/gdb/infrun.h b/gdb/infrun.h
index b9b64aca45a..b707314fba6 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -418,5 +418,8 @@ struct scoped_enable_commit_resumed
bool m_prev_enable_commit_resumed;
};
+/* Set up state for normal_stop after we just attached, on
+ target_attach_no_wait targets. */
+extern void set_normal_stop_state_just_attached ();
#endif /* GDB_INFRUN_H */
diff --git a/gdb/testsuite/gdb.base/attach.exp b/gdb/testsuite/gdb.base/attach.exp
index 0d1550a0541..484fa776ae4 100644
--- a/gdb/testsuite/gdb.base/attach.exp
+++ b/gdb/testsuite/gdb.base/attach.exp
@@ -159,16 +159,9 @@ proc_with_prefix do_attach_failure_tests {} {
# Verify that we can't double attach to the process.
- set test "first attach"
- gdb_test_multiple "attach $testpid" "$test" {
- -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*main.*at .*$srcfile:.*$gdb_prompt $" {
- pass "$test"
- }
- -re "Attaching to program.*`?$escapedbinfile\.exe'?, process $testpid.*\[Switching to thread $testpid\..*\].*$gdb_prompt $" {
- # Response expected on Cygwin.
- pass "$test"
- }
- }
+ gdb_test "attach $testpid" \
+ "Attaching to program.*`?${escapedbinfile}(\.exe)?'?, process $testpid.*main.*at .*$srcfile:.*" \
+ "first attach"
gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
@@ -252,16 +245,9 @@ proc_with_prefix do_attach_tests {} {
}
}
- set test "attach1, after setting file"
- gdb_test_multiple "attach $testpid" "$test" {
- -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*main.*at .*$srcfile:.*$gdb_prompt $" {
- pass "$test"
- }
- -re "Attaching to program.*`?$escapedbinfile\.exe'?, process $testpid.*\[Switching to thread $testpid\..*\].*$gdb_prompt $" {
- # Response expected on Cygwin
- pass "$test"
- }
- }
+ gdb_test "attach $testpid" \
+ "Attaching to program.*`?${escapedbinfile}(\.exe)?'?, process $testpid.*main.*at .*$srcfile:.*" \
+ "attach1, after setting file"
# Verify that we can "see" the variable "should_exit" in the
# program, and that it is zero.
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 939c5813aa7..0477731d40c 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1980,6 +1980,13 @@ windows_nat_target::attach (const char *args, int from_tty)
#endif
do_initial_windows_stuff (pid, 1);
+
+ /* The thread that reports the initial breakpoint, and thus ends up
+ as the selected thread when we get here, was injected into the
+ inferior by DebugActiveProcess. Switch to the main thread, which
+ is normally more useful to the user than the injected thread. */
+ switch_to_thread (first_thread_of_inferior (current_inferior ()));
+
target_terminal::ours ();
}
base-commit: eb6c9310ee4d6cbde509d251fafb54ae45f5a5bf
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 00/47] Windows non-stop mode
2025-06-05 17:57 ` [PATCH v2 00/47] Windows non-stop mode Tom Tromey
2025-06-11 22:06 ` [PATCH] Improve attach on Windows (was: Re: [PATCH v2 00/47] Windows non-stop mode) Pedro Alves
@ 2025-06-11 23:51 ` Pedro Alves
2025-06-12 19:23 ` Tom Tromey
1 sibling, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-06-11 23:51 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
[-- Attachment #1: Type: text/plain, Size: 3107 bytes --]
On 2025-06-05 18:57, Tom Tromey wrote:
>
> I'm not quite as sure about the last one. This test sets a couple of
> breakpoints that have 'commands', where the commands delete the
> breakpoint, like:
>
> [ break main.adb:4 then ... ]
> "silent",
> 'printf "Breakpoint hit #1.\\n"',
> "clear main.adb:4",
> "continue",
> "end",
>
> When running the expected output is seen but gdb says:
>
> (gdb) run
> Starting program: C:\[...]
> Breakpoint hit #1.
> Breakpoint hit #2.
> No unwaited-for children left.
>
> It's that last line that causes the problem. The test expects an "exit
> notification", which is kind of complicated in this test suite, but I
> think for native testing boils down to a message like "[Inferior
> ... exited ...]".
>
> I see this "unwaited-for" text in infrun.c. But I wonder if this is
> really intended.
Hmm, I'm not sure how you're seeing that. That is the output of a TARGET_WAITKIND_NO_RESUMED
event. But it can also be a TARGET_WAITKIND_THREAD_EXITED, which GDB treats as
TARGET_WAITKIND_NO_RESUMED in some cases, but only if the target backend is not in non-stop mode.
In any case, TARGET_WAITKIND_NO_RESUMED means that infrun now believes that the target
has no resumed threads left running, so if it went back to waiting for events, it
would be stuck there forever, as no thread would ever report an event -- none is resumed.
"No unwaited-for children left" is the text I came up with back when I added
TARGET_WAITKIND_NO_RESUMED, and I based it on "man waitpid", which says:
ERRORS
ECHILD (for wait()) The calling process does not have any unwaited-for children.
I kind of regret it, though. Something like "No resumed threads left." would be
probably clearer and less implementer-speak.
Is the test using scheduler locking (and was previously ignoring the error
that "set scheduler-locking on" currently issues on Windows) ?
I ask because that is expected output if you continue with scheduler locking enabled,
and the thread exits. E.g., the new gdb.threads/leader-exit-schedlock.exp testcase added
by the series, has:
Thread 1 "leader-exit-schedlock" hit Breakpoint 2, main () at /home/alves/rocm/gdb/src/gdb/testsuite/gdb.threads/leader-exit-schedlock.c:55
55 return 0; /* break-here */
(gdb) PASS: gdb.threads/leader-exit-schedlock.exp: target-non-stop=off: exit-threads-first=0: schedlock=on: continue to breakpoint: break-here
set scheduler-locking on
(gdb) PASS: gdb.threads/leader-exit-schedlock.exp: target-non-stop=off: exit-threads-first=0: schedlock=on: set scheduler-locking on
c
Continuing.
[Thread 11496.0x150 exited]
No unwaited-for children left.
But I don't suppose your test was really doing that.
Maybe some logs like:
set debug infrun
set debugevents on
set debugexceptions on
set debugexec on
would help see what's going on.
I wrote a new testcase based on your description, see attached. It passes cleanly here.
Can you help pointing me to what would need to be changed in the test to make it closer to the AdaCore test?
Pedro Alves
[-- Attachment #2: 0001-New-gdb.threads-continue-break-command.exp-testcase.patch --]
[-- Type: text/x-patch, Size: 5301 bytes --]
From 326bf95fb5b5b92145d6b2aa30fc30cb7fcd2171 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 11 Jun 2025 23:35:57 +0100
Subject: [PATCH] New gdb.threads/continue-break-command.exp testcase
Change-Id: Ie57493bb5032f28619db418d9d8068aae2d40b09
---
.../gdb.threads/continue-break-command.c | 94 +++++++++++++++++++
.../gdb.threads/continue-break-command.exp | 76 +++++++++++++++
2 files changed, 170 insertions(+)
create mode 100644 gdb/testsuite/gdb.threads/continue-break-command.c
create mode 100644 gdb/testsuite/gdb.threads/continue-break-command.exp
diff --git a/gdb/testsuite/gdb.threads/continue-break-command.c b/gdb/testsuite/gdb.threads/continue-break-command.c
new file mode 100644
index 00000000000..78fabdc5d33
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-break-command.c
@@ -0,0 +1,94 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025 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>
+
+#define NUM_THREADS 3
+
+static pthread_barrier_t threads_started_barrier;
+
+static pthread_barrier_t may_exit_barrier;
+
+static void *
+thread_func (void *arg)
+{
+ /* Wait until all threads have started. */
+ pthread_barrier_wait (&threads_started_barrier);
+
+ /* Wait until the main thread lets us exit. */
+ pthread_barrier_wait (&may_exit_barrier);
+
+ return NULL;
+}
+
+static void
+threads_started (void)
+{
+}
+
+static void
+foo ()
+{
+}
+
+static void
+bar ()
+{
+}
+
+int
+main (void)
+{
+ pthread_t thread[NUM_THREADS];
+ int i;
+
+ alarm (30);
+
+ pthread_barrier_init (&threads_started_barrier, NULL, NUM_THREADS + 1);
+ pthread_barrier_init (&may_exit_barrier, NULL, NUM_THREADS + 1);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ {
+ int ret;
+
+ ret = pthread_create (&thread[i], NULL, thread_func, NULL);
+ assert (ret == 0);
+ }
+
+ pthread_barrier_wait (&threads_started_barrier);
+
+ threads_started ();
+
+ /* The testcase sets breakpoints on these two, that when hit, are
+ removed. */
+ foo ();
+ bar ();
+
+ /* Call them again to make sure the breakpoints are really
+ deleted. */
+ foo ();
+ bar ();
+
+ pthread_barrier_wait (&may_exit_barrier);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ pthread_join (thread[i], NULL);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/continue-break-command.exp b/gdb/testsuite/gdb.threads/continue-break-command.exp
new file mode 100644
index 00000000000..5f020425419
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-break-command.exp
@@ -0,0 +1,76 @@
+# Copyright 2025 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/>.
+
+# XXX, some meaninful explanation here.
+
+standard_testfile .c
+
+if { [build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] \
+ == -1 } {
+ return
+}
+
+proc test {} {
+ clean_restart $::binfile
+
+ if ![runto threads_started] {
+ return
+ }
+
+ # Disable "[Thread 14192.0x3618 exited with code 0]" messages.
+ gdb_test_no_output "set print thread-events off"
+
+ gdb_breakpoint "foo" message
+ gdb_test_multiple "commands" "command for foo" {
+ -re "End with" {
+ gdb_test \
+ [multi_line_input \
+ {silent} \
+ {printf "Breakpoint hit #1.\n"} \
+ {clear foo} \
+ {continue} \
+ {end}] \
+ "" \
+ $gdb_test_name
+ }
+ }
+
+ gdb_breakpoint "bar" message
+ gdb_test_multiple "commands" "commands for bar" {
+ -re "End with" {
+ gdb_test \
+ [multi_line_input \
+ {silent} \
+ {printf "Breakpoint hit #2.\n"} \
+ {clear bar} \
+ {continue} \
+ {end}] \
+ "" \
+ $gdb_test_name
+ }
+ }
+
+ gdb_test "info breakpoints" ".*"
+
+ gdb_test_sequence "continue" "" \
+ [list \
+ "Continuing\\." \
+ "^\r\nBreakpoint hit #1\\." \
+ "^\r\nBreakpoint hit #2\\." \
+ "^\r\n$::inferior_exited_re normally"]
+}
+
+test
base-commit: eb6c9310ee4d6cbde509d251fafb54ae45f5a5bf
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 00/47] Windows non-stop mode
2025-06-11 23:51 ` [PATCH v2 00/47] Windows non-stop mode Pedro Alves
@ 2025-06-12 19:23 ` Tom Tromey
2025-06-13 10:34 ` Pedro Alves
0 siblings, 1 reply; 97+ messages in thread
From: Tom Tromey @ 2025-06-12 19:23 UTC (permalink / raw)
To: Pedro Alves; +Cc: Tom Tromey, gdb-patches
Pedro> Is the test using scheduler locking (and was previously ignoring the error
Pedro> that "set scheduler-locking on" currently issues on Windows) ?
Nope. The test is fairly basic. I've appended the two files (the test
script and the Ada source) , since I don't think there's anything
particularly proprietary about them. They seem to be pretty old.
Pedro> I ask because that is expected output if you continue with scheduler locking enabled,
Pedro> and the thread exits. E.g., the new gdb.threads/leader-exit-schedlock.exp testcase added
Pedro> by the series, has:
Pedro> Maybe some logs like:
Pedro> set debug infrun
Pedro> set debugevents on
Pedro> set debugexceptions on
Pedro> set debugexec on
Pedro> would help see what's going on.
I will try to do this soon.
Tom
from testcase_utils import exit_notification, gnatmake, new_gdb, require
require("Ada")
gnatmake("main")
gdb = new_gdb("main")
gdb.breakpoint("main.adb:4", file="main.adb")
gdb.composite_command(
"commands",
sub_commands=[
"silent",
'printf "Breakpoint hit #1.\\n"',
"clear main.adb:4",
"continue",
"end",
],
)
gdb.breakpoint("main.adb:5", file="main.adb")
gdb.composite_command(
"commands",
sub_commands=[
"silent",
'printf "Breakpoint hit #2.\\n"',
"clear main.adb:5",
"continue",
"end",
],
)
gdb.run(
out=[
"@...",
"Breakpoint hit #@NUMBER.",
"Breakpoint hit #@NUMBER.",
"@...",
exit_notification(),
]
)
procedure Main is
I,J : Integer := 1;
begin
I := J*1000; -- Must remain at line 4 for the purpose of this test
J := I*5; -- Must remain at line 5 for the purpose of this test
end Main;
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 00/47] Windows non-stop mode
2025-06-12 19:23 ` Tom Tromey
@ 2025-06-13 10:34 ` Pedro Alves
2025-06-13 14:23 ` Tom Tromey
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2025-06-13 10:34 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
[-- Attachment #1: Type: text/plain, Size: 1873 bytes --]
On 2025-06-12 20:23, Tom Tromey wrote:
> Pedro> Is the test using scheduler locking (and was previously ignoring the error
> Pedro> that "set scheduler-locking on" currently issues on Windows) ?
>
> Nope. The test is fairly basic. I've appended the two files (the test
> script and the Ada source) , since I don't think there's anything
> particularly proprietary about them. They seem to be pretty old.
Thanks! Indeed. It doesn't look that different from what I've tried, then. I had made the test
use threads just in case it was easier to adapt it that way toward what AdaCore's test was doing,
but I removed all of that now that it's clear it's not necessary, and the result is the same for me.
I've attached the simplified testcase.
You mentioned that you merged my branch into AdaCore's local tree. I wonder if this is some local change
in the AdaCore gdb that might need some adjustment. Is it possible to run that test in AdaCore's testsuite
with a GDB straight based on my branch on top of master, instead of AdaCore's local tree?
Like, the equivalent of:
$ make check RUNTESTFLAGS="GDB=/path/to/pedros/gdb" TESTS="gdb.base/continue-break-command.exp"
>
> Pedro> I ask because that is expected output if you continue with scheduler locking enabled,
> Pedro> and the thread exits. E.g., the new gdb.threads/leader-exit-schedlock.exp testcase added
> Pedro> by the series, has:
>
> Pedro> Maybe some logs like:
> Pedro> set debug infrun
> Pedro> set debugevents on
> Pedro> set debugexceptions on
> Pedro> set debugexec on
> Pedro> would help see what's going on.
>
> I will try to do this soon.
Thank you!
If you end up debugging this, windows_nat_target::any_resumed_thread() is where we
decide whether to report a TARGET_WAITKIND_NO_RESUMED event. Maybe that is returning
true in a situation that it shouldn't, for some reason.
Pedro Alves
[-- Attachment #2: 0001-New-gdb.base-continue-break-command.exp-testcase.patch --]
[-- Type: text/x-patch, Size: 4214 bytes --]
From 581dd502f6898ba38373c2ff302b840b3edfed79 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 11 Jun 2025 23:35:57 +0100
Subject: [PATCH] New gdb.base/continue-break-command.exp testcase
Change-Id: Ie57493bb5032f28619db418d9d8068aae2d40b09
---
.../gdb.base/continue-break-command.c | 42 +++++++++++
.../gdb.base/continue-break-command.exp | 75 +++++++++++++++++++
2 files changed, 117 insertions(+)
create mode 100644 gdb/testsuite/gdb.base/continue-break-command.c
create mode 100644 gdb/testsuite/gdb.base/continue-break-command.exp
diff --git a/gdb/testsuite/gdb.base/continue-break-command.c b/gdb/testsuite/gdb.base/continue-break-command.c
new file mode 100644
index 00000000000..585476aa838
--- /dev/null
+++ b/gdb/testsuite/gdb.base/continue-break-command.c
@@ -0,0 +1,42 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025 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/>. */
+
+static void
+foo ()
+{
+}
+
+static void
+bar ()
+{
+}
+
+int
+main (void)
+{
+ /* The testcase sets breakpoints on these two, that when hit, are
+ removed by their corresponding breakpoint command. */
+ foo ();
+ bar ();
+
+ /* Call them again to make sure the breakpoints are really
+ deleted. */
+ foo ();
+ bar ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/continue-break-command.exp b/gdb/testsuite/gdb.base/continue-break-command.exp
new file mode 100644
index 00000000000..6d77d282d78
--- /dev/null
+++ b/gdb/testsuite/gdb.base/continue-break-command.exp
@@ -0,0 +1,75 @@
+# Copyright 2025 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/>.
+
+# XXX, some meaninful explanation here.
+
+standard_testfile .c
+
+if { [build_executable "failed to prepare" $testfile $srcfile {debug}] \
+ == -1 } {
+ return
+}
+
+proc test {} {
+ clean_restart $::binfile
+
+ if ![runto_main] {
+ return
+ }
+
+ # Disable "[Thread 14192.0x3618 exited with code 0]" messages.
+ gdb_test_no_output "set print thread-events off"
+
+ gdb_breakpoint "foo" message
+ gdb_test_multiple "commands" "command for foo" {
+ -re "End with" {
+ gdb_test \
+ [multi_line_input \
+ {silent} \
+ {printf "Breakpoint hit #1.\n"} \
+ {clear foo} \
+ {continue} \
+ {end}] \
+ "" \
+ $gdb_test_name
+ }
+ }
+
+ gdb_breakpoint "bar" message
+ gdb_test_multiple "commands" "commands for bar" {
+ -re "End with" {
+ gdb_test \
+ [multi_line_input \
+ {silent} \
+ {printf "Breakpoint hit #2.\n"} \
+ {clear bar} \
+ {continue} \
+ {end}] \
+ "" \
+ $gdb_test_name
+ }
+ }
+
+ gdb_test "info breakpoints" ".*"
+
+ gdb_test_sequence "continue" "" \
+ [list \
+ "Continuing\\." \
+ "^\r\nBreakpoint hit #1\\." \
+ "^\r\nBreakpoint hit #2\\." \
+ "^\r\n$::inferior_exited_re normally"]
+}
+
+test
base-commit: eb6c9310ee4d6cbde509d251fafb54ae45f5a5bf
--
2.49.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 00/47] Windows non-stop mode
2025-06-13 10:34 ` Pedro Alves
@ 2025-06-13 14:23 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2025-06-13 14:23 UTC (permalink / raw)
To: Pedro Alves; +Cc: Tom Tromey, gdb-patches
Pedro> You mentioned that you merged my branch into AdaCore's local
Pedro> tree. I wonder if this is some local change in the AdaCore gdb
Pedro> that might need some adjustment. Is it possible to run that test
Pedro> in AdaCore's testsuite with a GDB straight based on my branch on
Pedro> top of master, instead of AdaCore's local tree?
Yeah, I think so. I will try it.
There aren't many local patches. The only windows-nat patch is
+ DEBUG_EXCEPTION_SIMPLE ("EXCEPTION_BREAKPOINT");
... so not very interesting.
There is a "random thread switch" patch in infrun though.
(I sent this to the list once.)
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH] Improve attach on Windows
2025-06-11 22:06 ` [PATCH] Improve attach on Windows (was: Re: [PATCH v2 00/47] Windows non-stop mode) Pedro Alves
@ 2026-04-02 12:21 ` Pedro Alves
2026-04-02 18:52 ` Tom Tromey
0 siblings, 1 reply; 97+ messages in thread
From: Pedro Alves @ 2026-04-02 12:21 UTC (permalink / raw)
To: Tom Tromey; +Cc: gdb-patches
Hi!
I'm looking at the windows non-stop series again, trying to figure out where we were.
There was one problem exposed by the AdaCore testsuite that I wasn't able to reproduce locally, so I don't know what to do about it.
More below.
I'll look at the rest of the series, rebase, and see what can be merged already.
On 2025-06-11 23:06, Pedro Alves wrote:
> On 2025-06-05 18:57, Tom Tromey wrote:
>>>>>>> "Pedro" == Pedro Alves <pedro@palves.net> writes:
>>
>> Pedro> I've pushed the series to the users/palves/windows-non-stop-v2 branch
>> Pedro> on sourceware.org, for your convenience.
>>
>> I merged this into the local tree and ran the internal AdaCore test
>> suite on Windows 2016.
>
> Thank you very much for doing this. Much appreciated.
>
>>
>> There were three "failures".
>>
>> One of them is actually an improvement, where the test works around a
>> current issue.
>>
>
> One step forward, two steps back, I guess.
>
> Tackling this incrementally. I've looked at the first one:
>
>> One of them seems to be a problem with the test. This test "attach"es
>> to a running process without a "file", and the test doesn't seem to
>> match the new output:
>>
>> (gdb) attach 1228
>> Attaching to process 1228
>> [Switching to Thread 1228.0x580]
>> 0x00007ffe3c756714 in ntdll!ZwDelayExecution ()
>> from C:\Windows\SYSTEM32\ntdll.dll
>>
>
> I guess your test wasn't expecting that the current frame is printed?
>
> See the patch below, on top of current master. You are now seeing the current
> frame being printed, unlike what I claim in the commit log of the patch below,
> because with target-non-stop on, the Windows backend is a target_wait_no_wait==false
> target, like Linux.
>
> But even with the non-stop series, with "maint set target-non-stop off",
> (or with Windows older than Windows 10,) then target_wait_no_wait==true, and
> GDB doesn't print the frame.
>
> IMO, this is just a preexisting bug that the non-stop series happens to hide.
>
> Let me know what you think of the patch.
>
> And yes, the fact that run control communicates with normal_stop via
> globals is ugly and fragile to say the least.
>
> From 0cb83571c91ba42b3de519b034509092a526dc65 Mon Sep 17 00:00:00 2001
> From: Pedro Alves <pedro@palves.net>
> Date: Wed, 11 Jun 2025 22:05:23 +0100
> Subject: [PATCH] Improve attach on Windows
I think this one is a clear and easy improvement.
I've retested it, and I see the same progression in gdb.base/attach.exp, so I've now pushed it.
Thanks,
Pedro Alves
>
> Unlike most targets, on Windows, when you attach, GDB doesn't print
> the current stack frame. Vis:
>
> On GNU/Linux:
>
> attach 3340347
> Attaching to program: /home/pedro/gdb/build/gdb/testsuite/outputs/gdb.base/attach/attach, process 3340347
> Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
> Reading symbols from /usr/lib/debug/.build-id/d5/197096f709801829b118af1b7cf6631efa2dcd.debug...
> Reading symbols from /lib64/ld-linux-x86-64.so.2...
> Reading symbols from /usr/lib/debug/.build-id/9c/b53985768bb99f138f48655f7b8bf7e420d13d.debug...
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
> 0x00005b3bf29be174 in main () at /home/pedro/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/attach.c:19
> 19 while (! should_exit)
> (gdb) PASS: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
>
> On Cygwin:
>
> (gdb) attach 6692
> Attaching to program: /home/alves/gdb/build-cygwin-testsuite/outputs/gdb.base/attach/attach, process 6692
> [New Thread 6692.0x2e60]
> [New Thread 6692.0x2e9c]
> [New Thread 6692.0xd6c]
> [New Thread 6692.0x137c]
> [New Thread 6692.0x1270]
> (gdb) FAIL: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
>
> On Linux, GDB prints the frame because after the target_attach, GDB
> goes back to the event loop, to wait for an initial stop event. The
> stop event arrives, and we process it, which sets the stop_print_frame
> global, and then we get to normal_stop, which prints the frame iff
> stop_print_frame is set, which it is.
>
> Windows OTOH, is a target_attach_no_wait target, so after
> target_attach, there is no going back to event loop. In
> infcmd.c:attach_command, we go straight to attach_post_wait which
> takes us to normal_stop. But this time, nothing set
> stop_print_frame to true, so no frame is printed. Actually, if the
> global happened to be true due to an earlier event from debugging a
> previous inferior, then we will print the frame.
>
> This patch makes GDB's behavior consistent, by making sure the globals
> normal_stop looks at are in a good state in the target_attach_no_wait
> path.
>
> With that alone, GDB now prints the frame:
>
> (gdb) attach 2915
> Attaching to program: /usr/bin/sleep.exe, process 2832
> [New Thread 2832.0x2a68]
> [New Thread 2832.0xb1c]
> [New Thread 2832.0x8ac]
> [Switching to Thread 2832.0x8ac]
> 0x00007ffec51d4a71 in ntdll!DbgBreakPoint () from C:/Windows/SYSTEM32/ntdll.dll
>
> This is still not ideal, IMHO, as the current thread is the thread
> that Windows injects to attach:
>
> (gdb) info threads
> Id Target Id Frame
> 1 Thread 2832.0x2100 "sleep" 0x00007ffec51d18d7 in ntdll!ZwWaitForMultipleObjects () from C:/Windows/SYSTEM32/ntdll.dll
> 2 Thread 2832.0x2a68 "sig" 0x00007ffec51d0e47 in ntdll!ZwReadFile () from C:/Windows/SYSTEM32/ntdll.dll
> 3 Thread 2832.0xb1c 0x00007ffec51d49d7 in ntdll!ZwWaitForWorkViaWorkerFactory () from C:/Windows/SYSTEM32/ntdll.dll
> * 4 Thread 2832.0x8ac 0x00007ffec51d4a71 in ntdll!DbgBreakPoint () from C:/Windows/SYSTEM32/ntdll.dll
>
> Automatically switching to main thread is IMHO more useful. That
> results in very similar output than what we see on Linux:
>
> attach 5164
> Attaching to program: /home/alves/gdb/build-cygwin-testsuite/outputs/gdb.base/attach/attach, process 5164
> [New Thread 5164.0x87c]
> [New Thread 5164.0x28f0]
> [New Thread 5164.0x376c]
> [New Thread 5164.0x2db4]
> [New Thread 5164.0xce4]
> main () at /home/alves/gdb/src/gdb/testsuite/gdb.base/attach.c:19
> 19 while (! should_exit)
> (gdb)
>
> If we do this, then we can simplify gdb.base/attach.exp a bit by
> removing a couple Cygwin special cases.
>
> The patch does all that, which results in the following
> gdb.base/attach.exp progressions:
>
> -FAIL: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
> -FAIL: gdb.base/attach.exp: do_attach_tests: attach2, with no file
> -FAIL: gdb.base/attach.exp: do_attach_tests: load file manually, after attach2 (re-read) (got interactive prompt)
> -FAIL: gdb.base/attach.exp: do_attach_tests: attach when process' a.out not in cwd
> -FAIL: gdb.base/attach.exp: do_attach_failure_tests: first attach
> +PASS: gdb.base/attach.exp: do_attach_tests: attach1, after setting file
> +PASS: gdb.base/attach.exp: do_attach_tests: attach2, with no file
> +PASS: gdb.base/attach.exp: do_attach_tests: attach when process' a.out not in cwd
> +PASS: gdb.base/attach.exp: do_attach_failure_tests: first attach
>
> Change-Id: I359bdb25660c9a4d5d873e8771cfd1cd2a54c97b
> ---
> gdb/infcmd.c | 5 ++++-
> gdb/infrun.c | 13 +++++++++++++
> gdb/infrun.h | 3 +++
> gdb/testsuite/gdb.base/attach.exp | 26 ++++++--------------------
> gdb/windows-nat.c | 7 +++++++
> 5 files changed, 33 insertions(+), 21 deletions(-)
>
> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> index e9b58ce5521..cbf2373193c 100644
> --- a/gdb/infcmd.c
> +++ b/gdb/infcmd.c
> @@ -2722,7 +2722,10 @@ attach_command (const char *args, int from_tty)
> return;
> }
> else
> - attach_post_wait (from_tty, mode);
> + {
> + set_normal_stop_state_just_attached ();
> + attach_post_wait (from_tty, mode);
> + }
>
> disable_commit_resumed.reset_and_commit ();
> }
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 2e02642c52a..41ab3342ee3 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -407,6 +407,19 @@ static process_stratum_target *target_last_proc_target;
> static ptid_t target_last_wait_ptid;
> static struct target_waitstatus target_last_waitstatus;
>
> +/* See infrun.h. */
> +
> +void
> +set_normal_stop_state_just_attached ()
> +{
> + stop_print_frame = true;
> + stopped_by_random_signal = 0;
> +
> + target_waitstatus status;
> + status.set_ignore ();
> + set_last_target_status (nullptr, minus_one_ptid, status);
> +}
> +
> void init_thread_stepping_state (struct thread_info *tss);
>
> static const char follow_fork_mode_child[] = "child";
> diff --git a/gdb/infrun.h b/gdb/infrun.h
> index b9b64aca45a..b707314fba6 100644
> --- a/gdb/infrun.h
> +++ b/gdb/infrun.h
> @@ -418,5 +418,8 @@ struct scoped_enable_commit_resumed
> bool m_prev_enable_commit_resumed;
> };
>
> +/* Set up state for normal_stop after we just attached, on
> + target_attach_no_wait targets. */
> +extern void set_normal_stop_state_just_attached ();
>
> #endif /* GDB_INFRUN_H */
> diff --git a/gdb/testsuite/gdb.base/attach.exp b/gdb/testsuite/gdb.base/attach.exp
> index 0d1550a0541..484fa776ae4 100644
> --- a/gdb/testsuite/gdb.base/attach.exp
> +++ b/gdb/testsuite/gdb.base/attach.exp
> @@ -159,16 +159,9 @@ proc_with_prefix do_attach_failure_tests {} {
>
> # Verify that we can't double attach to the process.
>
> - set test "first attach"
> - gdb_test_multiple "attach $testpid" "$test" {
> - -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*main.*at .*$srcfile:.*$gdb_prompt $" {
> - pass "$test"
> - }
> - -re "Attaching to program.*`?$escapedbinfile\.exe'?, process $testpid.*\[Switching to thread $testpid\..*\].*$gdb_prompt $" {
> - # Response expected on Cygwin.
> - pass "$test"
> - }
> - }
> + gdb_test "attach $testpid" \
> + "Attaching to program.*`?${escapedbinfile}(\.exe)?'?, process $testpid.*main.*at .*$srcfile:.*" \
> + "first attach"
>
> gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
> gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
> @@ -252,16 +245,9 @@ proc_with_prefix do_attach_tests {} {
> }
> }
>
> - set test "attach1, after setting file"
> - gdb_test_multiple "attach $testpid" "$test" {
> - -re "Attaching to program.*`?$escapedbinfile'?, process $testpid.*main.*at .*$srcfile:.*$gdb_prompt $" {
> - pass "$test"
> - }
> - -re "Attaching to program.*`?$escapedbinfile\.exe'?, process $testpid.*\[Switching to thread $testpid\..*\].*$gdb_prompt $" {
> - # Response expected on Cygwin
> - pass "$test"
> - }
> - }
> + gdb_test "attach $testpid" \
> + "Attaching to program.*`?${escapedbinfile}(\.exe)?'?, process $testpid.*main.*at .*$srcfile:.*" \
> + "attach1, after setting file"
>
> # Verify that we can "see" the variable "should_exit" in the
> # program, and that it is zero.
> diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
> index 939c5813aa7..0477731d40c 100644
> --- a/gdb/windows-nat.c
> +++ b/gdb/windows-nat.c
> @@ -1980,6 +1980,13 @@ windows_nat_target::attach (const char *args, int from_tty)
> #endif
>
> do_initial_windows_stuff (pid, 1);
> +
> + /* The thread that reports the initial breakpoint, and thus ends up
> + as the selected thread when we get here, was injected into the
> + inferior by DebugActiveProcess. Switch to the main thread, which
> + is normally more useful to the user than the injected thread. */
> + switch_to_thread (first_thread_of_inferior (current_inferior ()));
> +
> target_terminal::ours ();
> }
>
>
> base-commit: eb6c9310ee4d6cbde509d251fafb54ae45f5a5bf
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 02/47] Add test for continuing with some threads running
2025-05-21 19:36 ` Kevin Buettner
@ 2026-04-02 13:07 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2026-04-02 13:07 UTC (permalink / raw)
To: Kevin Buettner; +Cc: gdb-patches
Hi!
On 2025-05-21 20:36, Kevin Buettner wrote:
> On Mon, 19 May 2025 14:22:23 +0100
Wow, another year passed... :-/
> Pedro Alves <pedro@palves.net> wrote:
>
>> This testcase would have helped catch some issues I ran into while
>> working on the Windows non-stop support.
>>
>> It tests continuing all threads in all-stop mode when at least one
>> thread is already running.
>
> LGTM.
>
> Approved-by: Kevin Buettner <kevinb@redhat.com>
>
Thank you. I've merged this, as below, with minor tweaks to adjust to modern times:
- copyright year
- "maint set target-non-stop on" for gdbserver
- gdb_watchdog instead of alarm
- tcl formatting to please pre-commit
- pass testfile instead of binfile to clean_restart
From f0fab4d2e5ff01cd81534eb64b43f8ab3da5a82a Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Fri, 24 Jan 2025 18:10:50 +0000
Subject: [PATCH] Add test for continuing with some threads running
This testcase would have helped catch some issues I ran into while
working on the Windows non-stop support.
It tests continuing all threads in all-stop mode when at least one
thread is already running.
Approved-by: Kevin Buettner <kevinb@redhat.com>
Change-Id: Ie8cd5c67502aed3c3b159d5eb5eeedee2f84eeef
commit-id:f4f07192
---
.../gdb.threads/continue-some-running.c | 77 +++++++++++++++++++
.../gdb.threads/continue-some-running.exp | 57 ++++++++++++++
2 files changed, 134 insertions(+)
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.c
create mode 100644 gdb/testsuite/gdb.threads/continue-some-running.exp
diff --git a/gdb/testsuite/gdb.threads/continue-some-running.c b/gdb/testsuite/gdb.threads/continue-some-running.c
new file mode 100644
index 00000000000..fd77af3cefa
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-some-running.c
@@ -0,0 +1,77 @@
+/* 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>
+#include "gdb_watchdog.h"
+
+volatile int wait_for_gdb = 1;
+
+#define NUM_THREADS 3
+
+static pthread_barrier_t threads_started_barrier;
+
+static pthread_barrier_t may_exit_barrier;
+
+static void *
+thread_func (void *arg)
+{
+ /* Wait until all threads have started. */
+ pthread_barrier_wait (&threads_started_barrier);
+
+ /* Wait until the main thread lets us exit. */
+ pthread_barrier_wait (&may_exit_barrier);
+
+ return NULL;
+}
+
+static void
+threads_started (void)
+{
+}
+
+int
+main (void)
+{
+ pthread_t thread[NUM_THREADS];
+ int i;
+
+ gdb_watchdog (30);
+
+ pthread_barrier_init (&threads_started_barrier, NULL, NUM_THREADS + 1);
+ pthread_barrier_init (&may_exit_barrier, NULL, NUM_THREADS + 1);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ {
+ int ret;
+
+ ret = pthread_create (&thread[i], NULL, thread_func, NULL);
+ assert (ret == 0);
+ }
+
+ pthread_barrier_wait (&threads_started_barrier);
+
+ threads_started ();
+
+ pthread_barrier_wait (&may_exit_barrier);
+
+ for (i = 0; i < NUM_THREADS; i++)
+ pthread_join (thread[i], NULL);
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/continue-some-running.exp b/gdb/testsuite/gdb.threads/continue-some-running.exp
new file mode 100644
index 00000000000..d6716e8ff16
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/continue-some-running.exp
@@ -0,0 +1,57 @@
+# 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 continuing in all-stop mode when one thread is already running.
+
+standard_testfile .c
+
+if { [build_executable "failed to prepare" $testfile $srcfile \
+ {debug pthreads}] \
+ == -1 } {
+ return
+}
+
+proc test {} {
+ # This test requires background execution, which relies on
+ # non-stop mode.
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS " -ex \"maint set target-non-stop on\""
+ clean_restart $::testfile
+ }
+
+ if {![runto threads_started]} {
+ return
+ }
+
+ delete_breakpoints
+
+ # Set a non-main thread running, while everything else is left
+ # stopped.
+ gdb_test_no_output "set scheduler-locking on"
+
+ gdb_test "thread 2" ".*" "switch to secondary thread"
+
+ gdb_test -no-prompt-anchor "continue &" "Continuing\\."
+
+ # Now resume all threads, while there is already one thread
+ # running.
+ gdb_test "thread 1" ".*" "switch to main thread"
+
+ gdb_test_no_output "set scheduler-locking off"
+
+ gdb_continue_to_end "" continue 1
+}
+
+test
--
2.53.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call
2025-05-21 19:44 ` Kevin Buettner
@ 2026-04-02 13:17 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2026-04-02 13:17 UTC (permalink / raw)
To: Kevin Buettner; +Cc: gdb-patches
On 2025-05-21 20:44, Kevin Buettner wrote:
> On Mon, 19 May 2025 14:22:24 +0100
> Pedro Alves <pedro@palves.net> wrote:
>
>> There's one unnecessary check for currently_stepping in
>> handle_signal_stop that can be removed. It is unnecessary because
>> currently_stepping is only ever called if
>> ecs->event_thread->control.trap_expected is true, and then if it is
>> true, then currently_stepping always returns true too.
>
> After looking at the definition of currently_stepping(), I agree
> with your reasoning (and your patch).
>
> Approved-by: Kevin Buettner <kevinb@redhat.com>
>
Thank you, now merged.
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue
2025-05-19 13:22 ` [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue Pedro Alves
@ 2026-04-02 13:33 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2026-04-02 13:33 UTC (permalink / raw)
To: gdb-patches
FYI, I've merged this one too.
Pedro Alves
On 2025-05-19 14:22, Pedro Alves wrote:
> The gdb.base/watchpoint.exp on Windows with non-stop support added
> (later in the series) exposed an issue with the currently_stepping
> logic when tested with software watchpoints.
>
> The issue only happens when:
>
> - You have multiple threads. gdb.base/watchpoint.exp exposed it on
> Windows because there the OS always spawns a few extra threads.
>
> - Displaced stepping is not available. The Windows non-stop work
> does not implement displaced stepping yet. That is left as an
> optimization for later.
>
> - The target backend is working in non-stop mode.
>
> I've written a new test that exposes the issue on GNU/Linux as well
> (on hardware single-step targets, like x86-64). There, we see:
>
> continue
> Continuing.
> ../../src/gdb/infrun.c:2918: internal-error: resume_1: Assertion `!(thread_has_single_step_breakpoints_set (tp) && step)' failed.
> A problem internal to GDB has been detected,
> further debugging may prove unreliable.
> ----- Backtrace -----
> FAIL: gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp: target-non-stop=on: displaced-stepping=off: continue until exit (GDB internal error)
>
> Currently, software watchpoints are implemented by forcing
> single-stepping. That is done by currently_stepping returning true
> when we have a software watchpoint. proceed calls resume, which calls
> resume_1, which then ends up always requesting a single-step resume,
> even if the higher layers wanted a continue.
>
> Now, if you set a software watchpoint, and then continue the program,
> and there's a breakpoint at the current PC, GDB needs to step over
> that breakpoint first. If displaced stepping is not available, then
> GDB temporarily pauses all threads, removes the breakpoint,
> single-steps the thread that needs to move past the breakpoint, and
> then finally, reinserts the breakpoint, and restarts all threads
> again. That last restarting step happens in the restart_threads
> infrun function.
>
> restart_threads iterates over all threads trying to restart them one
> by one. There, we have:
>
> if (currently_stepping (tp))
> {
> infrun_debug_printf ("restart threads: [%s] was stepping",
> tp->ptid.to_string ().c_str ());
>
> but, what if TP is actually a new thread that hasn't yet ever set
> stepping? currently_stepping still returns true, due to the software
> watchpoint, and we end up in keep_going_stepped_thread, here:
>
> if (tp->stop_pc () != tp->prev_pc)
> {
> ptid_t resume_ptid;
>
> infrun_debug_printf ("expected thread advanced also (%s -> %s)",
> paddress (current_inferior ()->arch (), tp->prev_pc),
> paddress (current_inferior ()->arch (),
> tp->stop_pc ()));
>
> ... because prev_pc was stale at that point (we had no reason to
> update it earlier). E.g. on Windows we see something like:
>
> [infrun] restart_threads: start: event_thread=1867996.1867996.0, inf=-1
> [infrun] restart_threads: restart threads: [1867996.1867996.0] is event thread
> [infrun] restart_threads: restart threads: [1867996.1868003.0] was stepping
> [infrun] keep_going_stepped_thread: resuming previously stepped thread
> [infrun] keep_going_stepped_thread: expected thread advanced also (0 -> 0x7ffff7ce57f8)
> [infrun] clear_step_over_info: clearing step over info
> [infrun] do_target_resume: resume_ptid=1867996.1868003.0, step=0, sig=GDB_SIGNAL_0
>
> On GNU/Linux, we may see:
>
> [infrun] keep_going_stepped_thread: expected thread advanced also (0x7ffff7d2683d -> 0x7ffff7ce57f8)
>
> there prev_pc might have been updated on an earlier proceed call,
> which makes the issue harder to see, but it is stale too here.
>
> That means we insert a single-step breakpoint at the current PC, and
> continue the thread, with target_resume directly, asking for a normal
> continue.
>
> Eventually, something causes a user-visible stop. For example, the
> software watchpoint triggers. That makes GDB stop all threads.
>
> Now, if the user re-resumes the program, say, with "continue", we fail
> this assertion in resume_1 coming from proceed:
>
> /* If STEP is set, it's a request to use hardware stepping
> facilities. But in that case, we should never
> use singlestep breakpoint. */
> gdb_assert (!(thread_has_single_step_breakpoints_set (tp) && step));
>
> "step" is true because currently_stepping returns true since we have a
> software watchpoint. And the thread has a single-step breakpoint
> installed from earlier, because of that code mentioned above, in
> keep_going_stepped_thread reached from restart_threads.
>
> This patch fixes the root cause -- the currently_stepping call in
> restart_threads returned true for a thread that has never set stepping
> in the first place. This is because currently_stepping really serves
> two purposes currently:
>
> #1 - for a thread that we are about to resume, should we set it
> stepping?
>
> #2 - for a thread that just stopped, was it stepping before?
>
> The fix is thus to decouple those two aspects:
>
> - for #1, we simply rename currently_stepping to should_step.
>
> - for #2, we record whether the thread was stepping before in a new
> currently_stepping flag in thread_info.
>
> As mentioned, there's a new testcase included. I tested this on
> x86-64 GNU/Linux, native and gdbserver, and on Windows x64 with the
> non-stop series. The assertion triggers on all of those with the fix,
> and is fixed by this patch on all of those, too.
>
> Change-Id: I7b07bc62e8570333d2e4856d2e55ae6e58f8260c
> ---
> gdb/gdbthread.h | 4 +
> gdb/infrun.c | 28 +++---
> .../sw-watchpoint-step-over-bp-with-threads.c | 64 +++++++++++++
> ...w-watchpoint-step-over-bp-with-threads.exp | 91 +++++++++++++++++++
> 4 files changed, 173 insertions(+), 14 deletions(-)
> create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
> create mode 100644 gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
>
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index c561e9a7b64..a8fd967c702 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -152,6 +152,10 @@ struct thread_control_state
> the finished single step. */
> int trap_expected = 0;
>
> + /* True if the thread TP is in the middle of (software or hardware)
> + single-stepping. */
> + bool currently_stepping = false;
> +
> /* Nonzero if the thread is being proceeded for a "finish" command
> or a similar situation when return value should be printed. */
> int proceed_to_finish = 0;
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 119bd151034..9f625a7bd44 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -87,7 +87,7 @@ static void sig_print_header (void);
>
> static void follow_inferior_reset_breakpoints (void);
>
> -static bool currently_stepping (struct thread_info *tp);
> +static bool should_step (thread_info *tp);
>
> static void insert_hp_step_resume_breakpoint_at_frame (const frame_info_ptr &);
>
> @@ -2656,7 +2656,7 @@ resume_1 (enum gdb_signal sig)
> "status %s (currently_stepping=%d).",
> tp->ptid.to_string ().c_str (),
> tp->pending_waitstatus ().to_string ().c_str (),
> - currently_stepping (tp));
> + tp->control.currently_stepping);
>
> tp->inf->process_target ()->threads_executing = true;
> tp->set_resumed (true);
> @@ -2685,7 +2685,7 @@ resume_1 (enum gdb_signal sig)
> tp->stepped_breakpoint = 0;
>
> /* Depends on stepped_breakpoint. */
> - step = currently_stepping (tp);
> + step = tp->control.currently_stepping = should_step (tp);
>
> if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
> {
> @@ -3060,7 +3060,7 @@ clear_proceed_status_thread (struct thread_info *tp)
> ("thread %s has pending wait status %s (currently_stepping=%d).",
> tp->ptid.to_string ().c_str (),
> tp->pending_waitstatus ().to_string ().c_str (),
> - currently_stepping (tp));
> + tp->control.currently_stepping);
> }
> }
>
> @@ -5041,7 +5041,7 @@ adjust_pc_after_break (struct thread_info *thread,
> we also need to back up to the breakpoint address. */
>
> if (thread_has_single_step_breakpoints_set (thread)
> - || !currently_stepping (thread)
> + || !thread->control.currently_stepping
> || (thread->stepped_breakpoint
> && thread->prev_pc == breakpoint_pc))
> regcache_write_pc (regcache, breakpoint_pc);
> @@ -5368,7 +5368,7 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus &ws)
> && software_breakpoint_inserted_here_p (aspace, pc))
> tp->set_stop_reason (TARGET_STOPPED_BY_SW_BREAKPOINT);
> else if (!thread_has_single_step_breakpoints_set (tp)
> - && currently_stepping (tp))
> + && tp->control.currently_stepping)
> tp->set_stop_reason (TARGET_STOPPED_BY_SINGLE_STEP);
> }
> }
> @@ -5563,7 +5563,7 @@ handle_one (const wait_one_event &event)
> paddress (current_inferior ()->arch (),
> t->stop_pc ()),
> t->ptid.to_string ().c_str (),
> - currently_stepping (t));
> + t->control.currently_stepping);
> }
> }
>
> @@ -6668,7 +6668,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
> tp->ptid.to_string ().c_str ());
> }
>
> - if (currently_stepping (tp))
> + if (tp->control.currently_stepping)
> {
> infrun_debug_printf ("restart threads: [%s] was stepping",
> tp->ptid.to_string ().c_str ());
> @@ -6795,7 +6795,7 @@ finish_step_over (struct execution_control_state *ecs)
> paddress (current_inferior ()->arch (),
> tp->stop_pc ()),
> tp->ptid.to_string ().c_str (),
> - currently_stepping (tp));
> + tp->control.currently_stepping);
>
> /* This in-line step-over finished; clear this so we won't
> start a new one. This is what handle_signal_stop would
> @@ -7204,7 +7204,7 @@ handle_signal_stop (struct execution_control_state *ecs)
> /* If not, perhaps stepping/nexting can. */
> if (random_signal)
> random_signal = !(ecs->event_thread->stop_signal () == GDB_SIGNAL_TRAP
> - && currently_stepping (ecs->event_thread));
> + && ecs->event_thread->control.currently_stepping);
>
> /* Perhaps the thread hit a single-step breakpoint of _another_
> thread. Single-step breakpoints are transparent to the
> @@ -8632,12 +8632,12 @@ keep_going_stepped_thread (struct thread_info *tp)
> return true;
> }
>
> -/* Is thread TP in the middle of (software or hardware)
> - single-stepping? (Note the result of this function must never be
> - passed directly as target_resume's STEP parameter.) */
> +/* Should thread TP be stepped (software or hardware)? (Note the
> + result of this function must never be passed directly as
> + target_resume's STEP parameter.) */
>
> static bool
> -currently_stepping (struct thread_info *tp)
> +should_step (thread_info *tp)
> {
> return ((tp->control.step_range_end
> && tp->control.step_resume_breakpoint == nullptr)
> diff --git a/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
> new file mode 100644
> index 00000000000..7f014036c9b
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.c
> @@ -0,0 +1,64 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> + Copyright 2025 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 *
> +thread_func (void *arg)
> +{
> + pthread_barrier_wait (&threads_started_barrier);
> +
> + while (1)
> + sleep (1);
> +
> + return NULL;
> +}
> +
> +static void
> +dummy ()
> +{
> +}
> +
> +static unsigned watched_global = 0;
> +
> +int
> +main (void)
> +{
> + pthread_t thread;
> + int ret;
> +
> + alarm (30);
> +
> + pthread_barrier_init (&threads_started_barrier, NULL, 2);
> +
> + ret = pthread_create (&thread, NULL, thread_func, NULL);
> + assert (ret == 0);
> +
> + /* Make sure all threads are scheduled before hitting the
> + breakpoint. */
> + pthread_barrier_wait (&threads_started_barrier);
> +
> + ++watched_global; /* break here start */
> +
> + dummy (); /* just so there's extra code here */
> +
> + return 0; /* break here end */
> +}
> diff --git a/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
> new file mode 100644
> index 00000000000..b45db57e80f
> --- /dev/null
> +++ b/gdb/testsuite/gdb.threads/sw-watchpoint-step-over-bp-with-threads.exp
> @@ -0,0 +1,91 @@
> +# Copyright 2025 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 continuing with a software watchpoint installed, when there are
> +# multiple threads, and previously we stepped over a breakpoint.
> +#
> +# This is a regression test for a GDB bug where stepping over a
> +# breakpoint in-line made GDB insert a software single-step breakpoint
> +# in some threads by mistake, which later would cause an assertion to
> +# fail.
> +#
> +# The issue only triggered when:
> +#
> +# - The program has multiple threads.
> +# - The target backend is working in non-stop mode.
> +# - Displaced stepping is not available.
> +# - The target supports hardware single-step.
> +#
> +# However, we exercise more combinations for completeness.
> +
> +standard_testfile .c
> +
> +if { [build_executable "failed to prepare" $testfile $srcfile \
> + {debug pthreads}] \
> + == -1 } {
> + return
> +}
> +
> +proc test {target-non-stop displaced-stepping} {
> +
> + save_vars ::GDBFLAGS {
> + append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
> + append ::GDBFLAGS " -ex \"set displaced ${displaced-stepping}\""
> + clean_restart $::binfile
> + }
> +
> + if ![runto_main] {
> + return
> + }
> +
> + # Run to a breakpoint, and leave it installed, so that GDB needs
> + # to step over it before continuing.
> + gdb_breakpoint [gdb_get_line_number "break here start"]
> + gdb_continue_to_breakpoint "started"
> +
> + # GDB should know about at least two threads by now.
> + gdb_test "p \$_inferior_thread_count >= 2" " = 1"
> +
> + # Set a software watchpoint. This makes GDB single-step all
> + # instructions when we next continue.
> + gdb_test_no_output "set can-use-hw-watchpoints 0"
> + gdb_test "watch watched_global" "Watchpoint $::decimal: watched_global"
> +
> + # Continue with the software watchpoint in place. In the original
> + # bug, with displaced stepping disabled, this would make GDB
> + # incorrectly install a software single-step breakpoint on threads
> + # other than the main one.
> + gdb_test_multiple "cont" "continue to watchpoint" {
> + -re -wrap "Continuing.*Watchpoint.*watched_global.*Old value = 0.*New value = 1.*" {
> + pass $gdb_test_name
> + }
> + }
> +
> + # The final continue, with the software watchpoint set, so that
> + # GDB single-steps all threads (if the target is non-stop). With
> + # the buggy GDB, the non-main thread had a software single-step
> + # breakpoint set, and on hardware single-step targets, GDB would
> + # fail an assertion that checks that we never ask the target to
> + # hardware single-step a thread when we have a software
> + # single-step breakpoint set for that thread.
> + gdb_breakpoint [gdb_get_line_number "break here end"]
> + gdb_continue_to_breakpoint "end"
> +}
> +
> +foreach_with_prefix target-non-stop {auto on off} {
> + foreach_with_prefix displaced-stepping {auto on off} {
> + test ${target-non-stop} ${displaced-stepping}
> + }
> +}
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH] Improve attach on Windows
2026-04-02 12:21 ` [PATCH] Improve attach on Windows Pedro Alves
@ 2026-04-02 18:52 ` Tom Tromey
0 siblings, 0 replies; 97+ messages in thread
From: Tom Tromey @ 2026-04-02 18:52 UTC (permalink / raw)
To: Pedro Alves; +Cc: Tom Tromey, gdb-patches
Pedro> I'm looking at the windows non-stop series again, trying to
Pedro> figure out where we were.
IIRC I reviewed some but not all of them. I don't really recall though.
Pedro> There was one problem exposed by the AdaCore testsuite that I
Pedro> wasn't able to reproduce locally, so I don't know what to do
Pedro> about it.
I don't remember that but I am happy to try out patches in the local
setup.
Tom
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state
2025-05-19 13:22 ` [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state Pedro Alves
@ 2026-04-06 18:01 ` Pedro Alves
0 siblings, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2026-04-06 18:01 UTC (permalink / raw)
To: gdb-patches
[-- Attachment #1: Type: text/plain, Size: 1058 bytes --]
On 2025-05-19 14:22, Pedro Alves wrote:
> While working on Windows non-stop support, I ran into a
> very-hard-to-track-down bug.
>
...
> The patch adds getters/setters for both (user) state and
> internal_state, and adds assertions around state transitions, ensuring
> that internal_state doesn't get out of sync with
> thread::have_pending_wait_status().
I've rebased this on current master and I've now merged it, after retesting on native and gdbserver linux.
The rebase only required mechanical changes around thread_info pointer to thread_info references.
> It also adds an assertion to
> clear_proceed_status_thread, making sure that we don't try to proceed
> a thread that is already running. Turns out that catches
> attach_command calling init_wait_for_inferior too late, after
> attaching has already created already-running threads.
I've ended up splitting this part into a separate preparatory patch, to help with
archaeology and bisecting if this ever creates a problem.
I've attached the two patches as they were merged.
Pedro Alves
[-- Attachment #2: 0001-init_wait_for_inferior-doesn-t-imply-proceed.patch --]
[-- Type: text/x-patch, Size: 4713 bytes --]
From fdb1f0881318c83d9db836c9558c33407aca173e Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 19 Feb 2025 14:37:39 +0000
Subject: [PATCH 1/2] init_wait_for_inferior doesn't imply proceed
The next patch adds an assertion to clear_proceed_status_thread,
making sure that we don't try to proceed a thread that is already
running. Turns out that catches attach_command calling
init_wait_for_inferior too late, after attaching has already created
already-running threads. This patch fixes.
However, that alone changes MI output when attaching (as caught by
e.g. gdb.rocm/mi-attach.exp). We'd go from:
-target-attach 2468587
=thread-group-started,id="i1",pid="2468587"
=thread-created,id="1",group-id="i1"
^done
to:
-target-attach 2443288
=thread-group-started,id="i1",pid="2443288"
=thread-created,id="1",group-id="i1"
=thread-created,id="2",group-id="i1"
~"[New LWP 2443317 (id 2)]\n"
^running
*running,thread-id="2"
(gdb)
That change would happen because init_wait_for_inferior calls
clear_proceed_status, which calls notify_about_to_proceed, where we
end up in mi_interp::on_about_to_proceed(), setting the mi_proceeded
flag. If that happens before linux_nat_target::attach is called, then
the set_running calls inside linux_nat_target::attach result in
emiting ^running in MI instead of ^done due to the back-compatibility
hack in mi_on_resume_1.
Now, init_wait_inferior really does not imply we're about to call
proceed. So restore the MI output by adding a new parameter to
clear_proceed_status that lets init_wait_inferior tell
clear_proceed_status to skip notifying the about_to_proceed observers.
I audited all init_wait_inferior calls, and this made sense in all of
them. The places do call proceed after init_wait_inferior, already
explicitly clear_proceed_status after init_wait_inferior. E.g.,
run_command_1.
Tested on x86_64-linux-gnu, native and gdbserver.
Change-Id: I4f5097d68f4694d44e1ae23fea3e9bce45fb078c
commit-id:123f9020
---
gdb/infcmd.c | 8 ++++----
gdb/infrun.c | 7 ++++---
gdb/infrun.h | 5 +++--
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 98b45f884b1..2be2f4376c0 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -2862,6 +2862,10 @@ attach_command (const char *args, int from_tty)
this function should probably be moved into target_pre_inferior. */
target_pre_inferior ();
+ /* Set up execution context to know that we should return from
+ wait_for_inferior as soon as the target reports a stop. */
+ init_wait_for_inferior ();
+
gdb::unique_xmalloc_ptr<char> stripped = strip_bg_char (args, &async_exec);
args = stripped.get ();
@@ -2904,10 +2908,6 @@ attach_command (const char *args, int from_tty)
finished. */
target_terminal::inferior ();
- /* Set up execution context to know that we should return from
- wait_for_inferior as soon as the target reports a stop. */
- init_wait_for_inferior ();
-
inferior->needs_setup = true;
if (target_is_non_stop_p ())
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 2e53d2a6c30..a6b5db3edd5 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3123,7 +3123,7 @@ notify_about_to_proceed ()
}
void
-clear_proceed_status (int step)
+clear_proceed_status (int step, bool about_to_proceed)
{
/* With scheduler-locking replay, stop replaying other threads in the
same process if we're not replaying the selected thread.
@@ -3162,7 +3162,8 @@ clear_proceed_status (int step)
inferior->control.stop_soon = NO_STOP_QUIETLY;
}
- notify_about_to_proceed ();
+ if (about_to_proceed)
+ notify_about_to_proceed ();
}
/* Returns true if TP is still stopped at a breakpoint that needs
@@ -3851,7 +3852,7 @@ init_wait_for_inferior (void)
breakpoint_init_inferior (current_inferior (), inf_starting);
- clear_proceed_status (0);
+ clear_proceed_status (0, false);
nullify_last_target_wait_ptid ();
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 7e2b652e4f1..9eaa5387ef0 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -129,8 +129,9 @@ extern void start_remote (int from_tty);
/* Clear out all variables saying what to do when inferior is
continued or stepped. First do this, then set the ones you want,
then call `proceed'. STEP indicates whether we're preparing for a
- step/stepi command. */
-extern void clear_proceed_status (int step);
+ step/stepi command. Set ABOUT_TO_PROCEED to false if we're not
+ calling `proceeed` yet. */
+extern void clear_proceed_status (int step, bool about_to_proceed = true);
extern void proceed (CORE_ADDR, enum gdb_signal);
base-commit: 73434094ad8a861fe555045804e943d186ff6879
--
2.53.0
[-- Attachment #3: 0002-thread_info-executing-resumed-thread_info-internal_s.patch --]
[-- Type: text/x-patch, Size: 92355 bytes --]
From 2954dd2b734526165b920eb575bcd0fc07e0aa24 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 19 Feb 2025 14:37:39 +0000
Subject: [PATCH 2/2] thread_info::executing+resumed ->
thread_info::internal_state
While working on Windows non-stop support, I ran into a
very-hard-to-track-down bug.
The problem turned out to be that
infrun.c:proceed_resume_thread_checked resumed an already-executing
thread because the thread was marked as "executing=true,
resumed=false", and that function only skips resuming threads that are
marked resumed=true. The consequence was that GDB corrupted the
registers of the Windows DLL loader threads, eventually leading to a
GDB+inferior deadlock.
Originally, the "resumed" flag was only ever set when infrun decided
is was ready to process a thread's pending wait status. infrun has
since evolved to set the resumed flag when we set a thread's executing
flag too. We are not always consistent throughout in guaranteeing
that a thread is marked resumed=true whenever it is marked
executing=true, though. For instance, no target code that supports
non-stop mode (linux-nat, remote, and windows-nat with this series) is
making sure that new threads are marked resumed=true when they are
added to the thread list. They are only marked as {state=running,
executing=true}, the "resumed" flag is not touched.
Making proceed_resume_thread_checked check thr->executing() in
addition to thr->resumed(), feels like papering over a combination of
states that shouldn't happen nowadays.
OTOH, having to have the target backends mark new threads as
resumed=true just feels like too many different states (three) to set:
add_thread (...);
set_running (...);
set_executing (...);
set_resumed (...);
Yuck. I think we can do better.
We really have too many "state tracking" flags in a thread.
Basically:
- whether a thread is "running/stopped/exited" (from the user's
perspective). This is the thread_info::state field.
- whether a thread is "executing" (infrun asked the target to set the
thread executing). This is thread_info::executing().
- whether a thread is "resumed" (infrun wants the thread to be
resumed, but maybe can't yet because the thread has a pending wait
status). This is thread_info::resumed()
"running", "executing", and "resumed" are almost synonyms, so this can
be highly confusing English-wise too.
For "running" vs "executing", in comments, we tipically need to
explain that "running/stopped/exited" is for the user/frontend
perspective, while "executing true/false" is for gdb's internal run
control.
(Also, "executing or not" can also mean something else in GDB's
codebase -- "target has execution" does not mean that threads are
actually running right now -- it's a test for whether we have a live
process vs a core dump!)
One simplification we can do that avoids this running vs executing
ambiguity is to replace the "executing" field with an "internal_state"
field, similar to the thread_info::state field, and make that new
internal_state field reuse the same enum thread_state type that is
used by thread_info::state. Like:
struct thread_info
{
...
/* Frontend/public/external/user view of the thread state. */
enum thread_state m_state = THREAD_STOPPED;
/* The thread's internal state. When the thread is stopped
internally while handling an internal event, like a software
single-step breakpoint, the internal state will be
THREAD_STOPPED, but the external state will still be
THREAD_RUNNING. */
enum thread_state m_internal_state = THREAD_STOPPED;
};
(Assume we'd add state() and internal_state() getters.)
With that, every check for thr->executing() is replaced with a
'thr->internal_state() == THREAD_RUNNING' check, and the code is
clearer by design. There is no confusion between "running" vs
"executing" any more, because they now mean the exact same thing.
Instead, we say e.g., 'thread has (user) state "running", and internal
state "stopped"'. Or simpler, 'thread is running (from the user's
perspective), but internally stopped'. That is after all what we
would way in comments today already.
That still leaves the 'resumed' flag, though. That's the least
obvious one. Turns out we can get rid of it, and make it a new state
tracked by thread_info::internal_state. That is, we make
internal_state have its own enumeration type (decoupled from
thread_info::state's type), and convert the resumed true/false flag to
a new enumerator of this new enumeration. Like so:
enum thread_int_state
{
THREAD_INT_STOPPED,
THREAD_INT_RUNNING,
+ THREAD_INT_RESUMED_PENDING_STATUS,
THREAD_INT_EXITED,
};
That is what this patch does. So in summary, we go from:
thread_info::state {THREAD_STOPPED, THREAD_RUNNING, THREAD_EXITED}
thread_info::executing {false, true}
thread_info::resumed {false, true}
to:
thread_info::state {THREAD_STOPPED, THREAD_RUNNING, THREAD_EXITED}
thread_info::internal_state {THREAD_INT_STOPPED, THREAD_INT_RUNNING,
THREAD_INT_RESUMED_PENDING_STATUS,
THREAD_INT_EXITED}
The patch adds getters/setters for both (user) state and
internal_state, and adds assertions around state transitions, ensuring
that internal_state doesn't get out of sync with
thread::have_pending_wait_status().
The code that adds/removes threads from the proc_target's
resumed_with_pending_wait_status list is all centralized within
thread_info::set_internal_state, when we switch to/from the
resumed-pending-status state. With the assertions in place, it should
be impossible to end up with a THREAD_INT_RUNNING thread with a
pending status.
The thread.c:set_running, thread.c:set_executing, thread.c:set_resumed
global functions are all gone, replaced with new thread.c:set_state
and thread.c:set_internal_state functions.
Tested on x86_64-linux-gnu, native and gdbserver.
Change-Id: I4f5097d68f4694d44e1ae23fea3e9bce45fb078c
commit-id:42ba97d4
---
gdb/aarch64-tdep.c | 2 +-
gdb/amd-dbgapi-target.c | 8 +-
gdb/breakpoint.c | 10 +-
gdb/bsd-uthread.c | 4 +-
gdb/fork-child.c | 4 +-
gdb/frame.c | 4 +-
gdb/gcore.c | 4 +-
gdb/gdbthread.h | 146 +++++++++--------
gdb/i386-tdep.c | 4 +-
gdb/inf-ptrace.c | 2 +-
gdb/infcall.c | 8 +-
gdb/infcmd.c | 31 ++--
gdb/inferior.h | 4 +-
gdb/inflow.c | 2 +-
gdb/infrun.c | 154 +++++++++---------
gdb/infrun.h | 7 +-
gdb/linux-fork.c | 9 +-
gdb/linux-nat.c | 8 +-
gdb/linux-thread-db.c | 4 +-
gdb/mi/mi-cmd-var.c | 4 +-
gdb/mi/mi-interp.c | 2 +-
gdb/mi/mi-main.c | 8 +-
gdb/process-stratum-target.c | 6 +-
gdb/python/py-infthread.c | 6 +-
gdb/record-btrace.c | 26 +--
gdb/record-full.c | 6 +-
gdb/regcache.c | 2 +-
gdb/remote.c | 76 +++++----
gdb/sol-thread.c | 4 +-
gdb/target.c | 15 +-
gdb/thread-iter.h | 2 +-
gdb/thread.c | 299 ++++++++++++++++++++---------------
gdb/top.c | 2 +-
33 files changed, 476 insertions(+), 397 deletions(-)
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index ce00f518299..4befaa2720d 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -4355,7 +4355,7 @@ aarch64_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
/* If the thread is running, we will not be able to fetch the mask
registers. */
- if (thread != nullptr && thread->state != THREAD_RUNNING)
+ if (thread != nullptr && thread->state () != THREAD_RUNNING)
{
/* Otherwise, fetch the register cache and the masks. */
struct regcache *regs
diff --git a/gdb/amd-dbgapi-target.c b/gdb/amd-dbgapi-target.c
index 655dddd36ed..811e22182c6 100644
--- a/gdb/amd-dbgapi-target.c
+++ b/gdb/amd-dbgapi-target.c
@@ -1160,7 +1160,7 @@ amd_dbgapi_target::stop (ptid_t ptid)
/* Use the threads_safe iterator since stop_one_thread may delete the
thread if it has exited. */
for (auto &thread : inf->threads_safe ())
- if (thread.state != THREAD_EXITED && thread.ptid.matches (ptid)
+ if (thread.state () != THREAD_EXITED && thread.ptid.matches (ptid)
&& ptid_is_gpu (thread.ptid))
stop_one_thread (&thread);
}
@@ -1388,8 +1388,8 @@ add_gpu_thread (inferior *inf, ptid_t wave_ptid)
/* Create new GPU threads silently to avoid spamming the terminal
with thousands of "[New Thread ...]" messages. */
thread_info *thread = add_thread_silent (proc_target, wave_ptid);
- set_running (proc_target, wave_ptid, true);
- set_executing (proc_target, wave_ptid, true);
+ set_state (proc_target, wave_ptid, THREAD_RUNNING);
+ set_internal_state (proc_target, wave_ptid, THREAD_INT_RUNNING);
return thread;
}
@@ -2112,7 +2112,7 @@ amd_dbgapi_target::update_thread_list ()
which does not have a corresponding wave_id represents a wave which
is gone at this point and should be deleted. */
for (thread_info &tp : inf->threads_safe ())
- if (ptid_is_gpu (tp.ptid) && tp.state != THREAD_EXITED)
+ if (ptid_is_gpu (tp.ptid) && tp.state () != THREAD_EXITED)
{
auto it = threads.find (tp.ptid.tid ());
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index d7be1b44229..31811e3a553 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -620,9 +620,9 @@ breakpoints_should_be_inserted_now (void)
return 1;
/* Don't remove breakpoints yet if, even though all threads are
- stopped, we still have events to process. */
+ stopped, some still have pending events to process. */
for (thread_info &tp : all_non_exited_threads ())
- if (tp.resumed () && tp.has_pending_waitstatus ())
+ if (tp.internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
return 1;
}
return 0;
@@ -2050,7 +2050,8 @@ watchpoint_in_thread_scope (struct watchpoint *b)
return (b->pspace == current_program_space
&& (b->watchpoint_thread == null_ptid
|| (inferior_ptid == b->watchpoint_thread
- && !inferior_thread ()->executing ())));
+ && (inferior_thread ()->internal_state ()
+ != THREAD_INT_RUNNING))));
}
/* Set watchpoint B to disp_del_at_next_stop, even including its possible
@@ -4991,7 +4992,8 @@ get_bpstat_thread ()
return NULL;
thread_info *tp = inferior_thread ();
- if (tp->state == THREAD_EXITED || tp->executing ())
+ if (tp->internal_state () == THREAD_INT_EXITED
+ || tp->internal_state () == THREAD_INT_RUNNING)
return NULL;
return tp;
}
diff --git a/gdb/bsd-uthread.c b/gdb/bsd-uthread.c
index e4c5c9ee3e3..0aa8ebda558 100644
--- a/gdb/bsd-uthread.c
+++ b/gdb/bsd-uthread.c
@@ -413,7 +413,7 @@ bsd_uthread_target::wait (ptid_t ptid, struct target_waitstatus *status,
/* Don't let the core see a ptid without a corresponding thread. */
thread_info *thread = beneath->find_thread (ptid);
- if (thread == NULL || thread->state == THREAD_EXITED)
+ if (thread == NULL || thread->state () == THREAD_EXITED)
add_thread (beneath, ptid);
return ptid;
@@ -464,7 +464,7 @@ bsd_uthread_target::update_thread_list ()
process_stratum_target *proc_target
= as_process_stratum_target (this->beneath ());
thread_info *thread = proc_target->find_thread (ptid);
- if (thread == nullptr || thread->state == THREAD_EXITED)
+ if (thread == nullptr || thread->state () == THREAD_EXITED)
{
/* If INFERIOR_PTID doesn't have a tid member yet, then ptid
is still the initial thread of the process. Notify GDB
diff --git a/gdb/fork-child.c b/gdb/fork-child.c
index bd75a9593aa..a9d0bffb8e1 100644
--- a/gdb/fork-child.c
+++ b/gdb/fork-child.c
@@ -130,8 +130,8 @@ gdb_startup_inferior (pid_t pid, int num_traps)
ptid_t ptid = startup_inferior (proc_target, pid, num_traps, NULL, NULL);
- /* Mark all threads non-executing. */
- set_executing (proc_target, ptid, false);
+ /* Mark all threads internally stopped. */
+ set_internal_state (proc_target, ptid, THREAD_INT_STOPPED);
return ptid;
}
diff --git a/gdb/frame.c b/gdb/frame.c
index cefaa87fec3..7a83f5e61c0 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1940,11 +1940,11 @@ has_stack_frames ()
thread_info *tp = inferior_thread ();
/* Don't try to read from a dead thread. */
- if (tp->state == THREAD_EXITED)
+ if (tp->internal_state () == THREAD_INT_EXITED)
return false;
/* ... or from a spinning thread. */
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
return false;
}
diff --git a/gdb/gcore.c b/gdb/gcore.c
index 48e431ed8f9..ac3b44eb812 100644
--- a/gdb/gcore.c
+++ b/gdb/gcore.c
@@ -894,7 +894,7 @@ thread_info *
gcore_find_signalled_thread ()
{
thread_info *curr_thr = inferior_thread ();
- if (curr_thr->state != THREAD_EXITED
+ if (curr_thr->state () != THREAD_EXITED
&& curr_thr->stop_signal () != GDB_SIGNAL_0)
return curr_thr;
@@ -903,7 +903,7 @@ gcore_find_signalled_thread ()
return &thr;
/* Default to the current thread, unless it has exited. */
- if (curr_thr->state != THREAD_EXITED)
+ if (curr_thr->state () != THREAD_EXITED)
return curr_thr;
return nullptr;
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 7644924b614..d64bf6e5a01 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -51,8 +51,8 @@ extern bool debug_threads;
#define threads_debug_printf(fmt, ...) \
debug_prefixed_printf_cond (debug_threads, "threads", fmt, ##__VA_ARGS__)
-/* Frontend view of the thread state. Possible extensions: stepping,
- finishing, until(ling),...
+/* User/frontend view of the thread state. Possible extensions:
+ stepping, finishing, until(ling),...
NOTE: Since the thread state is not a boolean, most times, you do
not want to check it with negation. If you really want to check if
@@ -81,6 +81,34 @@ enum thread_state
THREAD_EXITED,
};
+/* Internal view of the thread's running state. When a thread is
+ running from the user's perspective, it will still occasionally
+ stop, due to breakpoint hits, single-stepping, etc. Often those
+ stops are not meant to be user-visible. In such situations, the
+ user state will be THREAD_RUNNING, while the internal state
+ transitions between stopped, running, etc. */
+
+enum thread_int_state
+{
+ /* The thread is stopped. If the thread has a pending wait status,
+ we should not process it until we try to let the thread run, in
+ which case we switch the thread to
+ THREAD_INT_RESUMED_PENDING_STATUS state. */
+ THREAD_INT_STOPPED,
+
+ /* The thread is running. */
+ THREAD_INT_RUNNING,
+
+ /* infrun wants the thread to be resumed, but didn't set it running
+ yet, because the thread has a pending wait status to process. We
+ shouldn't let the thread really run until that wait status has
+ been processed. */
+ THREAD_INT_RESUMED_PENDING_STATUS,
+
+ /* The thread is listed, but known to have exited. */
+ THREAD_INT_EXITED,
+};
+
/* STEP_OVER_ALL means step over all subroutine calls.
STEP_OVER_UNDEBUGGABLE means step over calls to undebuggable functions.
STEP_OVER_NONE means don't step over any subroutine calls. */
@@ -218,10 +246,10 @@ struct thread_suspend_state
last stopped, a pending breakpoint waitstatus is discarded.
- If the thread is running, then this field has its value removed by
- calling stop_pc.reset() (see thread_info::set_executing()).
+ calling stop_pc.reset() (see thread_info::set_internal_state()).
Attempting to read a std::optional with no value is undefined
behavior and will trigger an assertion error when _GLIBCXX_DEBUG is
- defined, which should make error easier to track down. */
+ defined, which should make errors easier to track down. */
std::optional<CORE_ADDR> stop_pc;
};
@@ -263,8 +291,23 @@ class thread_info : public intrusive_list_node<thread_info>,
bool deletable () const;
- /* Mark this thread as running and notify observers. */
- void set_running (bool running);
+ /* Get the thread's (user-visible) state. */
+ thread_state state () const { return m_state; }
+
+ /* Set this thread's (user-visible) state. If the thread is set
+ running, notify observers. */
+ void set_state (thread_state state) { set_state (state, false); }
+
+ /* Get the thread's internal state. */
+ thread_int_state internal_state () const { return m_internal_state; }
+
+ /* Set the thread's internal state from STATE. If the state
+ switches to THREAD_INT_RUNNING, also clears the thread's stop_pc.
+ The thread may also be added to (when switching to
+ THREAD_INT_RESUMED_PENDING_STATUS), or removed from (when
+ switching from THREAD_INT_RESUMED_PENDING_STATUS), the list of
+ threads with a pending wait status. */
+ void set_internal_state (thread_int_state state);
ptid_t ptid; /* "Actual process id";
In fact, this may be overloaded with
@@ -326,28 +369,6 @@ class thread_info : public intrusive_list_node<thread_info>,
m_name = std::move (name);
}
- bool executing () const
- { return m_executing; }
-
- /* Set the thread's 'm_executing' field from EXECUTING, and if EXECUTING
- is true also clears the thread's stop_pc. */
- void set_executing (bool executing);
-
- bool resumed () const
- { return m_resumed; }
-
- /* Set the thread's 'm_resumed' field from RESUMED. The thread may also
- be added to (when RESUMED is true), or removed from (when RESUMED is
- false), the list of threads with a pending wait status. */
- void set_resumed (bool resumed);
-
- /* Frontend view of the thread state. Note that the THREAD_RUNNING/
- THREAD_STOPPED states are different from EXECUTING. When the
- thread is stopped internally while handling an internal event,
- like a software single-step breakpoint, EXECUTING will be false,
- but STATE will still be THREAD_RUNNING. */
- enum thread_state state = THREAD_STOPPED;
-
/* State of GDB control of inferior thread execution.
See `struct thread_control_state'. */
thread_control_state control;
@@ -573,20 +594,22 @@ class thread_info : public intrusive_list_node<thread_info>,
displaced_step_thread_state displaced_step_state;
private:
- /* True if this thread is resumed from infrun's perspective.
- Note that a thread can be marked both as not-executing and
- resumed at the same time. This happens if we try to resume a
- thread that has a wait status pending. We shouldn't let the
- thread really run until that wait status has been processed, but
- we should not process that wait status if we didn't try to let
- the thread run. */
- bool m_resumed = false;
-
- /* True means the thread is executing. Note: this is different
- from saying that there is an active target and we are stopped at
- a breakpoint, for instance. This is a real indicator whether the
- thread is off and running. */
- bool m_executing = false;
+ /* Set this thread's (user-visible) state. If the thread is set
+ running, notify observers, unless SUPPRESS_NOTIFICATION is true.
+ Returns the thread's previous state. */
+ thread_state set_state (thread_state state, bool suppress_notification);
+ friend void set_state (process_stratum_target *targ,
+ ptid_t ptid,
+ thread_state state);
+ friend void finish_thread_state (process_stratum_target *targ,
+ ptid_t ptid);
+
+ /* User view of the thread's stopped/running/exited state. */
+ enum thread_state m_state = THREAD_STOPPED;
+
+ /* The thread's internal state. See definition of
+ thread_int_state. */
+ enum thread_int_state m_internal_state = THREAD_INT_STOPPED;
/* State of inferior thread to restore after GDB is done with an inferior
call. See `struct thread_suspend_state'. */
@@ -818,17 +841,15 @@ extern void switch_to_no_thread ();
/* Switch from one thread to another. Does not read registers. */
extern void switch_to_thread_no_regs (struct thread_info *thread);
-/* Marks or clears thread(s) PTID of TARG as resumed. If PTID is
- MINUS_ONE_PTID, applies to all threads of TARG. If
- ptid_is_pid(PTID) is true, applies to all threads of the process
- pointed at by {TARG,PTID}. */
-extern void set_resumed (process_stratum_target *targ,
- ptid_t ptid, bool resumed);
+/* Marks thread PTID of TARG with user state STATE. If PTID is
+ minus_one_ptid, marks all threads of TARG. */
+extern void set_state (process_stratum_target *targ,
+ ptid_t ptid, thread_state state);
-/* Marks thread PTID of TARG as running, or as stopped. If PTID is
+/* Marks thread PTID of TARG with internal state STATE. If PTID is
minus_one_ptid, marks all threads of TARG. */
-extern void set_running (process_stratum_target *targ,
- ptid_t ptid, bool running);
+extern void set_internal_state (process_stratum_target *targ,
+ ptid_t ptid, thread_int_state state);
/* Marks or clears thread(s) PTID of TARG as having been requested to
stop. If PTID is MINUS_ONE_PTID, applies to all threads of TARG.
@@ -838,25 +859,19 @@ extern void set_running (process_stratum_target *targ,
extern void set_stop_requested (process_stratum_target *targ,
ptid_t ptid, bool stop);
-/* Marks thread PTID of TARG as executing, or not. If PTID is
- minus_one_ptid, marks all threads of TARG.
-
- Note that this is different from the running state. See the
- description of state and executing fields of struct
- thread_info. */
-extern void set_executing (process_stratum_target *targ,
- ptid_t ptid, bool executing);
-
/* True if any (known or unknown) thread of TARG is or may be
executing. */
extern bool threads_are_executing (process_stratum_target *targ);
-/* Merge the executing property of thread PTID of TARG over to its
- thread state property (frontend running/stopped view).
+/* Propagate the internal thread state of thread PTID of TARG over to
+ its (user) thread state.
- "not executing" -> "stopped"
- "executing" -> "running"
- "exited" -> "exited"
+ user <- internal
+ ------- ------------------------
+ stopped <- stopped
+ running <- running
+ running <- continued-pending-status
+ exited <- exited
If PTID is minus_one_ptid, go over all threads of TARG.
@@ -1066,5 +1081,6 @@ extern void thread_try_catch_cmd (thread_info *thr,
/* Return a string representation of STATE. */
extern const char *thread_state_string (enum thread_state state);
+extern const char *thread_int_state_string (enum thread_int_state state);
#endif /* GDB_GDBTHREAD_H */
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index 93357b41b10..fa935b5fcdb 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -4765,8 +4765,8 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
/* Since we are reading pseudo registers, we need to tell GDB that it is
safe to do so, by saying we aren't _really_ running the inferior right
now. */
- SCOPE_EXIT { inferior_thread ()->set_executing (true); };
- inferior_thread () -> set_executing (false);
+ SCOPE_EXIT { inferior_thread ()->set_internal_state (THREAD_INT_RUNNING); };
+ inferior_thread ()->set_internal_state (THREAD_INT_STOPPED);
uint8_t opcode;
if (record_read_memory (gdbarch, ir->addr, &opcode, 1))
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index 98cc1094e24..5363717208e 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -175,7 +175,7 @@ inf_ptrace_target::attach (const char *args, int from_tty)
/* Don't consider the thread stopped until we've processed its
initial SIGSTOP stop. */
- set_executing (this, thr->ptid, true);
+ set_internal_state (this, thr->ptid, THREAD_INT_RUNNING);
unpusher.release ();
}
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 941b0a95c5b..f16a1f376b3 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -807,7 +807,7 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
struct gdb_exception caught_error;
ptid_t call_thread_ptid = call_thread->ptid;
- int was_running = call_thread->state == THREAD_RUNNING;
+ int was_running = call_thread->state () == THREAD_RUNNING;
*timed_out_p = false;
infcall_debug_printf ("call function at %s in thread %s, was_running = %d",
@@ -946,7 +946,7 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
of error out of resume()), then we wouldn't need this. */
if (caught_error.reason < 0)
{
- if (call_thread->state != THREAD_EXITED)
+ if (call_thread->state () != THREAD_EXITED)
breakpoint_auto_delete (call_thread->control.stop_bpstat);
}
@@ -1589,7 +1589,7 @@ call_function_by_hand_dummy (struct value *function,
infcall_debug_printf ("after inferior call, exception (%d): %s",
e.reason, e.what ());
infcall_debug_printf ("after inferior call, thread state is: %s",
- thread_state_string (call_thread->state));
+ thread_state_string (call_thread->state ()));
gdb::observers::inferior_call_post.notify (call_thread_ptid, funaddr);
@@ -1600,7 +1600,7 @@ call_function_by_hand_dummy (struct value *function,
threads appear after GDB has reported a stop. */
update_thread_list ();
- if (call_thread->state != THREAD_EXITED)
+ if (call_thread->state () != THREAD_EXITED)
{
/* The FSM should still be the same. */
gdb_assert (call_thread->thread_fsm () == sm);
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 2be2f4376c0..b51d2e64f80 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -692,12 +692,12 @@ proceed_thread_callback (struct thread_info *thread)
into a single target `resume_all' request, because some threads
may be stopped in internal breakpoints/events, or stopped waiting
for its turn in the displaced stepping queue (that is, they are
- running && !executing). The target side has no idea about why
- the thread is stopped, so a `resume_all' command would resume too
- much. If/when GDB gains a way to tell the target `hold this
- thread stopped until I say otherwise', then we can optimize
- this. */
- if (thread->state != THREAD_STOPPED)
+ running from the user's perspective but internally stopped). The
+ target side has no idea about why the thread is stopped, so a
+ `resume_all' command would resume too much. If/when GDB gains a
+ way to tell the target `hold this thread stopped until I say
+ otherwise', then we can optimize this. */
+ if (thread->state () != THREAD_STOPPED)
return false;
if (!thread->inf->has_execution ())
@@ -713,7 +713,7 @@ static void
ensure_valid_thread (void)
{
if (inferior_ptid == null_ptid
- || inferior_thread ()->state == THREAD_EXITED)
+ || inferior_thread ()->state () == THREAD_EXITED)
error (_("Cannot execute this command without a live selected thread."));
}
@@ -742,7 +742,7 @@ error_is_running (void)
static void
ensure_not_running (void)
{
- if (inferior_thread ()->state == THREAD_RUNNING)
+ if (inferior_thread ()->state () == THREAD_RUNNING)
error_is_running ();
}
@@ -1100,7 +1100,8 @@ prepare_one_step (thread_info *tp, struct step_command_fsm *sm)
/* Pretend that we've ran. */
resume_ptid = user_visible_resume_ptid (1);
- set_running (tp->inf->process_target (), resume_ptid, true);
+ set_state (tp->inf->process_target (), resume_ptid,
+ THREAD_RUNNING);
step_into_inline_frame (tp);
@@ -2078,12 +2079,12 @@ info_program_command (const char *args, int from_tty)
print_thread_id (tp),
target_pid_to_str (tp->ptid).c_str ());
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
{
gdb_printf (_("Selected thread has exited.\n"));
return;
}
- else if (tp->state == THREAD_RUNNING)
+ else if (tp->state () == THREAD_RUNNING)
{
gdb_printf (_("Selected thread is running.\n"));
return;
@@ -2105,13 +2106,13 @@ info_program_command (const char *args, int from_tty)
print_thread_id (tp),
target_pid_to_str (tp->ptid).c_str ());
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
{
gdb_printf (_("Thread has since exited.\n"));
return;
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
{
gdb_printf (_("Thread is now running.\n"));
return;
@@ -2707,7 +2708,7 @@ proceed_after_attach (inferior *inf)
scoped_restore_current_thread restore_thread;
for (thread_info &thread : inf->non_exited_threads ())
- if (!thread.executing ()
+ if (thread.internal_state () != THREAD_INT_RUNNING
&& !thread.stop_requested
&& thread.stop_signal () == GDB_SIGNAL_0)
{
@@ -2990,7 +2991,7 @@ notice_new_inferior (thread_info *thr, bool leave_running, int from_tty)
/* When we "notice" a new inferior we need to do all the things we
would normally do if we had just attached to it. */
- if (thr->executing ())
+ if (thr->internal_state () == THREAD_INT_RUNNING)
{
struct inferior *inferior = current_inferior ();
diff --git a/gdb/inferior.h b/gdb/inferior.h
index fbf9765fb0e..9c031035a23 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -200,8 +200,8 @@ extern void child_interrupt (struct target_ops *self);
/* From fork-child.c */
/* Helper function to call STARTUP_INFERIOR with PID and NUM_TRAPS.
- This function already calls set_executing. Return the ptid_t from
- STARTUP_INFERIOR. */
+ This function already sets the threads' internal state to
+ THREAD_STOPPED. Return the ptid_t from STARTUP_INFERIOR. */
extern ptid_t gdb_startup_inferior (pid_t pid, int num_traps);
/* From infcmd.c */
diff --git a/gdb/inflow.c b/gdb/inflow.c
index ff118b558ca..c40ad2cf529 100644
--- a/gdb/inflow.c
+++ b/gdb/inflow.c
@@ -552,7 +552,7 @@ child_interrupt (struct target_ops *self)
thread_info *resumed = NULL;
for (thread_info &thr : all_non_exited_threads ())
{
- if (thr.executing ())
+ if (thr.internal_state () == THREAD_INT_RUNNING)
{
resumed = &thr;
break;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index a6b5db3edd5..2c9a27325af 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -1007,14 +1007,14 @@ follow_inferior_reset_breakpoints (void)
insert_breakpoints ();
}
-/* The child has exited or execed: resume THREAD, a thread of the parent,
- if it was meant to be executing. */
+/* The child has exited or execed: resume THREAD, a thread of the
+ parent, if it was meant to be running. */
static void
proceed_after_vfork_done (thread_info *thread)
{
- if (thread->state == THREAD_RUNNING
- && !thread->executing ()
+ if (thread->state () == THREAD_RUNNING
+ && thread->internal_state () != THREAD_INT_RUNNING
&& !thread->stop_requested
&& thread->stop_signal () == GDB_SIGNAL_0)
{
@@ -2247,15 +2247,14 @@ start_step_over (void)
}
if (tp.control.trap_expected
- || tp.resumed ()
- || tp.executing ())
+ || tp.internal_state () == THREAD_INT_RESUMED_PENDING_STATUS
+ || tp.internal_state () == THREAD_INT_RUNNING)
{
internal_error ("[%s] has inconsistent state: "
- "trap_expected=%d, resumed=%d, executing=%d\n",
+ "trap_expected=%d, internal_state=%s\n",
tp.ptid.to_string ().c_str (),
tp.control.trap_expected,
- tp.resumed (),
- tp.executing ());
+ thread_int_state_string (tp.internal_state ()));
}
infrun_debug_printf ("resuming [%s] for step-over",
@@ -2279,7 +2278,7 @@ start_step_over (void)
/* If the thread's step over could not be initiated because no buffers
were available, it was re-added to the global step over chain. */
- if (tp.resumed ())
+ if (tp.internal_state () != THREAD_INT_STOPPED)
{
infrun_debug_printf ("[%s] was resumed.",
tp.ptid.to_string ().c_str ());
@@ -2679,7 +2678,7 @@ resume_1 (enum gdb_signal sig)
tp->control.currently_stepping);
tp->inf->process_target ()->threads_executing = true;
- tp->set_resumed (true);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
/* FIXME: What should we do if we are supposed to resume this
thread with a signal? Maybe we should maintain a queue of
@@ -2805,7 +2804,6 @@ resume_1 (enum gdb_signal sig)
resume_ptid = internal_resume_ptid (user_step);
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
- tp->set_resumed (true);
return;
}
}
@@ -2997,7 +2995,6 @@ resume_1 (enum gdb_signal sig)
}
do_target_resume (resume_ptid, step, sig);
- tp->set_resumed (true);
}
/* Resume the inferior. SIG is the signal to give the inferior
@@ -3061,6 +3058,7 @@ static void
clear_proceed_status_thread (struct thread_info *tp)
{
infrun_debug_printf ("%s", tp->ptid.to_string ().c_str ());
+ gdb_assert (tp->internal_state () != THREAD_INT_RUNNING);
/* If we're starting a new sequence, then the previous finished
single-step is no longer relevant. */
@@ -3072,6 +3070,7 @@ clear_proceed_status_thread (struct thread_info *tp)
"Discarding.",
tp->ptid.to_string ().c_str ());
+ tp->set_internal_state (THREAD_INT_STOPPED);
tp->clear_pending_waitstatus ();
tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
}
@@ -3144,7 +3143,8 @@ clear_proceed_status (int step, bool about_to_proceed)
/* In all-stop mode, delete the per-thread status of all threads
we're about to resume, implicitly and explicitly. */
for (thread_info &tp : all_non_exited_threads (resume_target, resume_ptid))
- clear_proceed_status_thread (&tp);
+ if (tp.internal_state () != THREAD_INT_RUNNING)
+ clear_proceed_status_thread (&tp);
}
if (inferior_ptid != null_ptid)
@@ -3504,6 +3504,8 @@ check_multi_target_resumption (process_stratum_target *resume_target)
static void
proceed_resume_thread_checked (thread_info *tp)
{
+ gdb_assert (tp->internal_state () != THREAD_INT_EXITED);
+
if (!tp->inf->has_execution ())
{
infrun_debug_printf ("[%s] target has no execution",
@@ -3511,11 +3513,10 @@ proceed_resume_thread_checked (thread_info *tp)
return;
}
- if (tp->resumed ())
+ if (tp->internal_state () != THREAD_INT_STOPPED)
{
infrun_debug_printf ("[%s] resumed",
tp->ptid.to_string ().c_str ());
- gdb_assert (tp->executing () || tp->has_pending_waitstatus ());
return;
}
@@ -3691,7 +3692,7 @@ proceed (CORE_ADDR addr, enum gdb_signal siggnal)
inferior function, as in that case we pretend the inferior
doesn't run at all. */
if (!cur_thr->control.in_infcall)
- set_running (resume_target, resume_ptid, true);
+ set_state (resume_target, resume_ptid, THREAD_RUNNING);
infrun_debug_printf ("addr=%s, signal=%s, resume_ptid=%s",
paddress (gdbarch, addr),
@@ -3892,9 +3893,9 @@ infrun_thread_stop_requested (ptid_t ptid)
for reporting the stop now. */
for (thread_info &tp : all_threads (curr_target, ptid))
{
- if (tp.state != THREAD_RUNNING)
+ if (tp.state () != THREAD_RUNNING)
continue;
- if (tp.executing ())
+ if (tp.internal_state () == THREAD_INT_RUNNING)
continue;
/* Remove matching threads from the step-over queue, so
@@ -3925,10 +3926,10 @@ infrun_thread_stop_requested (ptid_t ptid)
if (step_over_info_valid_p ())
continue;
- /* Otherwise we can process the (new) pending event now. Set
- it so this pending event is considered by
+ /* Otherwise we can process the (new) pending event now. Switch
+ state so this pending event is considered by
do_target_wait. */
- tp.set_resumed (true);
+ tp.set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
}
}
@@ -4034,7 +4035,7 @@ random_pending_event_thread (inferior *inf, ptid_t waiton_ptid)
}
infrun_debug_printf ("Found %s.", thread->ptid.to_string ().c_str ());
- gdb_assert (thread->resumed ());
+ gdb_assert (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS);
gdb_assert (thread->has_pending_waitstatus ());
return thread;
@@ -4147,6 +4148,7 @@ do_target_wait_1 (inferior *inf, ptid_t ptid,
tp->set_stop_reason (TARGET_STOPPED_BY_NO_REASON);
*status = tp->pending_waitstatus ();
+ tp->set_internal_state (THREAD_INT_STOPPED);
tp->clear_pending_waitstatus ();
/* Wake up the event loop again, until all pending events are
@@ -4345,7 +4347,7 @@ prepare_for_detach (void)
{
if (thr.displaced_step_state.in_progress ())
{
- if (thr.executing ())
+ if (thr.internal_state () == THREAD_INT_RUNNING)
{
if (!thr.stop_requested)
{
@@ -4353,8 +4355,6 @@ prepare_for_detach (void)
thr.stop_requested = true;
}
}
- else
- thr.set_resumed (false);
}
}
@@ -4488,7 +4488,7 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
for (thread_info &thr : all_threads_safe ())
{
- if (thr.state == THREAD_EXITED)
+ if (thr.state () == THREAD_EXITED)
continue;
if (&thr == ecs->event_thread)
@@ -4824,7 +4824,7 @@ fetch_inferior_event ()
if (cmd_done
&& exec_done_display_p
&& (inferior_ptid == null_ptid
- || inferior_thread ()->state != THREAD_RUNNING))
+ || inferior_thread ()->state () != THREAD_RUNNING))
gdb_printf (_("completed.\n"));
}
@@ -5387,14 +5387,14 @@ save_waitstatus (struct thread_info *tp, const target_waitstatus &ws)
}
}
-/* Mark the non-executing threads accordingly. In all-stop, all
+/* Mark the internally stopped threads accordingly. In all-stop, all
threads of all processes are stopped when we get any event
reported. In non-stop mode, only the event thread stops. */
static void
-mark_non_executing_threads (process_stratum_target *target,
- ptid_t event_ptid,
- const target_waitstatus &ws)
+mark_internally_stopped_threads (process_stratum_target *target,
+ ptid_t event_ptid,
+ const target_waitstatus &ws)
{
ptid_t mark_ptid;
@@ -5414,18 +5414,15 @@ mark_non_executing_threads (process_stratum_target *target,
target_mourn_inferior, by associating the same
inferior/thread to another fork. We haven't mourned yet at
this point, but we must mark any threads left in the
- process as not-executing so that finish_thread_state marks
- them stopped (in the user's perspective) if/when we present
+ process as internally stopped so that finish_thread_state marks
+ them stopped in the user's perspective if/when we present
the stop to the user. */
mark_ptid = ptid_t (event_ptid.pid ());
}
else
mark_ptid = event_ptid;
- set_executing (target, mark_ptid, false);
-
- /* Likewise the resumed flag. */
- set_resumed (target, mark_ptid, false);
+ set_internal_state (target, mark_ptid, THREAD_INT_STOPPED);
}
/* Handle one event after stopping threads. If the eventing thread
@@ -5491,11 +5488,11 @@ handle_one (const wait_one_event &event)
if (t != nullptr)
{
- /* Set the threads as non-executing to avoid
- another stop attempt on them. */
+ /* Set the threads as internally stopped to avoid another
+ stop attempt on them. */
switch_to_thread_no_regs (t);
- mark_non_executing_threads (event.target, event.ptid,
- event.ws);
+ mark_internally_stopped_threads (event.target, event.ptid,
+ event.ws);
save_waitstatus (t, event.ws);
t->stop_requested = false;
@@ -5517,8 +5514,7 @@ handle_one (const wait_one_event &event)
t = add_thread (event.target, event.ptid);
t->stop_requested = false;
- t->set_executing (false);
- t->set_resumed (false);
+ t->set_internal_state (THREAD_INT_STOPPED);
t->control.may_range_step = 0;
/* This may be the first time we see the inferior report
@@ -5749,7 +5745,7 @@ stop_all_threads (const char *reason, inferior *inf)
if (!target_is_non_stop_p ())
continue;
- if (t.executing ())
+ if (t.internal_state () == THREAD_INT_RUNNING)
{
/* If already stopping, don't request a stop again.
We just haven't seen the notification yet. */
@@ -5776,7 +5772,7 @@ stop_all_threads (const char *reason, inferior *inf)
/* The thread may be not executing, but still be
resumed with a pending status to process. */
- t.set_resumed (false);
+ t.set_internal_state (THREAD_INT_STOPPED);
}
}
@@ -5892,7 +5888,7 @@ handle_no_resumed (struct execution_control_state *ecs)
for (thread_info &thread : all_non_exited_threads ())
{
- if (swap_terminal && thread.executing ())
+ if (swap_terminal && thread.internal_state () == THREAD_INT_RUNNING)
{
if (thread.inf != curr_inf)
{
@@ -5904,7 +5900,7 @@ handle_no_resumed (struct execution_control_state *ecs)
swap_terminal = false;
}
- if (!ignore_event && thread.resumed ())
+ if (!ignore_event && thread.internal_state () != THREAD_INT_STOPPED)
{
/* Either there were no unwaited-for children left in the
target at some point, but there are now, or some target
@@ -6176,7 +6172,7 @@ handle_inferior_event (struct execution_control_state *ecs)
}
}
- mark_non_executing_threads (ecs->target, ecs->ptid, ecs->ws);
+ mark_internally_stopped_threads (ecs->target, ecs->ptid, ecs->ws);
switch (ecs->ws.kind ())
{
@@ -6440,7 +6436,7 @@ handle_inferior_event (struct execution_control_state *ecs)
/* If not resuming the parent, mark it stopped. */
if (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
&& follow_child && !detach_fork && !non_stop && !sched_multi)
- parent->set_running (false);
+ parent->set_state (THREAD_STOPPED);
/* If resuming the child, mark it running. */
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
@@ -6448,7 +6444,7 @@ handle_inferior_event (struct execution_control_state *ecs)
|| (ecs->ws.kind () != TARGET_WAITKIND_THREAD_CLONED
&& (follow_child
|| (!detach_fork && (non_stop || sched_multi)))))
- child->set_running (true);
+ child->set_state (THREAD_RUNNING);
/* In non-stop mode, also resume the other branch. */
if ((ecs->ws.kind () == TARGET_WAITKIND_THREAD_CLONED
@@ -6640,18 +6636,17 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
continue;
}
- if (!(tp.state == THREAD_RUNNING || tp.control.in_infcall))
+ if (!(tp.state () == THREAD_RUNNING || tp.control.in_infcall))
{
infrun_debug_printf ("restart threads: [%s] not meant to be running",
tp.ptid.to_string ().c_str ());
continue;
}
- if (tp.resumed ())
+ if (tp.internal_state () != THREAD_INT_STOPPED)
{
- infrun_debug_printf ("restart threads: [%s] resumed",
+ infrun_debug_printf ("restart threads: [%s] already resumed",
tp.ptid.to_string ().c_str ());
- gdb_assert (tp.executing () || tp.has_pending_waitstatus ());
continue;
}
@@ -6659,7 +6654,6 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
{
infrun_debug_printf ("restart threads: [%s] needs step-over",
tp.ptid.to_string ().c_str ());
- gdb_assert (!tp.resumed ());
continue;
}
@@ -6668,7 +6662,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
{
infrun_debug_printf ("restart threads: [%s] has pending status",
tp.ptid.to_string ().c_str ());
- tp.set_resumed (true);
+ tp.set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
continue;
}
@@ -6707,7 +6701,7 @@ restart_threads (struct thread_info *event_thread, inferior *inf)
static bool
resumed_thread_with_pending_status (struct thread_info *tp)
{
- return tp->resumed () && tp->has_pending_waitstatus ();
+ return tp->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS;
}
/* Called when we get an event that may finish an in-line or
@@ -6796,12 +6790,12 @@ finish_step_over (struct execution_control_state *ecs)
/* Record the event thread's event for later. */
save_waitstatus (tp, ecs->ws);
- /* This was cleared early, by handle_inferior_event. Set it
+ /* The internal state was reset to stopped early, by
+ handle_inferior_event. Switch to resumed-pending-status
so this pending event is considered by
do_target_wait. */
- tp->set_resumed (true);
-
- gdb_assert (!tp->executing ());
+ gdb_assert (tp->internal_state () == THREAD_INT_STOPPED);
+ tp->set_internal_state (THREAD_INT_RESUMED_PENDING_STATUS);
regcache = get_thread_regcache (tp);
tp->set_stop_pc (regcache_read_pc (regcache));
@@ -8464,7 +8458,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
for (thread_info &tp : all_threads_safe ())
{
- if (tp.state != THREAD_RUNNING)
+ if (tp.state () == THREAD_EXITED)
continue;
if (tp.has_pending_waitstatus ())
@@ -8488,7 +8482,7 @@ restart_stepped_thread (process_stratum_target *resume_target,
for (thread_info &tp : all_threads_safe ())
{
- if (tp.state != THREAD_RUNNING)
+ if (tp.state () == THREAD_EXITED)
continue;
if (tp.has_pending_waitstatus ())
@@ -8523,26 +8517,27 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
current inferior may no longer have a process_stratum target
pushed, as we just detached. */
- /* See if we have a THREAD_RUNNING thread that need to be
- re-resumed. If we have any thread that is already executing,
- then we don't need to resume the target -- it is already been
- resumed. With the remote target (in all-stop), it's even
- impossible to issue another resumption if the target is already
- resumed, until the target reports a stop. */
+ /* See if we have a thread that is running from the user's
+ perspective that need to be re-resumed. If we have any thread
+ that is already executing, then we don't need to resume the
+ target -- it is already been resumed. With the remote target (in
+ all-stop), it's even impossible to issue another resumption if
+ the target is already resumed, until the target reports a
+ stop. */
for (thread_info &thr : all_threads (proc_target))
{
- if (thr.state != THREAD_RUNNING)
+ if (thr.state () != THREAD_RUNNING)
continue;
/* If we have any thread that is already executing, then we
don't need to resume the target -- it is already been
resumed. */
- if (thr.executing ())
+ if (thr.internal_state () == THREAD_INT_RUNNING)
return;
- /* If we have a pending event to process, skip resuming the
+ /* If we have a pending status to process, skip resuming the
target and go straight to processing it. */
- if (thr.resumed () && thr.has_pending_waitstatus ())
+ if (thr.internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
return;
}
@@ -8555,7 +8550,7 @@ restart_after_all_stop_detach (process_stratum_target *proc_target)
it. */
for (thread_info &thr : all_threads (proc_target))
{
- if (thr.state != THREAD_RUNNING)
+ if (thr.state () != THREAD_RUNNING)
continue;
execution_control_state ecs (&thr);
@@ -8592,7 +8587,7 @@ keep_going_stepped_thread (struct thread_info *tp)
stepping thread is still alive. For that reason, we need to
synchronously query the target now. */
- if (tp->state == THREAD_EXITED || !target_thread_alive (tp->ptid))
+ if (tp->state () == THREAD_EXITED || !target_thread_alive (tp->ptid))
{
infrun_debug_printf ("not resuming previously stepped thread, it has "
"vanished");
@@ -8645,7 +8640,6 @@ keep_going_stepped_thread (struct thread_info *tp)
get_frame_address_space (frame),
tp->stop_pc ());
- tp->set_resumed (true);
resume_ptid = internal_resume_ptid (tp->control.stepping_command);
do_target_resume (resume_ptid, false, GDB_SIGNAL_0);
}
@@ -9054,7 +9048,7 @@ static void
keep_going_pass_signal (struct execution_control_state *ecs)
{
gdb_assert (ecs->event_thread->ptid == inferior_ptid);
- gdb_assert (!ecs->event_thread->resumed ());
+ gdb_assert (ecs->event_thread->internal_state () == THREAD_INT_STOPPED);
/* Save the pc before execution, to compare with pc after stop. */
ecs->event_thread->prev_pc
@@ -9525,7 +9519,7 @@ stop_context::changed () const
return true;
if (inf_num != current_inferior ()->num)
return true;
- if (thread != nullptr && thread->state != THREAD_STOPPED)
+ if (thread != nullptr && thread->state () != THREAD_STOPPED)
return true;
if (get_stop_id () != stop_id)
return true;
@@ -9619,7 +9613,7 @@ normal_stop ()
to a spurious stop event. The thread exiting will have already
been reported (when the thread list was parsed), so making this a
spurious stop will cause GDB to drop back to the prompt. */
- if (inferior_thread ()->state != THREAD_EXITED)
+ if (inferior_thread ()->state () != THREAD_EXITED)
notify_signal_received (inferior_thread ()->stop_signal ());
else
{
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 9eaa5387ef0..f15662d5bc9 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -66,12 +66,11 @@ infrun_debug_show_threads (const char *title, ThreadRange threads)
infrun_debug_printf ("%s:", title);
for (thread_info &thread : threads)
- infrun_debug_printf (" thread %s, executing = %d, resumed = %d, "
+ infrun_debug_printf (" thread %s, internal_state = %s, "
"state = %s",
thread.ptid.to_string ().c_str (),
- thread.executing (),
- thread.resumed (),
- thread_state_string (thread.state));
+ thread_int_state_string (thread.internal_state ()),
+ thread_state_string (thread.state ()));
}
}
diff --git a/gdb/linux-fork.c b/gdb/linux-fork.c
index 960995d94f1..92afa4dde6d 100644
--- a/gdb/linux-fork.c
+++ b/gdb/linux-fork.c
@@ -356,8 +356,7 @@ fork_load_infrun_state (struct fork_info *fp)
inferior_thread ()->set_stop_pc
(regcache_read_pc (get_thread_regcache (inferior_thread ())));
- inferior_thread ()->set_executing (false);
- inferior_thread ()->set_resumed (false);
+ inferior_thread ()->set_internal_state (THREAD_INT_STOPPED);
nullify_last_target_wait_ptid ();
/* Now restore the file positions of open file descriptors. */
@@ -719,7 +718,7 @@ delete_checkpoint_command (const char *args, int from_tty)
ptid. */
thread_info *parent = linux_target->find_thread (pptid);
if ((parent == NULL && find_fork_ptid (pptid).first != nullptr)
- || (parent != NULL && parent->state == THREAD_STOPPED))
+ || (parent != NULL && parent->state () == THREAD_STOPPED))
{
if (inferior_call_waitpid (pptid, ptid.pid ()))
warning (_("Unable to wait pid %s"),
@@ -859,7 +858,7 @@ print_checkpoints (struct ui_out *uiout, inferior *req_inf, fork_info *req_fi)
uiout->field_string
("target-id", target_pid_to_str (proc_ptid (fi.ptid)).c_str ());
- if (t->state == THREAD_RUNNING && is_current)
+ if (t->state () == THREAD_RUNNING && is_current)
uiout->text ("(running)");
else
{
@@ -1101,7 +1100,7 @@ restart_command (const char *args, int from_tty)
/* Don't allow switching from a thread/fork that's running. */
inferior *curinf = current_inferior ();
if (curinf->pid != 0
- && any_thread_of_inferior (curinf)->state == THREAD_RUNNING)
+ && any_thread_of_inferior (curinf)->state () == THREAD_RUNNING)
error (_("Cannot execute this command while "
"the selected thread is running."));
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 1d0a4609a3a..9a49d53ea2a 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1242,8 +1242,9 @@ linux_nat_target::attach (const char *args, int from_tty)
if (lwp->ptid.pid () != lwp->ptid.lwp ())
{
add_thread (linux_target, lwp->ptid);
- set_running (linux_target, lwp->ptid, true);
- set_executing (linux_target, lwp->ptid, true);
+ set_state (linux_target, lwp->ptid, THREAD_RUNNING);
+ set_internal_state (linux_target, lwp->ptid,
+ THREAD_INT_RUNNING);
}
return 0;
});
@@ -1327,7 +1328,8 @@ get_detach_signal (struct lwp_info *lp)
{
thread_info *tp = linux_target->find_thread (lp->ptid);
- if (target_is_non_stop_p () && !tp->executing ())
+ if (target_is_non_stop_p ()
+ && tp->internal_state () != THREAD_INT_RUNNING)
{
if (tp->has_pending_waitstatus ())
{
diff --git a/gdb/linux-thread-db.c b/gdb/linux-thread-db.c
index 0753d2e7915..55b9953ae4f 100644
--- a/gdb/linux-thread-db.c
+++ b/gdb/linux-thread-db.c
@@ -1361,7 +1361,7 @@ record_thread (struct thread_db_info *info,
/* Add the thread to GDB's thread list. If we already know about a
thread with this PTID, but it's marked exited, then the kernel
reused the tid of an old thread. */
- if (tp == NULL || tp->state == THREAD_EXITED)
+ if (tp == NULL || tp->state () == THREAD_EXITED)
tp = add_thread_with_info (info->process_target, ptid,
private_thread_info_up (priv));
else
@@ -1625,7 +1625,7 @@ thread_db_target::update_thread_list ()
continue;
thread_info *thread = any_live_thread_of_inferior (inf);
- if (thread == NULL || thread->executing ())
+ if (thread == NULL || thread->internal_state () == THREAD_INT_RUNNING)
continue;
/* It's best to avoid td_ta_thr_iter if possible. That walks
diff --git a/gdb/mi/mi-cmd-var.c b/gdb/mi/mi-cmd-var.c
index f1f1637b74b..1f7e0fdfe3d 100644
--- a/gdb/mi/mi-cmd-var.c
+++ b/gdb/mi/mi-cmd-var.c
@@ -607,14 +607,14 @@ mi_cmd_var_update_iter (struct varobj *var, bool only_floating,
if (thread_id == -1)
{
thread_stopped = (inferior_ptid == null_ptid
- || inferior_thread ()->state == THREAD_STOPPED);
+ || inferior_thread ()->state () == THREAD_STOPPED);
}
else
{
thread_info *tp = find_thread_global_id (thread_id);
thread_stopped = (tp == NULL
- || tp->state == THREAD_STOPPED);
+ || tp->state () == THREAD_STOPPED);
}
if (thread_stopped
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index a7b96c6a64c..8b3048c5a46 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -858,7 +858,7 @@ mi_interp::on_user_selected_context_changed (user_selected_what selection)
gdb_printf (this->event_channel, "thread-selected,id=\"%d\"",
tp->global_num);
- if (tp->state != THREAD_RUNNING)
+ if (tp->state () != THREAD_RUNNING)
{
if (has_stack_frames ())
print_stack_frame_to_uiout (mi_uiout, get_selected_frame (NULL),
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index c03cbfe5117..bf08fe822b3 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -238,7 +238,7 @@ mi_cmd_exec_jump (const char *args, const char *const *argv, int argc)
static void
proceed_thread (struct thread_info *thread, int pid)
{
- if (thread->state != THREAD_STOPPED)
+ if (thread->state () != THREAD_STOPPED)
return;
if (pid != 0 && thread->ptid.pid () != pid)
@@ -366,7 +366,7 @@ mi_cmd_exec_interrupt (const char *command, const char *const *argv, int argc)
iterate_over_threads ([&] (struct thread_info *thread)
{
- if (thread->state != THREAD_RUNNING)
+ if (thread->state () != THREAD_RUNNING)
return false;
if (thread->ptid.pid () != inf->pid)
@@ -507,7 +507,7 @@ mi_cmd_target_detach (const char *command, const char *const *argv, int argc)
target_detach detaches from the parent of inferior_ptid. */
tp = iterate_over_threads ([&] (struct thread_info *ti)
{
- return ti->ptid.pid () == pid && ti->state != THREAD_EXITED;
+ return ti->ptid.pid () == pid && ti->state () != THREAD_EXITED;
});
if (!tp)
error (_("Thread group is empty"));
@@ -2060,7 +2060,7 @@ mi_cmd_execute (struct mi_parse *parse)
if (tp == NULL)
error (_("Invalid thread id: %d"), parse->thread);
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
error (_("Thread id: %d has terminated"), parse->thread);
if (parse->cmd->preserve_user_selected_context ())
diff --git a/gdb/process-stratum-target.c b/gdb/process-stratum-target.c
index d5c136d0b81..6a205b31eee 100644
--- a/gdb/process-stratum-target.c
+++ b/gdb/process-stratum-target.c
@@ -114,8 +114,9 @@ process_stratum_target::maybe_add_resumed_with_pending_wait_status
{
gdb_assert (!thread->resumed_with_pending_wait_status_node.is_linked ());
- if (thread->resumed () && thread->has_pending_waitstatus ())
+ if (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
{
+ gdb_assert (thread->has_pending_waitstatus ());
infrun_debug_printf ("adding to resumed threads with event list: %s",
thread->ptid.to_string ().c_str ());
m_resumed_with_pending_wait_status.push_back (*thread);
@@ -128,8 +129,9 @@ void
process_stratum_target::maybe_remove_resumed_with_pending_wait_status
(thread_info *thread)
{
- if (thread->resumed () && thread->has_pending_waitstatus ())
+ if (thread->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS)
{
+ gdb_assert (thread->has_pending_waitstatus ());
infrun_debug_printf ("removing from resumed threads with event list: %s",
thread->ptid.to_string ().c_str ());
gdb_assert (thread->resumed_with_pending_wait_status_node.is_linked ());
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index cd9e2304ba8..4adfaa71ae2 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -259,7 +259,7 @@ thpy_is_stopped (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_STOPPED)
+ if (thread_obj->thread->state () == THREAD_STOPPED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
@@ -275,7 +275,7 @@ thpy_is_running (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_RUNNING)
+ if (thread_obj->thread->state () == THREAD_RUNNING)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
@@ -291,7 +291,7 @@ thpy_is_exited (PyObject *self, PyObject *args)
THPY_REQUIRE_VALID (thread_obj);
- if (thread_obj->thread->state == THREAD_EXITED)
+ if (thread_obj->thread->state () == THREAD_EXITED)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
diff --git a/gdb/record-btrace.c b/gdb/record-btrace.c
index bded22af8e6..d12d8616140 100644
--- a/gdb/record-btrace.c
+++ b/gdb/record-btrace.c
@@ -413,7 +413,7 @@ record_btrace_target::stop_recording ()
/* Check that before so stop recording is atomic. */
for (thread_info &tp : current_inferior ()->non_exited_threads ())
- if (tp.state == THREAD_RUNNING)
+ if (tp.state () == THREAD_RUNNING)
error (_("You cannot stop recording while threads are running."));
bool is_replaying = record_is_replaying (inferior_ptid);
@@ -1995,18 +1995,18 @@ get_thread_current_frame_id (struct thread_info *tp)
process_stratum_target *proc_target = tp->inf->process_target ();
- /* Clear the executing flag to allow changes to the current frame.
- We are not actually running, yet. We just started a reverse execution
- command or a record goto command.
- For the latter, EXECUTING is false and this has no effect.
- For the former, EXECUTING is true and we're in wait, about to
- move the thread. Since we need to recompute the stack, we temporarily
- set EXECUTING to false. */
- bool executing = tp->executing ();
- set_executing (proc_target, inferior_ptid, false);
+ /* Temporarily set the thread to internally stopped to allow changes
+ to the current frame. We are not actually running, yet. We just
+ started a reverse execution command or a record goto command.
+ For the latter, the thread is stopped and this has no effect.
+ For the former, the thread is running and we're in wait, about to
+ move the thread. Since we need to recompute the stack, we
+ temporarily set the thread to internally stopped. */
+ thread_int_state prev_int_state = tp->internal_state ();
+ set_internal_state (proc_target, inferior_ptid, THREAD_INT_STOPPED);
SCOPE_EXIT
{
- set_executing (proc_target, inferior_ptid, executing);
+ set_internal_state (proc_target, inferior_ptid, prev_int_state);
};
return get_frame_id (get_current_frame ());
}
@@ -2097,7 +2097,7 @@ record_btrace_stop_replaying (struct thread_info *tp)
if (btinfo->replay == nullptr)
return;
- switch (tp->state)
+ switch (tp->state ())
{
case THREAD_STOPPED:
/* Forget why we stopped; it was at a different location. */
@@ -2777,7 +2777,7 @@ record_btrace_set_replay (struct thread_info *tp,
{
struct btrace_thread_info *btinfo;
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
error (_("You cannot do that while the thread is running."));
btinfo = &tp->btrace;
diff --git a/gdb/record-full.c b/gdb/record-full.c
index e0af376f3eb..5258c3c5f84 100644
--- a/gdb/record-full.c
+++ b/gdb/record-full.c
@@ -1272,10 +1272,12 @@ record_full_wait_1 (struct target_ops *ops,
{
/* Try to insert the software single step breakpoint.
If insert success, set step to 0. */
- set_executing (proc_target, inferior_ptid, false);
+ set_internal_state (proc_target, inferior_ptid,
+ THREAD_INT_STOPPED);
SCOPE_EXIT
{
- set_executing (proc_target, inferior_ptid, true);
+ set_internal_state (proc_target, inferior_ptid,
+ THREAD_INT_RUNNING);
};
reinit_frame_cache ();
diff --git a/gdb/regcache.c b/gdb/regcache.c
index ccd24d588d3..e94a94c48eb 100644
--- a/gdb/regcache.c
+++ b/gdb/regcache.c
@@ -429,7 +429,7 @@ get_thread_regcache (process_stratum_target *target, ptid_t ptid)
struct regcache *
get_thread_regcache (thread_info *thread)
{
- gdb_assert (thread->state != THREAD_EXITED);
+ gdb_assert (thread->state () != THREAD_EXITED);
return get_thread_regcache (thread->inf->process_target (),
thread->ptid);
diff --git a/gdb/remote.c b/gdb/remote.c
index 3ec56a6d61b..c2555d528fa 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -1329,12 +1329,15 @@ class remote_target : public process_stratum_target
ptid_t select_thread_for_ambiguous_stop_reply
(const struct target_waitstatus &status);
- void remote_notice_new_inferior (ptid_t currthread, bool executing);
+ void remote_notice_new_inferior (ptid_t currthread,
+ thread_int_state internal_state);
void print_one_stopped_thread (thread_info *thread);
void process_initial_stop_replies (int from_tty);
- thread_info *remote_add_thread (ptid_t ptid, bool running, bool executing,
+ thread_info *remote_add_thread (ptid_t ptid,
+ thread_state state,
+ thread_int_state internal_state,
bool silent_p);
void btrace_sync_conf (const btrace_config *conf);
@@ -3151,13 +3154,16 @@ static remote_thread_info *get_remote_thread_info (thread_info *thread);
static remote_thread_info *get_remote_thread_info (remote_target *target,
ptid_t ptid);
-/* Add thread PTID to GDB's thread list. Tag it as executing/running
- according to EXECUTING and RUNNING respectively. If SILENT_P (or the
- remote_state::starting_up flag) is true then the new thread is added
- silently, otherwise the new thread will be announced to the user. */
+/* Add thread PTID to GDB's thread list. Tag its user and internal
+ states according to STATE and INTERNAL_STATE respectively. If
+ SILENT_P (or the remote_state::starting_up flag) is true then the
+ new thread is added silently, otherwise the new thread will be
+ announced to the user. */
thread_info *
-remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
+remote_target::remote_add_thread (ptid_t ptid,
+ thread_state state,
+ thread_int_state internal_state,
bool silent_p)
{
struct remote_state *rs = get_remote_state ();
@@ -3174,10 +3180,10 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
else
thread = add_thread (this, ptid);
- if (executing)
+ if (internal_state == THREAD_INT_RUNNING)
get_remote_thread_info (thread)->set_resumed ();
- set_executing (this, ptid, executing);
- set_running (this, ptid, running);
+ set_internal_state (this, ptid, internal_state);
+ set_state (this, ptid, state);
return thread;
}
@@ -3186,26 +3192,29 @@ remote_target::remote_add_thread (ptid_t ptid, bool running, bool executing,
It may be the first time we hear about such thread, so take the
opportunity to add it to GDB's thread list. In case this is the
first time we're noticing its corresponding inferior, add it to
- GDB's inferior list as well. EXECUTING indicates whether the
- thread is (internally) executing or stopped. */
+ GDB's inferior list as well. INTERNAL_STATE indicates whether the
+ thread is internally running or stopped. */
void
-remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
+remote_target::remote_notice_new_inferior (ptid_t currthread,
+ thread_int_state internal_state)
{
/* In non-stop mode, we assume new found threads are (externally)
running until proven otherwise with a stop reply. In all-stop,
we can only get here if all threads are stopped. */
- bool running = target_is_non_stop_p ();
+ thread_state state = (target_is_non_stop_p ()
+ ? THREAD_RUNNING
+ : THREAD_STOPPED);
/* If this is a new thread, add it to GDB's thread list.
If we leave it up to WFI to do this, bad things will happen. */
thread_info *tp = this->find_thread (currthread);
- if (tp != NULL && tp->state == THREAD_EXITED)
+ if (tp != NULL && tp->state () == THREAD_EXITED)
{
/* We're seeing an event on a thread id we knew had exited.
This has to be a new thread reusing the old id. Add it. */
- remote_add_thread (currthread, running, executing, false);
+ remote_add_thread (currthread, state, internal_state, false);
return;
}
@@ -3227,7 +3236,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
else
{
thread_info *thr
- = remote_add_thread (currthread, running, executing, false);
+ = remote_add_thread (currthread, state, internal_state, false);
switch_to_thread (thr);
}
return;
@@ -3264,7 +3273,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
/* This is really a new thread. Add it. */
thread_info *new_thr
- = remote_add_thread (currthread, running, executing, false);
+ = remote_add_thread (currthread, state, internal_state, false);
/* If we found a new inferior, let the common code do whatever
it needs to with it (e.g., read shared libraries, insert
@@ -3275,7 +3284,7 @@ remote_target::remote_notice_new_inferior (ptid_t currthread, bool executing)
struct remote_state *rs = get_remote_state ();
if (!rs->starting_up)
- notice_new_inferior (new_thr, executing, 0);
+ notice_new_inferior (new_thr, internal_state, 0);
}
}
}
@@ -4616,12 +4625,14 @@ remote_target::update_thread_list ()
if (item.ptid != null_ptid)
{
/* In non-stop mode, we assume new found threads are
- executing until proven otherwise with a stop reply.
- In all-stop, we can only get here if all threads are
+ running until proven otherwise with a stop reply. In
+ all-stop, we can only get here if all threads are
stopped. */
- bool executing = target_is_non_stop_p ();
+ thread_int_state internal_state = (target_is_non_stop_p ()
+ ? THREAD_INT_RUNNING
+ : THREAD_INT_STOPPED);
- remote_notice_new_inferior (item.ptid, executing);
+ remote_notice_new_inferior (item.ptid, internal_state);
thread_info *tp = this->find_thread (item.ptid);
remote_thread_info *info = get_remote_thread_info (tp);
@@ -5237,8 +5248,8 @@ remote_target::process_initial_stop_replies (int from_tty)
|| ws.sig () != GDB_SIGNAL_0)
evthread->set_pending_waitstatus (ws);
- set_executing (this, event_ptid, false);
- set_running (this, event_ptid, false);
+ set_internal_state (this, event_ptid, THREAD_INT_STOPPED);
+ set_state (this, event_ptid, THREAD_STOPPED);
get_remote_thread_info (evthread)->set_not_resumed ();
}
@@ -5251,7 +5262,7 @@ remote_target::process_initial_stop_replies (int from_tty)
if (non_stop)
{
thread_info *thread = any_live_thread_of_inferior (inf);
- notice_new_inferior (thread, thread->state == THREAD_RUNNING,
+ notice_new_inferior (thread, thread->state () == THREAD_RUNNING,
from_tty);
}
}
@@ -5311,8 +5322,8 @@ remote_target::process_initial_stop_replies (int from_tty)
first = &thread;
if (!non_stop)
- thread.set_running (false);
- else if (thread.state != THREAD_STOPPED)
+ thread.set_state (THREAD_STOPPED);
+ else if (thread.state () != THREAD_STOPPED)
continue;
if (selected == nullptr && thread.has_pending_waitstatus ())
@@ -7011,7 +7022,7 @@ remote_target::follow_fork (inferior *child_inf, ptid_t child_ptid,
void
remote_target::follow_clone (ptid_t child_ptid)
{
- remote_add_thread (child_ptid, false, false, false);
+ remote_add_thread (child_ptid, THREAD_STOPPED, THREAD_INT_STOPPED, false);
}
/* Target follow-exec function for remote targets. Save EXECD_PATHNAME
@@ -7120,7 +7131,8 @@ extended_remote_target::attach (const char *args, int from_tty)
/* Add the main thread to the thread list. We add the thread
silently in this case (the final true parameter). */
- thread_info *thr = remote_add_thread (curr_ptid, true, true, true);
+ thread_info *thr = remote_add_thread (curr_ptid, THREAD_RUNNING,
+ THREAD_INT_RUNNING, true);
switch_to_thread (thr);
}
@@ -8998,7 +9010,7 @@ remote_target::process_stop_reply (stop_reply_up stop_reply,
&& status->kind () != TARGET_WAITKIND_SIGNALLED
&& status->kind () != TARGET_WAITKIND_NO_RESUMED)
{
- remote_notice_new_inferior (ptid, false);
+ remote_notice_new_inferior (ptid, THREAD_INT_STOPPED);
/* Expedited registers. */
if (!stop_reply->regcache.empty ())
@@ -9104,7 +9116,7 @@ static ptid_t
first_remote_resumed_thread (remote_target *target)
{
for (thread_info &tp : all_non_exited_threads (target, minus_one_ptid))
- if (tp.resumed ())
+ if (tp.internal_state () != THREAD_INT_STOPPED)
return tp.ptid;
return null_ptid;
}
diff --git a/gdb/sol-thread.c b/gdb/sol-thread.c
index ec480186b59..c765a4205a0 100644
--- a/gdb/sol-thread.c
+++ b/gdb/sol-thread.c
@@ -451,7 +451,7 @@ sol_thread_target::wait (ptid_t ptid, struct target_waitstatus *ourstatus,
if (rtnval.tid_p ())
{
thread_info *thr = current_inferior ()->find_thread (rtnval);
- if (thr == NULL || thr->state == THREAD_EXITED)
+ if (thr == NULL || thr->state () == THREAD_EXITED)
{
process_stratum_target *proc_target
= current_inferior ()->process_target ();
@@ -998,7 +998,7 @@ sol_update_thread_list_callback (const td_thrhandle_t *th, void *ignored)
ptid_t ptid = ptid_t (current_inferior ()->pid, 0, ti.ti_tid);
thread_info *thr = current_inferior ()->find_thread (ptid);
- if (thr == NULL || thr->state == THREAD_EXITED)
+ if (thr == NULL || thr->state () == THREAD_EXITED)
{
process_stratum_target *proc_target
= current_inferior ()->process_target ();
diff --git a/gdb/target.c b/gdb/target.c
index 359618e800f..b38a0536525 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -2662,10 +2662,10 @@ target_resume (ptid_t scope_ptid, int step, enum gdb_signal signal)
current_inferior ()->top_target ()->resume (scope_ptid, step, signal);
registers_changed_ptid (curr_target, scope_ptid);
- /* We only set the internal executing state here. The user/frontend
- running state is set at a higher level. This also clears the
- thread's stop_pc as side effect. */
- set_executing (curr_target, scope_ptid, true);
+ /* We only set the internal state here. The user/frontend state is
+ set at a higher level. This also clears the thread's stop_pc as
+ side effect. */
+ set_internal_state (curr_target, scope_ptid, THREAD_INT_RUNNING);
clear_inline_frame_state (curr_target, scope_ptid);
if (target_can_async_p ())
@@ -3802,9 +3802,10 @@ target_pass_ctrlc (void)
for (thread_info &thr : inf->non_exited_threads ())
{
- /* A thread can be THREAD_STOPPED and executing, while
- running an infcall. */
- if (thr.state == THREAD_RUNNING || thr.executing ())
+ /* A thread can be externally THREAD_STOPPED and internally
+ THREAD_INT_RUNNING, while running an infcall. */
+ if (thr.state () == THREAD_RUNNING
+ || thr.internal_state () == THREAD_INT_RUNNING)
{
/* We can get here quite deep in target layers. Avoid
switching thread context or anything that would
diff --git a/gdb/thread-iter.h b/gdb/thread-iter.h
index caaf7220e27..3c15ada1cbb 100644
--- a/gdb/thread-iter.h
+++ b/gdb/thread-iter.h
@@ -148,7 +148,7 @@ struct non_exited_thread_filter
{
bool operator() (struct thread_info &thr) const
{
- return thr.state != THREAD_EXITED;
+ return thr.state () != THREAD_EXITED;
}
};
diff --git a/gdb/thread.c b/gdb/thread.c
index 96e3bb7b50f..a6ae2a75139 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -228,23 +228,13 @@ set_thread_exited (thread_info *tp, std::optional<ULONGEST> exit_code,
if (thread_is_in_step_over_chain (tp))
global_thread_step_over_chain_remove (tp);
- if (tp->state != THREAD_EXITED)
+ if (tp->state () != THREAD_EXITED)
{
- process_stratum_target *proc_target = tp->inf->process_target ();
-
- /* Some targets unpush themselves from the inferior's target stack before
- clearing the inferior's thread list (which marks all threads as exited,
- and therefore leads to this function). In this case, the inferior's
- process target will be nullptr when we arrive here.
-
- See also the comment in inferior::unpush_target. */
- if (proc_target != nullptr)
- proc_target->maybe_remove_resumed_with_pending_wait_status (tp);
-
notify_thread_exited (tp, exit_code, silent);
/* Tag it as exited. */
- tp->state = THREAD_EXITED;
+ tp->set_state (THREAD_EXITED);
+ tp->set_internal_state (THREAD_INT_EXITED);
/* Clear breakpoints, etc. associated with this thread. */
clear_thread_inferior_resources (tp);
@@ -379,34 +369,50 @@ thread_info::deletable () const
/* See gdbthread.h. */
void
-thread_info::set_executing (bool executing)
-{
- m_executing = executing;
- if (executing)
- this->clear_stop_pc ();
-}
-
-/* See gdbthread.h. */
-
-void
-thread_info::set_resumed (bool resumed)
+thread_info::set_internal_state (thread_int_state state)
{
- if (resumed == m_resumed)
+ if (m_internal_state == state)
return;
- process_stratum_target *proc_target = this->inf->process_target ();
-
- /* If we transition from resumed to not resumed, we might need to remove
- the thread from the resumed threads with pending statuses list. */
- if (!resumed)
- proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+ if (state == THREAD_INT_RUNNING)
+ this->clear_stop_pc ();
- m_resumed = resumed;
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS)
+ gdb_assert (this->has_pending_waitstatus ());
+ else if (state == THREAD_INT_RUNNING)
+ gdb_assert (!this->has_pending_waitstatus ());
- /* If we transition from not resumed to resumed, we might need to add
+ /* If we transition from resumed-pending-status to another state, we
+ might need to remove the thread from the resumed threads with
+ pending statuses list. Conversely, if we transition to
+ resumed-pending-status from another state, we might need to add
the thread to the resumed threads with pending statuses list. */
- if (resumed)
- proc_target->maybe_add_resumed_with_pending_wait_status (this);
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS
+ || m_internal_state == THREAD_INT_RESUMED_PENDING_STATUS)
+ {
+ process_stratum_target *proc_target = this->inf->process_target ();
+
+ /* We need the proc_target NULL checks below, because some
+ targets unpush themselves from the inferior's target stack
+ before clearing the inferior's thread list (which marks all
+ threads as exited, and therefore leads to this function). In
+ this case, the inferior's process target will be nullptr when
+ we arrive here. See also the comment in
+ inferior::unpush_target. */
+
+ if (state != THREAD_INT_RESUMED_PENDING_STATUS && proc_target != nullptr)
+ proc_target->maybe_remove_resumed_with_pending_wait_status (this);
+
+ /* Note maybe_remove_resumed_with_pending_wait_status internally
+ reads this state. Thus it must be updated after the call
+ above, and before the call below. */
+ m_internal_state = state;
+
+ if (state == THREAD_INT_RESUMED_PENDING_STATUS && proc_target != nullptr)
+ proc_target->maybe_add_resumed_with_pending_wait_status (this);
+ }
+ else
+ m_internal_state = state;
}
/* See gdbthread.h. */
@@ -415,12 +421,13 @@ void
thread_info::set_pending_waitstatus (const target_waitstatus &ws)
{
gdb_assert (!this->has_pending_waitstatus ());
+ /* Doesn't make sense to set a pending status on an exited or
+ running thread. */
+ gdb_assert (this->internal_state () == THREAD_INT_STOPPED
+ || this->internal_state () == THREAD_INT_RESUMED_PENDING_STATUS);
m_suspend.waitstatus = ws;
m_suspend.waitstatus_pending_p = 1;
-
- process_stratum_target *proc_target = this->inf->process_target ();
- proc_target->maybe_add_resumed_with_pending_wait_status (this);
}
/* See gdbthread.h. */
@@ -430,9 +437,6 @@ thread_info::clear_pending_waitstatus ()
{
gdb_assert (this->has_pending_waitstatus ());
- process_stratum_target *proc_target = this->inf->process_target ();
- proc_target->maybe_remove_resumed_with_pending_wait_status (this);
-
m_suspend.waitstatus_pending_p = 0;
}
@@ -441,8 +445,8 @@ thread_info::clear_pending_waitstatus ()
void
thread_info::set_thread_options (gdb_thread_options thread_options)
{
- gdb_assert (this->state != THREAD_EXITED);
- gdb_assert (!this->executing ());
+ gdb_assert (this->state () != THREAD_EXITED);
+ gdb_assert (this->internal_state () == THREAD_INT_STOPPED);
if (m_thread_options == thread_options)
return;
@@ -688,30 +692,30 @@ any_thread_of_inferior (inferior *inf)
thread_info *
any_live_thread_of_inferior (inferior *inf)
{
- struct thread_info *curr_tp = NULL;
- struct thread_info *tp_executing = NULL;
+ thread_info *curr_tp = NULL;
+ thread_info *tp_running = NULL;
gdb_assert (inf != NULL && inf->pid != 0);
- /* Prefer the current thread if it's not executing. */
+ /* Prefer the current thread if it's stopped. */
if (inferior_ptid != null_ptid && current_inferior () == inf)
{
- /* If the current thread is dead, forget it. If it's not
- executing, use it. Otherwise, still choose it (below), but
- only if no other non-executing thread is found. */
+ /* If the current thread is dead, forget it. If it's stopped,
+ use it. Otherwise, still choose it (below), but only if no
+ other running thread is found. */
curr_tp = inferior_thread ();
- if (curr_tp->state == THREAD_EXITED)
+ if (curr_tp->internal_state () == THREAD_INT_EXITED)
curr_tp = NULL;
- else if (!curr_tp->executing ())
+ else if (curr_tp->internal_state () != THREAD_INT_RUNNING)
return curr_tp;
}
for (thread_info &tp : inf->non_exited_threads ())
{
- if (!tp.executing ())
+ if (tp.internal_state () != THREAD_INT_RUNNING)
return &tp;
- tp_executing = &tp;
+ tp_running = &tp;
}
/* If both the current thread and all live threads are executing,
@@ -719,15 +723,15 @@ any_live_thread_of_inferior (inferior *inf)
if (curr_tp != NULL)
return curr_tp;
- /* Otherwise, just return an executing thread, if any. */
- return tp_executing;
+ /* Otherwise, just return a running thread, if any. */
+ return tp_running;
}
/* Return true if TP is an active thread. */
static bool
thread_alive (thread_info *tp)
{
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
return false;
/* Ensure we're looking at the right target stack. */
@@ -779,7 +783,7 @@ void
delete_exited_threads (void)
{
for (thread_info &tp : all_threads_safe ())
- if (tp.state == THREAD_EXITED)
+ if (tp.state () == THREAD_EXITED)
delete_thread (&tp);
}
@@ -858,43 +862,6 @@ thread_change_ptid (process_stratum_target *targ,
gdb::observers::thread_ptid_changed.notify (targ, old_ptid, new_ptid);
}
-/* See gdbthread.h. */
-
-void
-set_resumed (process_stratum_target *targ, ptid_t ptid, bool resumed)
-{
- for (thread_info &tp : all_non_exited_threads (targ, ptid))
- tp.set_resumed (resumed);
-}
-
-/* Helper for set_running, that marks one thread either running or
- stopped. */
-
-static bool
-set_running_thread (struct thread_info *tp, bool running)
-{
- bool started = false;
-
- if (running && tp->state == THREAD_STOPPED)
- started = true;
- tp->state = running ? THREAD_RUNNING : THREAD_STOPPED;
-
- threads_debug_printf ("thread: %s, running? %d%s",
- tp->ptid.to_string ().c_str (), running,
- (started ? " (started)" : ""));
-
- if (!running)
- {
- /* If the thread is now marked stopped, remove it from
- the step-over queue, so that we don't try to resume
- it until the user wants it to. */
- if (thread_is_in_step_over_chain (tp))
- global_thread_step_over_chain_remove (tp);
- }
-
- return started;
-}
-
/* Notify interpreters and observers that the target was resumed. */
static void
@@ -911,41 +878,73 @@ notify_target_resumed (ptid_t ptid)
/* See gdbthread.h. */
-void
-thread_info::set_running (bool running)
+thread_state
+thread_info::set_state (thread_state state,
+ bool suppress_notification)
{
- if (set_running_thread (this, running))
- notify_target_resumed (this->ptid);
+ thread_state prev_state = m_state;
+
+ if (prev_state == state)
+ return prev_state;
+
+ threads_debug_printf ("thread: %s, %s -> %s",
+ this->ptid.to_string ().c_str (),
+ thread_state_string (m_state),
+ thread_state_string (state));
+
+ m_state = state;
+
+ switch (m_state)
+ {
+ case THREAD_EXITED:
+ break;
+
+ case THREAD_STOPPED:
+ /* If the thread is now marked stopped, remove it from
+ the step-over queue, so that we don't try to resume
+ it until the user wants it to. */
+ if (thread_is_in_step_over_chain (this))
+ global_thread_step_over_chain_remove (this);
+ break;
+
+ case THREAD_RUNNING:
+ if (!suppress_notification)
+ notify_target_resumed (this->ptid);
+ break;
+ }
+
+ return prev_state;
}
void
-set_running (process_stratum_target *targ, ptid_t ptid, bool running)
+set_state (process_stratum_target *targ, ptid_t ptid, thread_state state)
{
/* We try not to notify the observer if no thread has actually
- changed the running state -- merely to reduce the number of
+ changed its public state -- merely to reduce the number of
messages to the MI frontend. A frontend is supposed to handle
multiple *running notifications just fine. */
- bool any_started = false;
+ bool any_changed = false;
for (thread_info &tp : all_non_exited_threads (targ, ptid))
- if (set_running_thread (&tp, running))
- any_started = true;
+ if (tp.set_state (state, true) != state)
+ any_changed = true;
- if (any_started)
+ if (any_changed && state == THREAD_RUNNING)
notify_target_resumed (ptid);
}
void
-set_executing (process_stratum_target *targ, ptid_t ptid, bool executing)
+set_internal_state (process_stratum_target *targ, ptid_t ptid,
+ thread_int_state state)
{
for (thread_info &tp : all_non_exited_threads (targ, ptid))
- tp.set_executing (executing);
+ tp.set_internal_state (state);
/* It only takes one running thread to spawn more threads. */
- if (executing)
+ if (state == THREAD_INT_RUNNING)
targ->threads_executing = true;
/* Only clear the flag if the caller is telling us everything is
- stopped. */
+ stopped or dead. */
else if (minus_one_ptid == ptid)
targ->threads_executing = false;
}
@@ -970,14 +969,41 @@ set_stop_requested (process_stratum_target *targ, ptid_t ptid, bool stop)
gdb::observers::thread_stop_requested.notify (ptid);
}
+/* Map INT_STATE to a user state. */
+
+static thread_state
+state_from_int_state (thread_int_state int_state)
+{
+ switch (int_state)
+ {
+ case THREAD_INT_RUNNING:
+ case THREAD_INT_RESUMED_PENDING_STATUS:
+ return THREAD_RUNNING;
+ case THREAD_INT_STOPPED:
+ return THREAD_STOPPED;
+ case THREAD_INT_EXITED:
+ return THREAD_EXITED;
+ }
+
+ gdb_assert_not_reached ("unknown thread_int_state: %d", int_state);
+}
+
+/* See gdbthread.h. Note this is a friend of thread_info so that it
+ can access the thread_info::set_state overload that lets us
+ suppress the target_resumed notification. */
+
void
finish_thread_state (process_stratum_target *targ, ptid_t ptid)
{
bool any_started = false;
for (thread_info &tp : all_non_exited_threads (targ, ptid))
- if (set_running_thread (&tp, tp.executing ()))
- any_started = true;
+ {
+ thread_state new_state = state_from_int_state (tp.internal_state ());
+ thread_state prev_state = tp.set_state (new_state, true);
+ if (prev_state != new_state && new_state == THREAD_RUNNING)
+ any_started = true;
+ }
if (any_started)
notify_target_resumed (ptid);
@@ -995,7 +1021,7 @@ validate_registers_access (void)
thread_info *tp = inferior_thread ();
/* Don't try to read from a dead thread. */
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
error (_("The current thread has terminated"));
/* ... or from a spinning thread. FIXME: This isn't actually fully
@@ -1003,7 +1029,7 @@ validate_registers_access (void)
at the prompt) when a thread is not executing for some internal
reason, but is marked running from the user's perspective. E.g.,
the thread is waiting for its turn in the step-over queue. */
- if (tp->executing ())
+ if (tp->internal_state () == THREAD_INT_RUNNING)
{
/* If we are replaying with the record-full subsystem, even though
the thread is executing, it is always safe to read from it since
@@ -1023,11 +1049,11 @@ can_access_registers_thread (thread_info *thread)
return false;
/* Don't try to read from a dead thread. */
- if (thread->state == THREAD_EXITED)
+ if (thread->state () == THREAD_EXITED)
return false;
/* ... or from a spinning thread. FIXME: see validate_registers_access. */
- if (thread->executing ())
+ if (thread->internal_state () == THREAD_INT_RUNNING)
{
/* See validate_registers_access. */
if (!record_full_is_replaying ())
@@ -1111,14 +1137,14 @@ should_print_thread (const char *requested_threads,
return false;
}
- if (thr->state == THREAD_EXITED)
+ if (thr->state () == THREAD_EXITED)
return false;
- bool is_stopped = (thr->state == THREAD_STOPPED);
+ bool is_stopped = (thr->state () == THREAD_STOPPED);
if (opts.show_stopped_threads && is_stopped)
return true;
- bool is_running = (thr->state == THREAD_RUNNING);
+ bool is_running = (thr->state () == THREAD_RUNNING);
if (opts.show_running_threads && is_running)
return true;
@@ -1209,7 +1235,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads,
uiout->field_string ("target-id", thread_target_id_str (tp));
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
uiout->text ("(running)\n");
else
{
@@ -1225,7 +1251,7 @@ do_print_thread (ui_out *uiout, const char *requested_threads,
{
const char *state = "stopped";
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
state = "running";
uiout->field_string ("state", state);
}
@@ -1336,7 +1362,7 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
for (inferior *inf : all_inferiors ())
for (thread_info &tp : inf->threads ())
{
- if (&tp == current_thread && tp.state == THREAD_EXITED)
+ if (&tp == current_thread && tp.state () == THREAD_EXITED)
current_exited = true;
print_thread (uiout, requested_threads, opts, global_ids, pid,
@@ -1501,7 +1527,7 @@ scoped_restore_current_thread::restore ()
changed, so we have to recheck it here. */
if (inferior_ptid != null_ptid
&& m_was_stopped
- && m_thread->state == THREAD_STOPPED
+ && m_thread->state () == THREAD_STOPPED
&& target_has_registers ()
&& target_has_stack ()
&& target_has_memory ())
@@ -1524,7 +1550,7 @@ scoped_restore_current_thread::scoped_restore_current_thread ()
{
m_thread = thread_info_ref::new_reference (inferior_thread ());
- m_was_stopped = m_thread->state == THREAD_STOPPED;
+ m_was_stopped = m_thread->state () == THREAD_STOPPED;
save_selected_frame (&m_selected_frame_id, &m_selected_frame_level);
}
}
@@ -1970,7 +1996,7 @@ thread_command (const char *tidstr, int from_tty)
{
struct thread_info *tp = inferior_thread ();
- if (tp->state == THREAD_EXITED)
+ if (tp->state () == THREAD_EXITED)
gdb_printf (_("[Current thread is %s (%s) (exited)]\n"),
print_thread_id (tp),
target_pid_to_str (inferior_ptid).c_str ());
@@ -2115,7 +2141,7 @@ print_selected_thread_frame (struct ui_out *uiout,
}
}
- if (tp->state == THREAD_RUNNING)
+ if (tp->state () == THREAD_RUNNING)
{
if (selection & USER_SELECTED_THREAD)
uiout->text ("(running)\n");
@@ -2160,7 +2186,7 @@ update_threads_executing (void)
for (thread_info &tp : inf->non_exited_threads ())
{
- if (tp.executing ())
+ if (tp.internal_state () == THREAD_INT_RUNNING)
{
targ->threads_executing = true;
return;
@@ -2214,6 +2240,29 @@ thread_state_string (enum thread_state state)
gdb_assert_not_reached ("unknown thread state");
}
+/* See gdbthread.h. */
+
+const char *
+thread_int_state_string (thread_int_state state)
+{
+ switch (state)
+ {
+ case THREAD_INT_STOPPED:
+ return "INT_STOPPED";
+
+ case THREAD_INT_RUNNING:
+ return "INT_RUNNING";
+
+ case THREAD_INT_RESUMED_PENDING_STATUS:
+ return "INT_RESUMED_PENDING_STATUS";
+
+ case THREAD_INT_EXITED:
+ return "INT_EXITED";
+ }
+
+ gdb_assert_not_reached ("unknown thread internal state");
+}
+
/* Return a new value for the selected thread's id. Return a value of
0 if no thread is selected. If GLOBAL is true, return the thread's
global number. Otherwise return the per-inferior number. */
diff --git a/gdb/top.c b/gdb/top.c
index 510e65c1d09..7526c07a87b 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -654,7 +654,7 @@ execute_command (const char *p, int from_tty)
we just finished executing did not resume the inferior's execution.
If it did resume the inferior, we will do that check after
the inferior stopped. */
- if (has_stack_frames () && inferior_thread ()->state != THREAD_RUNNING)
+ if (has_stack_frames () && inferior_thread ()->state () != THREAD_RUNNING)
check_frame_language_change ();
cleanup_if_error.release ();
--
2.53.0
^ permalink raw reply [flat|nested] 97+ messages in thread
* Re: [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global
2025-05-19 13:22 ` [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
2025-05-28 19:09 ` Tom Tromey
@ 2026-04-06 19:44 ` Pedro Alves
1 sibling, 0 replies; 97+ messages in thread
From: Pedro Alves @ 2026-04-06 19:44 UTC (permalink / raw)
To: gdb-patches
On 2025-05-19 14:22, Pedro Alves wrote:
> current_process.dr needs to be per-thread for non-stop. Actually, it
> doesn't even need to exist at all.
I've now rebased this patch, which required moving code around since
the x86 Windows watchpoints code meanwhile moved to new files, but all mostly
mechanical, and merged it.
> I don't understand why would windows_nat_target::resume want to call
> SetThreadContext itself. That duplicates things as it is currently
> worrying about setting the debug registers as well. windows_continue
> also does that, and windows_nat_target::resume always calls it. So
> this patch simplifies windows_nat_target::resume too.
I've dropped this whole paragraph from the commit log, as meanwhile Hannes merged a
patch doing the same (commit f0f1ae77fc, "Remove duplicate code from windows_nat_target::resume").
Retested with:
$ make check TESTS="gdb.*/*watch*.exp"
... on Cygwin. No regressions.
Here's what I merged.
From 8de9be5a80d1ff5920e5c515571668b7cfb78ea3 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Tue, 2 May 2023 20:42:35 +0100
Subject: [PATCH] Windows gdb: Eliminate global current_process.dr[8] global
current_process.dr needs to be per-thread for non-stop. Actually, it
doesn't even need to exist at all. We have x86_debug_reg_state
recording intent, and then the
cygwin_get_dr/cygwin_get_dr6/cygwin_get_dr7 functions are registered
as x86_dr_low_type vector functions, so they should return the current
value in the inferior's registers. See this comment in x86-dregs.c:
~~~
/* In non-stop/async, threads can be running while we change the
global dr_mirror (and friends). Say, we set a watchpoint, and
let threads resume. Now, say you delete the watchpoint, or
add/remove watchpoints such that dr_mirror changes while threads
are running. On targets that support non-stop,
inserting/deleting watchpoints updates the global dr_mirror only.
It does not update the real thread's debug registers; that's only
done prior to resume. Instead, 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. Now, say, a thread hit a watchpoint before
having been updated with the new dr_mirror contents, and we
haven't yet handled the corresponding SIGTRAP. If we trusted
dr_mirror below, we'd mistake the real trapped address (from the
last time we had updated debug registers in the thread) with
whatever was currently in dr_mirror. So to fix this, dr_mirror
always represents intention, what we _want_ threads to have in
debug registers. To get at the address and cause of the trap, we
need to read the state the thread still has in its debug
registers.
In sum, always get the current debug register values the current
thread has, instead of trusting the global mirror. If the thread
was running when we last changed watchpoints, the mirror no
longer represents what was set in this thread's debug
registers. */
~~~
This patch makes the Windows native target follow that model as well.
Tromey pointed out that gdb/2388 mentioned in the code being removed
was moved to https://sourceware.org/bugzilla/show_bug.cgi?id=9493 in
the bugzilla migration. I tried the reproducer mentioned there, and
it still works correctly.
Approved-By: Tom Tromey <tom@tromey.com>
Change-Id: Id762d0faa7d5e788402f2ff5adad5352447a7526
commit-id:8a975ed0
---
gdb/nat/windows-nat.h | 1 +
gdb/windows-nat.c | 1 +
gdb/x86-windows-nat.c | 73 ++++++++++++++++++++++--------------------
gdbserver/win32-low.cc | 1 +
4 files changed, 42 insertions(+), 34 deletions(-)
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 5411c73ff97..f4b3669df1b 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -150,6 +150,7 @@ struct windows_process_info
{
/* The process handle */
HANDLE handle = 0;
+ DWORD process_id = 0;
DWORD main_thread_id = 0;
enum gdb_signal last_sig = GDB_SIGNAL_0;
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 507a25199c8..e25ae81f054 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1235,6 +1235,7 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
windows_process->cygwin_load_start = 0;
windows_process->cygwin_load_end = 0;
#endif
+ windows_process->process_id = pid;
memset (&windows_process->current_event, 0,
sizeof (windows_process->current_event));
inf = current_inferior ();
diff --git a/gdb/x86-windows-nat.c b/gdb/x86-windows-nat.c
index f8830809694..c7be192a894 100644
--- a/gdb/x86-windows-nat.c
+++ b/gdb/x86-windows-nat.c
@@ -44,8 +44,6 @@ enum
struct x86_windows_per_inferior : public windows_per_inferior
{
- uintptr_t dr[8] {};
-
/* The function to use in order to determine whether a register is
a segment register or not. */
segment_register_p_ftype *segment_register_p = nullptr;
@@ -77,8 +75,6 @@ static x86_windows_per_inferior x86_windows_process;
void
x86_windows_nat_target::initialize_windows_arch (bool attaching)
{
- memset (x86_windows_process.dr, 0, sizeof (x86_windows_process.dr));
-
#ifdef __x86_64__
x86_windows_process.ignore_first_breakpoint
= !attaching && x86_windows_process.wow64_process;
@@ -113,19 +109,6 @@ x86_windows_nat_target::fill_thread_context (windows_thread_info *th)
{
context->ContextFlags = WindowsContext<decltype(context)>::all;
CHECK (get_thread_context (th->h, context));
-
- /* Copy dr values from that thread.
- But only if there were not modified since last stop.
- PR gdb/2388 */
- if (!th->debug_registers_changed)
- {
- x86_windows_process.dr[0] = context->Dr0;
- x86_windows_process.dr[1] = context->Dr1;
- x86_windows_process.dr[2] = context->Dr2;
- x86_windows_process.dr[3] = context->Dr3;
- x86_windows_process.dr[6] = context->Dr6;
- x86_windows_process.dr[7] = context->Dr7;
- }
});
}
@@ -137,15 +120,18 @@ x86_windows_nat_target::thread_context_continue (windows_thread_info *th,
{
x86_windows_process.with_context (th, [&] (auto *context)
{
+ struct x86_debug_reg_state *state
+ = x86_debug_reg_state (windows_process->process_id);
+
if (th->debug_registers_changed)
{
context->ContextFlags |= WindowsContext<decltype(context)>::debug;
- context->Dr0 = x86_windows_process.dr[0];
- context->Dr1 = x86_windows_process.dr[1];
- context->Dr2 = x86_windows_process.dr[2];
- context->Dr3 = x86_windows_process.dr[3];
+ context->Dr0 = state->dr_mirror[0];
+ context->Dr1 = state->dr_mirror[1];
+ context->Dr2 = state->dr_mirror[2];
+ context->Dr3 = state->dr_mirror[3];
context->Dr6 = DR6_CLEAR_VALUE;
- context->Dr7 = x86_windows_process.dr[7];
+ context->Dr7 = state->dr_control_mirror;
th->debug_registers_changed = false;
}
@@ -301,7 +287,6 @@ cygwin_set_dr (int i, CORE_ADDR addr)
{
if (i < 0 || i > 3)
internal_error (_("Invalid register %d in cygwin_set_dr.\n"), i);
- x86_windows_process.dr[i] = addr;
for (auto &th : x86_windows_process.thread_list)
th->debug_registers_changed = true;
@@ -313,8 +298,6 @@ cygwin_set_dr (int i, CORE_ADDR addr)
static void
cygwin_set_dr7 (unsigned long val)
{
- x86_windows_process.dr[7] = (CORE_ADDR) val;
-
for (auto &th : x86_windows_process.thread_list)
th->debug_registers_changed = true;
}
@@ -324,26 +307,48 @@ cygwin_set_dr7 (unsigned long val)
static CORE_ADDR
cygwin_get_dr (int i)
{
- return x86_windows_process.dr[i];
+ windows_thread_info *th
+ = windows_process->thread_rec (inferior_ptid, DONT_INVALIDATE_CONTEXT);
+
+ return windows_process->with_context (th, [&] (auto *context) -> CORE_ADDR
+ {
+ gdb_assert (context->ContextFlags != 0);
+ switch (i)
+ {
+ case 0:
+ return context->Dr0;
+ case 1:
+ return context->Dr1;
+ case 2:
+ return context->Dr2;
+ case 3:
+ return context->Dr3;
+ case 6:
+ return context->Dr6;
+ case 7:
+ return context->Dr7;
+ };
+
+ gdb_assert_not_reached ("invalid x86 dr register number: %d", i);
+ });
}
-/* Get the value of the DR6 debug status register from the inferior.
- Here we just return the value stored in dr[6]
- by the last call to thread_rec for current_event.dwThreadId id. */
+/* Get the value of the DR6 debug status register from the
+ inferior. */
+
static unsigned long
cygwin_get_dr6 (void)
{
- return (unsigned long) x86_windows_process.dr[6];
+ return cygwin_get_dr (6);
}
-/* Get the value of the DR7 debug status register from the inferior.
- Here we just return the value stored in dr[7] by the last call to
- thread_rec for current_event.dwThreadId id. */
+/* Get the value of the DR7 debug status register from the
+ inferior. */
static unsigned long
cygwin_get_dr7 (void)
{
- return (unsigned long) x86_windows_process.dr[7];
+ return cygwin_get_dr (7);
}
static int
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index b05f6af6b56..ddc5e5475ec 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -291,6 +291,7 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
windows_process.last_sig = GDB_SIGNAL_0;
windows_process.handle = proch;
+ windows_process.process_id = pid;
windows_process.main_thread_id = 0;
windows_process.soft_interrupt_requested = 0;
base-commit: fb1546987b21e3a63e43d4587d320fcbddf83025
--
2.53.0
^ permalink raw reply [flat|nested] 97+ messages in thread
end of thread, other threads:[~2026-04-06 19:45 UTC | newest]
Thread overview: 97+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-05-19 13:22 [PATCH v2 00/47] Windows non-stop mode Pedro Alves
2025-05-19 13:22 ` [PATCH v2 01/47] Make default_gdb_exit resilient to failed closes Pedro Alves
2025-05-19 13:56 ` Andrew Burgess
2025-06-06 13:56 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 02/47] Add test for continuing with some threads running Pedro Alves
2025-05-21 19:36 ` Kevin Buettner
2026-04-02 13:07 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 03/47] infrun: Remove unnecessary currently_stepping call Pedro Alves
2025-05-21 19:44 ` Kevin Buettner
2026-04-02 13:17 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 04/47] infrun: Split currently_stepping, fix sw watchpoints issue Pedro Alves
2026-04-02 13:33 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 05/47] thread_info::executing+resumed -> thread_info::internal_state Pedro Alves
2026-04-06 18:01 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 06/47] Windows gdb: Dead code in windows_nat_target::do_initial_windows_stuff Pedro Alves
2025-05-19 13:22 ` [PATCH v2 07/47] Windows gdb: Eliminate global current_process.dr[8] global Pedro Alves
2025-05-28 19:09 ` Tom Tromey
2026-04-06 19:44 ` Pedro Alves
2025-05-19 13:22 ` [PATCH v2 08/47] Windows gdb+gdbserver: New find_thread, replaces thread_rec(DONT_INVALIDATE_CONTEXT) Pedro Alves
2025-05-19 13:22 ` [PATCH v2 09/47] Windows gdb: handle_output_debug_string return type Pedro Alves
2025-05-19 13:22 ` [PATCH v2 10/47] Windows gdb: Eliminate reload_context Pedro Alves
2025-05-19 13:22 ` [PATCH v2 11/47] Windows gdb+gdbserver: Eliminate thread_rec(INVALIDATE_CONTEXT) calls Pedro Alves
2025-05-19 13:22 ` [PATCH v2 12/47] Windows gdb+gdbserver: Eliminate DONT_SUSPEND Pedro Alves
2025-05-19 13:22 ` [PATCH v2 13/47] Windows gdb+gdbserver: Eliminate windows_process_info::thread_rec Pedro Alves
2025-05-19 13:22 ` [PATCH v2 14/47] Windows gdb: Simplify windows_nat_target::wait Pedro Alves
2025-05-28 19:16 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 15/47] Windows gdb+gdbserver: Move suspending thread to when returning event Pedro Alves
2025-05-28 19:17 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 16/47] Windows gdb: Introduce continue_last_debug_event_main_thread Pedro Alves
2025-05-28 19:18 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 17/47] Windows gdb: Introduce windows_continue_flags Pedro Alves
2025-05-19 13:22 ` [PATCH v2 18/47] Windows gdb: Factor code out of windows_nat_target::windows_continue Pedro Alves
2025-05-19 13:22 ` [PATCH v2 19/47] Windows gdb: Pending stop and current_event Pedro Alves
2025-05-19 13:22 ` [PATCH v2 20/47] Windows gdb+gdbserver: Elim desired_stop_thread_id / rework pending_stops Pedro Alves
2025-05-30 20:41 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 21/47] Windows gdb+gdbserver: Introduce get_last_debug_event_ptid Pedro Alves
2025-05-28 19:21 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 22/47] Windows gdb: Can't pass signal to thread other than last stopped thread Pedro Alves
2025-05-28 19:22 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 23/47] Windows gdbserver: Fix scheduler-locking Pedro Alves
2025-05-30 20:37 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 24/47] Windows gdb: Enable "set scheduler-locking on" Pedro Alves
2025-05-19 13:22 ` [PATCH v2 25/47] Windows gdbserver: Eliminate soft-interrupt mechanism Pedro Alves
2025-05-19 13:22 ` [PATCH v2 26/47] Windows gdb+gdbserver: Make current_event per-thread state Pedro Alves
2025-05-28 19:30 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 27/47] Windows gdb+gdbserver: Make last_sig " Pedro Alves
2025-05-28 19:31 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 28/47] Windows gdb+gdbserver: Make siginfo_er " Pedro Alves
2025-05-28 19:33 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 29/47] Add backpointer from windows_thread_info to windows_process_info Pedro Alves
2025-05-19 13:22 ` [PATCH v2 30/47] Windows gdb+gdbserver: Share $_siginfo reading code Pedro Alves
2025-05-19 13:22 ` [PATCH v2 31/47] Windows gdb+gdbserver: Eliminate struct pending_stop Pedro Alves
2025-05-28 19:36 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 32/47] Windows gdb: Change serial_event management Pedro Alves
2025-05-28 19:37 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 33/47] Windows gdb: cygwin_set_dr => windows_set_dr, etc Pedro Alves
2025-05-19 13:22 ` [PATCH v2 34/47] Windows gdb: Avoid writing debug registers if watchpoint hit pending Pedro Alves
2025-05-30 20:43 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 35/47] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
2025-05-19 13:22 ` [PATCH v2 36/47] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
2025-05-28 19:44 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 37/47] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
2025-05-28 19:52 ` Tom Tromey
2025-05-19 13:22 ` [PATCH v2 38/47] Introduce windows_nat::event_code_to_string Pedro Alves
2025-05-28 19:53 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 39/47] Windows gdb: Add non-stop support Pedro Alves
2025-06-05 16:21 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 40/47] Windows gdb: Eliminate invalidate_context Pedro Alves
2025-05-28 19:54 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 41/47] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
2025-05-30 20:50 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 42/47] gdb_test_multiple: Anchor prompt match if -lbl Pedro Alves
2025-05-21 15:19 ` Tom de Vries
2025-05-27 22:41 ` Pedro Alves
2025-05-27 23:20 ` Pedro Alves
2025-05-28 11:59 ` [PATCH v2] of " Pedro Alves
2025-06-05 16:37 ` Pedro Alves
2025-06-05 17:20 ` [PATCH v3] " Pedro Alves
2025-06-06 9:58 ` Tom de Vries
2025-06-06 13:53 ` Pedro Alves
2025-05-19 13:23 ` [PATCH v2 43/47] Windows gdb: extra thread info => show exiting Pedro Alves
2025-05-28 19:58 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 44/47] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
2025-05-29 16:09 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 45/47] infrun: with AS+NS, prefer process exit over thread exit Pedro Alves
2025-05-19 13:23 ` [PATCH v2 46/47] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
2025-05-29 16:02 ` Tom Tromey
2025-05-19 13:23 ` [PATCH v2 47/47] Mention Windows scheduler-locking and non-stop support in NEWS Pedro Alves
2025-05-19 14:07 ` Eli Zaretskii
2025-06-05 17:57 ` [PATCH v2 00/47] Windows non-stop mode Tom Tromey
2025-06-11 22:06 ` [PATCH] Improve attach on Windows (was: Re: [PATCH v2 00/47] Windows non-stop mode) Pedro Alves
2026-04-02 12:21 ` [PATCH] Improve attach on Windows Pedro Alves
2026-04-02 18:52 ` Tom Tromey
2025-06-11 23:51 ` [PATCH v2 00/47] Windows non-stop mode Pedro Alves
2025-06-12 19:23 ` Tom Tromey
2025-06-13 10:34 ` Pedro Alves
2025-06-13 14:23 ` Tom Tromey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox