Don't stop when removable inferior exits
Commit Message
A while back I noticed that gdb would issue a stop if an inferior in a
multi-inferior process tree exited. For instance, if you used "gdb
make" and then "run", if one of the compilations exited, this would
show up as a stop.
However, this seems strange. Those intermediate processes are
interesting, but (IMO) only if they crash or hit a breakpoint --
exiting is normal.
This patch arranges to suppress these stops, but only for removable
inferiors, with the reasoning being that non-removable inferiors are
the ones that the user explicitly asked to 'run', and so returning to
the prompt when that completes seems sensible.
Regression tested on x86-64 Fedora 40.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19471
---
gdb/inferior.c | 16 +++++++
gdb/inferior.h | 3 ++
gdb/infrun.c | 16 ++++++-
.../gdb.multi/no-stop-removable-exit.exp | 45 +++++++++++++++++++
4 files changed, 78 insertions(+), 2 deletions(-)
create mode 100644 gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
Comments
Tom Tromey <tom@tromey.com> writes:
> A while back I noticed that gdb would issue a stop if an inferior in a
> multi-inferior process tree exited. For instance, if you used "gdb
> make" and then "run", if one of the compilations exited, this would
> show up as a stop.
>
> However, this seems strange. Those intermediate processes are
> interesting, but (IMO) only if they crash or hit a breakpoint --
> exiting is normal.
>
> This patch arranges to suppress these stops, but only for removable
> inferiors, with the reasoning being that non-removable inferiors are
> the ones that the user explicitly asked to 'run', and so returning to
> the prompt when that completes seems sensible.
That all makes sense.
>
> Regression tested on x86-64 Fedora 40.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19471
> ---
> gdb/inferior.c | 16 +++++++
> gdb/inferior.h | 3 ++
> gdb/infrun.c | 16 ++++++-
> .../gdb.multi/no-stop-removable-exit.exp | 45 +++++++++++++++++++
> 4 files changed, 78 insertions(+), 2 deletions(-)
> create mode 100644 gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
>
> diff --git a/gdb/inferior.c b/gdb/inferior.c
> index 8131f23b938..8f62ff84a54 100644
> --- a/gdb/inferior.c
> +++ b/gdb/inferior.c
> @@ -477,6 +477,22 @@ have_live_inferiors (void)
> return number_of_live_inferiors (NULL) > 0;
> }
>
> +/* Return true if there is at least one executing thread. */
> +
> +bool
> +any_executing_threads ()
> +{
> + for (inferior *inf : all_non_exited_inferiors (nullptr))
> + if (inf->has_execution ())
> + for (thread_info &tp ATTRIBUTE_UNUSED : inf->non_exited_threads ())
Is the ATTRIBUTE_UNUSED needed here? It seems like TP is used.
> + {
> + if (tp.executing ())
> + return true;
> + }
> +
> + return false;
> +}
> +
> /* Prune away any unused inferiors, and then prune away no longer used
> program spaces. */
>
> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index 5b499a207b4..9e9633a1905 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -765,6 +765,9 @@ extern int number_of_live_inferiors (process_stratum_target *proc_target);
> (not cores, not executables, real live processes). */
> extern int have_live_inferiors (void);
>
> +/* Return true if there is at least one executing thread. */
> +extern bool any_executing_threads ();
> +
> /* Save/restore the current inferior. */
>
> class scoped_restore_current_inferior
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index bd114e16b80..f4e51bcb34e 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -6343,8 +6343,20 @@ handle_inferior_event (struct execution_control_state *ecs)
>
> gdb_flush (gdb_stdout);
> target_mourn_inferior (inferior_ptid);
> - stop_print_frame = false;
> - stop_waiting (ecs);
> + /* When a removable inferior exits, there's ordinarily no reason
> + to report a stop, because this is normally an inferior that
> + was started by some other process, and not explicitly managed
> + by the user. However, it's possible for a removable inferior
> + to be the last live inferior, and in this case reporting a
> + stop is needed, as otherwise the user will see a message
> + about "unwaited-for children". */
> + if (current_inferior ()->removable && any_executing_threads ())
I removed the `any_executing_threads ()` and the new test still passes.
Would it be possible to extend the test to cover this case too?
> + prepare_to_wait (ecs);
> + else
> + {
> + stop_print_frame = false;
> + stop_waiting (ecs);
> + }
> return;
>
> case TARGET_WAITKIND_FORKED:
> diff --git a/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp b/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> new file mode 100644
> index 00000000000..111351cf299
> --- /dev/null
> +++ b/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> @@ -0,0 +1,45 @@
> +# Copyright 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 that removable inferiors don't cause a stop when they exit.
> +
> +# This test program conveniently launches another inferior.
> +standard_testfile watchpoint-multi-exit.c
> +
> +if {[prepare_for_testing "failed to build" $testfile $srcfile {debug}]} {
> + return
> +}
> +
> +gdb_test_no_output "set detach-on-fork off"
> +gdb_test_no_output "set schedule-multiple on"
> +
> +gdb_test_multiple "run" "do not stop when child exits" {
This is going to cause errors when using
--target_board=native-gdbserver. I'll take a moment to advertise 'make
check-all-boards'; there's some text in testsuite/README on how to set
it up.
Thanks,
Andrew
> + -re "Starting program: \[^\n\]*\r\n" {
> + exp_continue
> + }
> + -re "\\\[New inferior 2\[^\n\]\\\]\r\n" {
> + exp_continue
> + }
> + -re "\\\[Inferior 2 .*exited normally\\\]\r\n" {
> + exp_continue
> + }
> + -re -wrap "\\\[Inferior 1 .*exited normally\\\]" {
> + pass $gdb_test_name
> + }
> + -re -wrap "" {
> + # Any other stop is a failure.
> + fail $gdb_test_name
> + }
> +}
> --
> 2.49.0
On Tuesday, January 6, 2026 3:01 PM, Andrew Burgess wrote:
> Tom Tromey <tom@tromey.com> writes:
>
> > A while back I noticed that gdb would issue a stop if an inferior in a
> > multi-inferior process tree exited. For instance, if you used "gdb
> > make" and then "run", if one of the compilations exited, this would
> > show up as a stop.
> >
> > However, this seems strange. Those intermediate processes are
> > interesting, but (IMO) only if they crash or hit a breakpoint --
> > exiting is normal.
> >
> > This patch arranges to suppress these stops, but only for removable
> > inferiors, with the reasoning being that non-removable inferiors are
> > the ones that the user explicitly asked to 'run', and so returning to
> > the prompt when that completes seems sensible.
>
> That all makes sense.
>
> >
> > Regression tested on x86-64 Fedora 40.
> >
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=19471
> > ---
> > gdb/inferior.c | 16 +++++++
> > gdb/inferior.h | 3 ++
> > gdb/infrun.c | 16 ++++++-
> > .../gdb.multi/no-stop-removable-exit.exp | 45
> +++++++++++++++++++
> > 4 files changed, 78 insertions(+), 2 deletions(-)
> > create mode 100644 gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> >
> > diff --git a/gdb/inferior.c b/gdb/inferior.c
> > index 8131f23b938..8f62ff84a54 100644
> > --- a/gdb/inferior.c
> > +++ b/gdb/inferior.c
> > @@ -477,6 +477,22 @@ have_live_inferiors (void)
> > return number_of_live_inferiors (NULL) > 0;
> > }
> >
> > +/* Return true if there is at least one executing thread. */
> > +
> > +bool
> > +any_executing_threads ()
> > +{
> > + for (inferior *inf : all_non_exited_inferiors (nullptr))
> > + if (inf->has_execution ())
> > + for (thread_info &tp ATTRIBUTE_UNUSED : inf->non_exited_threads
> ())
>
> Is the ATTRIBUTE_UNUSED needed here? It seems like TP is used.
>
> > + {
> > + if (tp.executing ())
> > + return true;
> > + }
> > +
> > + return false;
> > +}
Is there a reason we cannot iterate over `all_non_exited_process_targets ()`
and check `threads_executing` instead of checking each thread?
> > +
> > /* Prune away any unused inferiors, and then prune away no longer
> used
> > program spaces. */
> >
> > diff --git a/gdb/inferior.h b/gdb/inferior.h
> > index 5b499a207b4..9e9633a1905 100644
> > --- a/gdb/inferior.h
> > +++ b/gdb/inferior.h
> > @@ -765,6 +765,9 @@ extern int number_of_live_inferiors
> (process_stratum_target *proc_target);
> > (not cores, not executables, real live processes). */
> > extern int have_live_inferiors (void);
> >
> > +/* Return true if there is at least one executing thread. */
> > +extern bool any_executing_threads ();
> > +
> > /* Save/restore the current inferior. */
> >
> > class scoped_restore_current_inferior
> > diff --git a/gdb/infrun.c b/gdb/infrun.c
> > index bd114e16b80..f4e51bcb34e 100644
> > --- a/gdb/infrun.c
> > +++ b/gdb/infrun.c
> > @@ -6343,8 +6343,20 @@ handle_inferior_event (struct
> execution_control_state *ecs)
> >
> > gdb_flush (gdb_stdout);
> > target_mourn_inferior (inferior_ptid);
> > - stop_print_frame = false;
> > - stop_waiting (ecs);
> > + /* When a removable inferior exits, there's ordinarily no
> reason
> > + to report a stop, because this is normally an inferior that
> > + was started by some other process, and not explicitly managed
> > + by the user. However, it's possible for a removable inferior
> > + to be the last live inferior, and in this case reporting a
> > + stop is needed, as otherwise the user will see a message
> > + about "unwaited-for children". */
> > + if (current_inferior ()->removable && any_executing_threads ())
>
> I removed the `any_executing_threads ()` and the new test still passes.
> Would it be possible to extend the test to cover this case too?
>
> > + prepare_to_wait (ecs);
> > + else
> > + {
> > + stop_print_frame = false;
> > + stop_waiting (ecs);
> > + }
> > return;
> >
> > case TARGET_WAITKIND_FORKED:
> > diff --git a/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> b/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> > new file mode 100644
> > index 00000000000..111351cf299
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.multi/no-stop-removable-exit.exp
> > @@ -0,0 +1,45 @@
> > +# Copyright 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 that removable inferiors don't cause a stop when they exit.
> > +
> > +# This test program conveniently launches another inferior.
> > +standard_testfile watchpoint-multi-exit.c
> > +
> > +if {[prepare_for_testing "failed to build" $testfile $srcfile
> {debug}]} {
> > + return
> > +}
> > +
> > +gdb_test_no_output "set detach-on-fork off"
> > +gdb_test_no_output "set schedule-multiple on"
> > +
> > +gdb_test_multiple "run" "do not stop when child exits" {
>
> This is going to cause errors when using
> --target_board=native-gdbserver. I'll take a moment to advertise 'make
> check-all-boards'; there's some text in testsuite/README on how to set
> it up.
>
> Thanks,
> Andrew
>
> > + -re "Starting program: \[^\n\]*\r\n" {
Using `\[^\r\n\]*` is much more common than `\[^\n\]*` in the testsuite.
> > + exp_continue
> > + }
> > + -re "\\\[New inferior 2\[^\n\]\\\]\r\n" {
> > + exp_continue
> > + }
> > + -re "\\\[Inferior 2 .*exited normally\\\]\r\n" {
I'd suggest using `\[^\r\n\]*` instead of `.*`, because otherwise we may
too eagerly match until Inferior 1's "exited normally", causing failures.
Thanks,
-Baris
> > + exp_continue
> > + }
> > + -re -wrap "\\\[Inferior 1 .*exited normally\\\]" {
> > + pass $gdb_test_name
> > + }
> > + -re -wrap "" {
> > + # Any other stop is a failure.
> > + fail $gdb_test_name
> > + }
> > +}
> > --
> > 2.49.0
Intel Deutschland GmbH
Registered Address: Dornacher Straße 1, 85622 Feldkirchen, Germany
Tel: +49 89 991 430, www.intel.de
Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell
Chairperson of the Supervisory Board: Nicole Lau
Registered Seat: Munich
Commercial Register: Amtsgericht München HRB 186928
>> + for (thread_info &tp ATTRIBUTE_UNUSED : inf->non_exited_threads ())
Andrew> Is the ATTRIBUTE_UNUSED needed here? It seems like TP is used.
Oops, I fixed this.
>> + if (current_inferior ()->removable && any_executing_threads ())
Andrew> I removed the `any_executing_threads ()` and the new test still passes.
Andrew> Would it be possible to extend the test to cover this case too?
I believe if you remove this, there's another test that fails -- one of
the ones in gdb.python I believe.
>> +gdb_test_multiple "run" "do not stop when child exits" {
Andrew> This is going to cause errors when using
Andrew> --target_board=native-gdbserver. I'll take a moment to advertise 'make
Andrew> check-all-boards'; there's some text in testsuite/README on how to set
Andrew> it up.
I'll take a look.
Tom
@@ -477,6 +477,22 @@ have_live_inferiors (void)
return number_of_live_inferiors (NULL) > 0;
}
+/* Return true if there is at least one executing thread. */
+
+bool
+any_executing_threads ()
+{
+ for (inferior *inf : all_non_exited_inferiors (nullptr))
+ if (inf->has_execution ())
+ for (thread_info &tp ATTRIBUTE_UNUSED : inf->non_exited_threads ())
+ {
+ if (tp.executing ())
+ return true;
+ }
+
+ return false;
+}
+
/* Prune away any unused inferiors, and then prune away no longer used
program spaces. */
@@ -765,6 +765,9 @@ extern int number_of_live_inferiors (process_stratum_target *proc_target);
(not cores, not executables, real live processes). */
extern int have_live_inferiors (void);
+/* Return true if there is at least one executing thread. */
+extern bool any_executing_threads ();
+
/* Save/restore the current inferior. */
class scoped_restore_current_inferior
@@ -6343,8 +6343,20 @@ handle_inferior_event (struct execution_control_state *ecs)
gdb_flush (gdb_stdout);
target_mourn_inferior (inferior_ptid);
- stop_print_frame = false;
- stop_waiting (ecs);
+ /* When a removable inferior exits, there's ordinarily no reason
+ to report a stop, because this is normally an inferior that
+ was started by some other process, and not explicitly managed
+ by the user. However, it's possible for a removable inferior
+ to be the last live inferior, and in this case reporting a
+ stop is needed, as otherwise the user will see a message
+ about "unwaited-for children". */
+ if (current_inferior ()->removable && any_executing_threads ())
+ prepare_to_wait (ecs);
+ else
+ {
+ stop_print_frame = false;
+ stop_waiting (ecs);
+ }
return;
case TARGET_WAITKIND_FORKED:
new file mode 100644
@@ -0,0 +1,45 @@
+# Copyright 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 that removable inferiors don't cause a stop when they exit.
+
+# This test program conveniently launches another inferior.
+standard_testfile watchpoint-multi-exit.c
+
+if {[prepare_for_testing "failed to build" $testfile $srcfile {debug}]} {
+ return
+}
+
+gdb_test_no_output "set detach-on-fork off"
+gdb_test_no_output "set schedule-multiple on"
+
+gdb_test_multiple "run" "do not stop when child exits" {
+ -re "Starting program: \[^\n\]*\r\n" {
+ exp_continue
+ }
+ -re "\\\[New inferior 2\[^\n\]\\\]\r\n" {
+ exp_continue
+ }
+ -re "\\\[Inferior 2 .*exited normally\\\]\r\n" {
+ exp_continue
+ }
+ -re -wrap "\\\[Inferior 1 .*exited normally\\\]" {
+ pass $gdb_test_name
+ }
+ -re -wrap "" {
+ # Any other stop is a failure.
+ fail $gdb_test_name
+ }
+}