From: Pedro Alves <pedro@palves.net>
To: gdb-patches@sourceware.org
Subject: [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit
Date: Wed, 29 Apr 2026 21:15:05 +0100 [thread overview]
Message-ID: <20260429201507.480870-10-pedro@palves.net> (raw)
In-Reply-To: <20260429201507.480870-1-pedro@palves.net>
This patch fixes gdb.base/ending-run.exp for Windows when the target
backend supports notifying infrun about thread exit events (which is
added by the Windows non-stop support, later).
Without this patch, and with the Windows target in non-stop mode
("maint set target-non-stop on"), we get, when stepping out of main:
(gdb) PASS: gdb.base/ending-run.exp: Step to return
next
32 }
(gdb) next
[Thread 7956.0x2658 exited]
[Thread 7956.0x2500 exited]
[Thread 7956.0x2798 exited]
Command aborted, thread exited.
(gdb) FAIL: gdb.base/ending-run.exp: step out of main
With the patch, we get:
(gdb) next
[Thread 9424.0x40c exited]
[Inferior 1 (process 9424) exited normally]
(gdb) PASS: gdb.base/ending-run.exp: step out of main
In the failing case, what happens is that "next" enables
target_thread_events. Then, the main thread causes the whole process
to exit. On Windows, that makes the main thread report a thread exit
event, followed by thread exit events for all other threads, except
the last thread that happens to be the one that exits last. That last
one reports an exit-process event instead.
Since "next" enabled target_thread_events, the Windows target backend
reports the main thread's exit event to infrun. And then, since the
thread that was stepping reported a thread-exit, GDB aborts the "next"
command.
Stepping out of main is a very common thing to do, and I think
reporting the thread exit in this case when the whole process is
exiting isn't very useful. I think we can do better. So instead, if
we're about to report a thread exit in all-stop mode with the backend
in non-stop mode, and while stopping all threads, we see a
whole-process-exit event, prefer processing that event instead of
reporting the original thread exit.
A similar issue can be triggered on GNU/Linux as well, if we step over
an exit syscall that is called by any thread other than main. This
scenario is exercised by the new testcase added by this patch.
Without the patch, the testcase shows:
(gdb) next
[Thread 0x7ffff7a00640 (LWP 3207243) exited]
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
Command aborted, thread exited.
Cannot remove breakpoints because program is no longer writable.
Further execution is probably impossible.
(gdb)
This is fixed for GNU/Linux by the patch, which results in:
(gdb) next
[Thread 0x7ffff7a00640 (LWP 3230550) exited]
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
warning: error removing breakpoint 0 at 0x5555555551c3
[Inferior 1 (process 3230539) exited normally]
(gdb)
Pure all-stop targets (such as GNU/Linux GDBserver unless you force
non-stop with "maint set target-non-stop on") will unfortunately still
have the "Further execution is probably impossible." behavior, because
GDB can't see the process-exit event until the target is re-resumed.
That's unfortunate, but I don't think that should prevent improving
non-stop targets. (And eventually I would like remote targets to be
always "maint set target-non-stop on" by default if possible, too.)
Change-Id: I56559f13e04aeafd812d15e4b408c8337bca5294
---
gdb/infrun.c | 209 ++++++++++++------
.../gdb.threads/step-over-process-exit.c | 49 ++++
.../gdb.threads/step-over-process-exit.exp | 66 ++++++
3 files changed, 255 insertions(+), 69 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.c
create mode 100644 gdb/testsuite/gdb.threads/step-over-process-exit.exp
diff --git a/gdb/infrun.c b/gdb/infrun.c
index aed66bf844e..11c5d5214d6 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -109,6 +109,8 @@ static bool step_over_info_valid_p (void);
static bool schedlock_applies (struct thread_info *tp);
+static void handle_process_exited (struct execution_control_state *ecs);
+
/* Asynchronous signal handler registered as event loop source for
when we have pending events ready to be passed to the core. */
static struct async_event_handler *infrun_async_inferior_event_token;
@@ -4778,7 +4780,68 @@ fetch_inferior_event ()
don't want to stop all the other threads. */
if (ecs.event_thread == nullptr
|| !ecs.event_thread->control.in_cond_eval)
- stop_all_threads_if_all_stop_mode ();
+ {
+ stop_all_threads_if_all_stop_mode ();
+
+ /* Say the user does "next" over an exit(0) call, or
+ out of main, either of which make the whole process
+ exit. If the target supports target_thread_events,
+ then that is activated while "next" is in progress,
+ and consequently we may be processing a thread-exit
+ event (caused by the process exit) for a thread
+ that reported its exit before the
+ whole-process-exit event is reported for another
+ thread (which is normally going to be the last
+ event out of the inferior, but we don't know for
+ which thread it will be).
+
+ If we're in 'all-stop on top of non-stop' mode, and
+ indeed the inferior process is exiting, then the
+ stop_all_threads_if_all_stop_mode call above must
+ have seen the process-exit event, as it will see
+ one stop for each and every (running) thread of the
+ process. Look at the pending statuses of all
+ threads, and see if we have a process-exit status.
+ If so, prefer handling it now and report the
+ inferior exit to the user instead of reporting the
+ original thread exit.
+
+ Do not do this if are handling any other kind of
+ event, like e.g., a breakpoint hit, which the user
+ may be interested in knowing was hit before the
+ process exited. If we ever have a "catch
+ thread-exit" or something similar, we may want to
+ skip this "prefer process-exit" if such a
+ catchpoint is installed. */
+ if (ecs.ws.kind () == TARGET_WAITKIND_THREAD_EXITED
+ && !non_stop && exists_non_stop_target ())
+ {
+ for (thread_info &thread : inf->non_exited_threads ())
+ {
+ if (thread.has_pending_waitstatus ()
+ && ((thread.pending_waitstatus ().kind ()
+ == TARGET_WAITKIND_EXITED)
+ || (thread.pending_waitstatus ().kind ()
+ == TARGET_WAITKIND_SIGNALLED)))
+ {
+ /* Found a pending process-exit event.
+ Prefer handling and reporting it now
+ over the thread-exit event. */
+ infrun_debug_printf
+ ("found pending process-exit event, preferring it");
+ ecs.ws = thread.pending_waitstatus ();
+ thread.clear_pending_waitstatus ();
+ ecs.event_thread = nullptr;
+ ecs.ptid = thread.ptid;
+ /* Re-record the last target status. */
+ set_last_target_status (ecs.target, ecs.ptid,
+ ecs.ws);
+ handle_process_exited (&ecs);
+ break;
+ }
+ }
+ }
+ }
clean_up_just_stopped_threads_fsms (&ecs);
@@ -6104,6 +6167,81 @@ handle_thread_exited (execution_control_state *ecs)
return true;
}
+/* Handle a process exit event. */
+
+static void
+handle_process_exited (execution_control_state *ecs)
+{
+ /* Depending on the system, ecs->ptid may point to a thread or to a
+ process. On some targets, target_mourn_inferior may need to have
+ access to the just-exited thread. That is the case of
+ GNU/Linux's "checkpoint" support, for example. Switch context
+ appropriately. */
+ thread_info *thr = ecs->target->find_thread (ecs->ptid);
+ if (thr != nullptr)
+ switch_to_thread (thr);
+ else
+ {
+ inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
+ switch_to_inferior_no_thread (inf);
+ }
+
+ handle_vfork_child_exec_or_exit (0);
+ target_terminal::ours (); /* Must do this before mourn anyway. */
+
+ /* Clearing any previous state of convenience variables. */
+ clear_exit_convenience_vars ();
+
+ if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
+ {
+ /* Record the exit code in the convenience variable $_exitcode,
+ so that the user can inspect this again later. */
+ set_internalvar_integer (lookup_internalvar ("_exitcode"),
+ (LONGEST) ecs->ws.exit_status ());
+
+ /* Also record this in the inferior itself. */
+ current_inferior ()->has_exit_code = true;
+ current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
+
+ /* Support the --return-child-result option. */
+ return_child_result_value = ecs->ws.exit_status ();
+
+ interps_notify_exited (ecs->ws.exit_status ());
+ }
+ else
+ {
+ struct gdbarch *gdbarch = current_inferior ()->arch ();
+
+ if (gdbarch_gdb_signal_to_target_p (gdbarch))
+ {
+ /* Set the value of the internal variable $_exitsignal,
+ which holds the signal uncaught by the inferior. */
+ set_internalvar_integer (lookup_internalvar ("_exitsignal"),
+ gdbarch_gdb_signal_to_target (gdbarch,
+ ecs->ws.sig ()));
+ }
+ else
+ {
+ /* We don't have access to the target's method used for
+ converting between signal numbers (GDB's internal
+ representation <-> target's representation).
+ Therefore, we cannot do a good job at displaying this
+ information to the user. It's better to just warn
+ her about it (if infrun debugging is enabled), and
+ give up. */
+ infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
+ "signal number.");
+ }
+
+ interps_notify_signal_exited (ecs->ws.sig ());
+ }
+
+ gdb_flush (gdb_stdout);
+ target_mourn_inferior (inferior_ptid);
+ stop_print_frame = false;
+ stop_waiting (ecs);
+}
+
/* Given an execution control state that has been freshly filled in by
an event from the inferior, figure out what it means and take
appropriate action.
@@ -6313,74 +6451,7 @@ handle_inferior_event (struct execution_control_state *ecs)
case TARGET_WAITKIND_EXITED:
case TARGET_WAITKIND_SIGNALLED:
- {
- /* Depending on the system, ecs->ptid may point to a thread or
- to a process. On some targets, target_mourn_inferior may
- need to have access to the just-exited thread. That is the
- case of GNU/Linux's "checkpoint" support, for example.
- Call the switch_to_xxx routine as appropriate. */
- thread_info *thr = ecs->target->find_thread (ecs->ptid);
- if (thr != nullptr)
- switch_to_thread (thr);
- else
- {
- inferior *inf = find_inferior_ptid (ecs->target, ecs->ptid);
- switch_to_inferior_no_thread (inf);
- }
- }
- handle_vfork_child_exec_or_exit (0);
- target_terminal::ours (); /* Must do this before mourn anyway. */
-
- /* Clearing any previous state of convenience variables. */
- clear_exit_convenience_vars ();
-
- if (ecs->ws.kind () == TARGET_WAITKIND_EXITED)
- {
- /* Record the exit code in the convenience variable $_exitcode, so
- that the user can inspect this again later. */
- set_internalvar_integer (lookup_internalvar ("_exitcode"),
- (LONGEST) ecs->ws.exit_status ());
-
- /* Also record this in the inferior itself. */
- current_inferior ()->has_exit_code = true;
- current_inferior ()->exit_code = (LONGEST) ecs->ws.exit_status ();
-
- /* Support the --return-child-result option. */
- return_child_result_value = ecs->ws.exit_status ();
-
- interps_notify_exited (ecs->ws.exit_status ());
- }
- else
- {
- struct gdbarch *gdbarch = current_inferior ()->arch ();
-
- if (gdbarch_gdb_signal_to_target_p (gdbarch))
- {
- /* Set the value of the internal variable $_exitsignal,
- which holds the signal uncaught by the inferior. */
- set_internalvar_integer (lookup_internalvar ("_exitsignal"),
- gdbarch_gdb_signal_to_target (gdbarch,
- ecs->ws.sig ()));
- }
- else
- {
- /* We don't have access to the target's method used for
- converting between signal numbers (GDB's internal
- representation <-> target's representation).
- Therefore, we cannot do a good job at displaying this
- information to the user. It's better to just warn
- her about it (if infrun debugging is enabled), and
- give up. */
- infrun_debug_printf ("Cannot fill $_exitsignal with the correct "
- "signal number.");
- }
-
- interps_notify_signal_exited (ecs->ws.sig ());
- }
-
- gdb_flush (gdb_stdout);
- target_mourn_inferior (inferior_ptid);
- stop_print_frame = false;
+ handle_process_exited (ecs);
stop_waiting (ecs);
return;
diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.c b/gdb/testsuite/gdb.threads/step-over-process-exit.c
new file mode 100644
index 00000000000..a9b86751596
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-process-exit.c
@@ -0,0 +1,49 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2025-2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+volatile int other_thread_exits = 0;
+
+static void *
+thread_function (void *arg)
+{
+ if (other_thread_exits)
+ exit (0); /* break here other */
+
+ while (1)
+ sleep (1);
+}
+
+int
+main ()
+{
+ pthread_t thread;
+
+ alarm (30);
+
+ pthread_create (&thread, NULL, thread_function, NULL);
+
+ if (!other_thread_exits)
+ exit (0); /* break here main */
+
+ while (1)
+ sleep (1);
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/step-over-process-exit.exp b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
new file mode 100644
index 00000000000..6c98ebe3956
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/step-over-process-exit.exp
@@ -0,0 +1,66 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2025-2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test stepping over an exit syscall from both the main thread, and a
+# non-main thread.
+
+if { [target_info exists exit_is_reliable] } {
+ set exit_is_reliable [target_info exit_is_reliable]
+} else {
+ set exit_is_reliable [expr {![target_info exists use_gdb_stub]}]
+}
+require {expr {$exit_is_reliable}}
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile {debug pthread}] } {
+ return -1
+}
+
+# WHICH is which thread exits the process. Can be "main" for main
+# thread, or "other" for the non-main thread.
+
+proc test {which} {
+ if {![runto_main]} {
+ return -1
+ }
+
+ set other [expr {$which == "other"}]
+ gdb_test "p other_thread_exits = $other" " = $other"
+
+ set break_line [gdb_get_line_number "break here $which"]
+ gdb_breakpoint $break_line
+ gdb_continue_to_breakpoint "exit syscall"
+
+ set target_non_stop [is_target_non_stop]
+
+ gdb_test_multiple "next" "" {
+ -re -wrap "$::inferior_exited_re normally\\\]" {
+ pass $gdb_test_name
+ }
+ -re -wrap "Further execution is probably impossible\\." {
+ # With a target in all-stop, this is the best we can do.
+ # We should not see this with a target backend in non-stop
+ # mode, however.
+ gdb_assert !$target_non_stop $gdb_test_name
+ }
+ }
+}
+
+foreach_with_prefix which {main other} {
+ test $which
+}
--
2.53.0
next prev parent reply other threads:[~2026-04-29 20:16 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-29 20:14 [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-04-29 20:14 ` [PATCH v3 01/11] Windows gdb+gdbserver: Check whether DBG_REPLY_LATER is available Pedro Alves
2026-04-29 20:14 ` [PATCH v3 02/11] linux-nat: Factor out get_detach_signal code to common code Pedro Alves
2026-04-29 20:14 ` [PATCH v3 03/11] Windows GDB: make windows_thread_info be private thread_info data Pedro Alves
2026-04-29 20:15 ` [PATCH v3 04/11] Introduce windows_nat::event_code_to_string Pedro Alves
2026-04-29 20:15 ` [PATCH v3 05/11] Windows gdb: Add non-stop support Pedro Alves
2026-04-29 20:15 ` [PATCH v3 06/11] Windows gdb: Watchpoints while running (internal vs external stops) Pedro Alves
2026-04-29 20:15 ` [PATCH v3 07/11] Windows gdb: extra thread info => show exiting Pedro Alves
2026-04-29 20:15 ` [PATCH v3 08/11] Add gdb.threads/leader-exit-schedlock.exp Pedro Alves
2026-04-29 20:15 ` Pedro Alves [this message]
2026-05-06 10:01 ` [PATCH v3 09/11] infrun: with AS+NS, prefer process exit over thread exit Bouhaouel, Mohamed
2026-05-08 21:37 ` Pedro Alves
2026-05-11 11:58 ` Bouhaouel, Mohamed
2026-04-29 20:15 ` [PATCH v3 10/11] Windows gdb: Always non-stop (default to "maint set target-non-stop on") Pedro Alves
2026-04-29 20:15 ` [PATCH v3 11/11] Mention Windows non-stop support in NEWS Pedro Alves
2026-04-30 5:55 ` [PATCH v3 00/11] Windows non-stop mode Eli Zaretskii
2026-04-30 10:13 ` Pedro Alves
2026-04-30 11:14 ` Eli Zaretskii
2026-04-30 12:01 ` Pedro Alves
2026-04-30 14:15 ` [PATCH] Clarify "maint set target-non-stop" in GDB manual (Re: [PATCH v3 00/11] Windows non-stop mode) Pedro Alves
2026-04-30 15:09 ` Eli Zaretskii
2026-04-30 16:18 ` [PATCH v2] " Pedro Alves
2026-04-30 16:27 ` Eli Zaretskii
2026-04-30 16:33 ` Pedro Alves
2026-04-30 17:45 ` [PATCH v3 00/11] Windows non-stop mode Pedro Alves
2026-05-08 18:43 ` Tom Tromey
2026-05-08 21:27 ` Pedro Alves
2026-05-22 0:22 ` Pedro Alves
2026-06-02 19:00 ` Tom Tromey
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260429201507.480870-10-pedro@palves.net \
--to=pedro@palves.net \
--cc=gdb-patches@sourceware.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox