Fix crash if breakpoint commands contain detach or kill

Message ID 20251221183723.248939-1-ssbssa@yahoo.de
State New
Headers
Series Fix crash if breakpoint commands contain detach or kill |

Commit Message

Hannes Domani Dec. 21, 2025, 6:37 p.m. UTC
  If breakpoint commands contain detach or kill, then gdb tries to access
freed memory:

(gdb) b main
Breakpoint 1 at 0x111d: file main.c, line 21.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>detach
>end
(gdb) run
Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".

main () at main.c:21
21        return 0;
[Inferior 1 (process 241852) detached]
=================================================================
==241817==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b7a3de0b760 at pc 0x55fcb92613fe bp 0x7ffec2d524f0 sp 0x7ffec2d524e0
READ of size 8 at 0x7b7a3de0b760 thread T0
    #0 0x55fcb92613fd in bpstat_do_actions_1 ../../gdb/breakpoint.c:4898
    #1 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
    #2 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
    #3 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769

0x7b7a3de0b760 is located 0 bytes inside of 56-byte region [0x7b7a3de0b760,0x7b7a3de0b798)
freed by thread T0 here:
    #0 0x7f1a43522a2d in operator delete(void*, unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:155
    #1 0x55fcb925d5cd in bpstat_clear(bpstat**) ../../gdb/breakpoint.c:4646
    #2 0x55fcbb69ea6a in clear_thread_inferior_resources ../../gdb/thread.c:185
    #3 0x55fcbb69f4cb in set_thread_exited(thread_info*, std::optional<unsigned long>, bool) ../../gdb/thread.c:244
    #4 0x55fcba368d64 in operator() ../../gdb/inferior.c:269
    #5 0x55fcba375e2b in clear_and_dispose<inferior::clear_thread_list()::<lambda(thread_info*)> > ../../gdb/../gdbsupport/intrusive_list.h:529
    #6 0x55fcba368f19 in inferior::clear_thread_list() ../../gdb/inferior.c:265
    #7 0x55fcba3694ba in exit_inferior(inferior*) ../../gdb/inferior.c:322
    #8 0x55fcba369e35 in detach_inferior(inferior*) ../../gdb/inferior.c:358
    #9 0x55fcba319d9f in inf_ptrace_target::detach_success(inferior*) ../../gdb/inf-ptrace.c:214
    #10 0x55fcba56a2f6 in linux_nat_target::detach(inferior*, int) ../../gdb/linux-nat.c:1582
    #11 0x55fcba62121c in thread_db_target::detach(inferior*, int) ../../gdb/linux-thread-db.c:1381
    #12 0x55fcbb5ca49e in target_detach(inferior*, int) ../../gdb/target.c:2557
    #13 0x55fcba356ba4 in detach_command(char const*, int) ../../gdb/infcmd.c:2894
    #14 0x55fcb9597eea in do_simple_func ../../gdb/cli/cli-decode.c:94
    #15 0x55fcb95b10b5 in cmd_func(cmd_list_element*, char const*, int) ../../gdb/cli/cli-decode.c:2831
    #16 0x55fcbb6f5282 in execute_command(char const*, int) ../../gdb/top.c:563
    #17 0x55fcb95eedb9 in execute_control_command_1 ../../gdb/cli/cli-script.c:526
    #18 0x55fcb95f04dd in execute_control_command(command_line*, int) ../../gdb/cli/cli-script.c:702
    #19 0x55fcb9261175 in bpstat_do_actions_1 ../../gdb/breakpoint.c:4940
    #20 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
    #21 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
    #22 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769

previously allocated by thread T0 here:
    #0 0x7f1a435218cd in operator new(unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:86
    #1 0x55fcb927061f in build_bpstat_chain(address_space const*, unsigned long, target_waitstatus const&) ../../gdb/breakpoint.c:5880
    #2 0x55fcba3d63b6 in handle_signal_stop ../../gdb/infrun.c:7083
    #3 0x55fcba3d01c7 in handle_inferior_event ../../gdb/infrun.c:6574
    #4 0x55fcba3b9918 in fetch_inferior_event() ../../gdb/infrun.c:4713

This checks after executing commands of each breakpoint if the bpstat
was deleted already, and stops any further processing immediately.
Now the result looks like this:

(gdb) b main
Breakpoint 1 at 0x111d: file main.c, line 21.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>detach
>end
(gdb) run
Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".

main () at main.c:21
21        return 0;
[Inferior 1 (process 242940) detached]
(gdb)

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=14354
---
 gdb/breakpoint.c                              |  6 +++
 .../detach-in-breakpoint-commands.exp         | 49 +++++++++++++++++++
 2 files changed, 55 insertions(+)
 create mode 100644 gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
  

Comments

Andrew Burgess Dec. 22, 2025, 10:11 a.m. UTC | #1
Hannes Domani <ssbssa@yahoo.de> writes:

> If breakpoint commands contain detach or kill, then gdb tries to access
> freed memory:
>
> (gdb) b main
> Breakpoint 1 at 0x111d: file main.c, line 21.
> (gdb) commands
> Type commands for breakpoint(s) 1, one per line.
> End with a line saying just "end".
>>detach
>>end
> (gdb) run
> Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
>
> main () at main.c:21
> 21        return 0;
> [Inferior 1 (process 241852) detached]
> =================================================================
> ==241817==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b7a3de0b760 at pc 0x55fcb92613fe bp 0x7ffec2d524f0 sp 0x7ffec2d524e0
> READ of size 8 at 0x7b7a3de0b760 thread T0
>     #0 0x55fcb92613fd in bpstat_do_actions_1 ../../gdb/breakpoint.c:4898
>     #1 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
>     #2 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
>     #3 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769
>
> 0x7b7a3de0b760 is located 0 bytes inside of 56-byte region [0x7b7a3de0b760,0x7b7a3de0b798)
> freed by thread T0 here:
>     #0 0x7f1a43522a2d in operator delete(void*, unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:155
>     #1 0x55fcb925d5cd in bpstat_clear(bpstat**) ../../gdb/breakpoint.c:4646
>     #2 0x55fcbb69ea6a in clear_thread_inferior_resources ../../gdb/thread.c:185
>     #3 0x55fcbb69f4cb in set_thread_exited(thread_info*, std::optional<unsigned long>, bool) ../../gdb/thread.c:244
>     #4 0x55fcba368d64 in operator() ../../gdb/inferior.c:269
>     #5 0x55fcba375e2b in clear_and_dispose<inferior::clear_thread_list()::<lambda(thread_info*)> > ../../gdb/../gdbsupport/intrusive_list.h:529
>     #6 0x55fcba368f19 in inferior::clear_thread_list() ../../gdb/inferior.c:265
>     #7 0x55fcba3694ba in exit_inferior(inferior*) ../../gdb/inferior.c:322
>     #8 0x55fcba369e35 in detach_inferior(inferior*) ../../gdb/inferior.c:358
>     #9 0x55fcba319d9f in inf_ptrace_target::detach_success(inferior*) ../../gdb/inf-ptrace.c:214
>     #10 0x55fcba56a2f6 in linux_nat_target::detach(inferior*, int) ../../gdb/linux-nat.c:1582
>     #11 0x55fcba62121c in thread_db_target::detach(inferior*, int) ../../gdb/linux-thread-db.c:1381
>     #12 0x55fcbb5ca49e in target_detach(inferior*, int) ../../gdb/target.c:2557
>     #13 0x55fcba356ba4 in detach_command(char const*, int) ../../gdb/infcmd.c:2894
>     #14 0x55fcb9597eea in do_simple_func ../../gdb/cli/cli-decode.c:94
>     #15 0x55fcb95b10b5 in cmd_func(cmd_list_element*, char const*, int) ../../gdb/cli/cli-decode.c:2831
>     #16 0x55fcbb6f5282 in execute_command(char const*, int) ../../gdb/top.c:563
>     #17 0x55fcb95eedb9 in execute_control_command_1 ../../gdb/cli/cli-script.c:526
>     #18 0x55fcb95f04dd in execute_control_command(command_line*, int) ../../gdb/cli/cli-script.c:702
>     #19 0x55fcb9261175 in bpstat_do_actions_1 ../../gdb/breakpoint.c:4940
>     #20 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
>     #21 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
>     #22 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769
>
> previously allocated by thread T0 here:
>     #0 0x7f1a435218cd in operator new(unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:86
>     #1 0x55fcb927061f in build_bpstat_chain(address_space const*, unsigned long, target_waitstatus const&) ../../gdb/breakpoint.c:5880
>     #2 0x55fcba3d63b6 in handle_signal_stop ../../gdb/infrun.c:7083
>     #3 0x55fcba3d01c7 in handle_inferior_event ../../gdb/infrun.c:6574
>     #4 0x55fcba3b9918 in fetch_inferior_event() ../../gdb/infrun.c:4713
>
> This checks after executing commands of each breakpoint if the bpstat
> was deleted already, and stops any further processing immediately.
> Now the result looks like this:
>
> (gdb) b main
> Breakpoint 1 at 0x111d: file main.c, line 21.
> (gdb) commands
> Type commands for breakpoint(s) 1, one per line.
> End with a line saying just "end".
>>detach
>>end
> (gdb) run
> Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
>
> main () at main.c:21
> 21        return 0;
> [Inferior 1 (process 242940) detached]
> (gdb)
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=14354


Thanks for fixing this.  The fix looks mostly OK, I have just a couple
of comments on the test, see below.

> ---
>  gdb/breakpoint.c                              |  6 +++
>  .../detach-in-breakpoint-commands.exp         | 49 +++++++++++++++++++
>  2 files changed, 55 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
>
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index 8d35e00be99..10b2284ecfa 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -4947,6 +4947,12 @@ bpstat_do_actions_1 (bpstat **bsp)
>  	    cmd = cmd->next;
>  	}
>  
> +      /* If the command tree somehow stopped the process, e.g. if it
> +	 contains 'kill', then the bpstat might have been deleted already.
> +	 *BSP will have been reset then, so stop in that case.  */
> +      if (*bsp == nullptr)
> +	break;
> +
>        if (breakpoint_proceeded)
>  	{
>  	  if (current_ui->async)
> diff --git a/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
> new file mode 100644
> index 00000000000..20a1c8bfaaf
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
> @@ -0,0 +1,49 @@
> +# 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/>.

I think here you should add:

  require !use_gdb_stub

The 'run' command cannot be used with 'remote' targets, so things like:

  make check \
    TESTS=gdb.base/detach-in-breakpoint-commands.exp \
    RUNTESTFLAGS="--target_board=native-remote"

will fail without that line.

You might be interested in the 'Testing All Simple Boards' section of
gdb/testsuite/README.  This describes how to use:

  make check-all-boards TESTS=gdb.base/detach-in-breakpoint-commands.exp

to test with all the different board files.  There's some environment
setup needed, but I find this really useful for checking new tests.  For
this patch though, the above suggestion fixes all issues.

> +
> +# Test breakpoint commands which detach or kill the inferior.
> +
> +standard_testfile main.c
> +
> +if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
> +    return
> +}
> +
> +gdb_test "break main" "Breakpoint $decimal at .*"
> +
> +# Test detach in breakpoint commands.
> +gdb_test \
> +    [multi_line_input \
> +	 {commands} \
> +	 {  detach} \
> +	 {end}] \
> +    "End with.*" \
> +    "detach in commands"
> +
> +gdb_test "run" "\[Inferior $decimal \\(process $decimal\\) detached\]" \
> +    "run - detach"

I think here, and in the 'run' test below too, the pattern within the
(...) is too restrictive.  The 'process $decimal' part is created by
calling target_pid_to_str, and can take any form.  As this isn't
critical to this test I would suggest:

  gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) detached\]" \
      "run - detach"

And a similar change below too.

If you are happy to accept these changes, then:

Approved-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew


> +
> +# Test kill in breakpoint commands.
> +gdb_test \
> +    [multi_line_input \
> +	 {commands} \
> +	 {  set confirm off} \
> +	 {  kill} \
> +	 {end}] \
> +    "End with.*" \
> +    "kill in commands"
> +
> +gdb_test "run" "\[Inferior $decimal \\(process $decimal\\) killed\]" \
> +    "run - kill"
> -- 
> 2.51.0
  
Hannes Domani Dec. 22, 2025, 11:15 a.m. UTC | #2
Am Montag, 22. Dezember 2025 um 11:11:50 MEZ hat Andrew Burgess <aburgess@redhat.com> Folgendes geschrieben:

> Hannes Domani <ssbssa@yahoo.de> writes:

> > If breakpoint commands contain detach or kill, then gdb tries to access
> > freed memory:
> >
> > (gdb) b main
> > Breakpoint 1 at 0x111d: file main.c, line 21.
> > (gdb) commands
> > Type commands for breakpoint(s) 1, one per line.
> > End with a line saying just "end".
> >>detach
> >>end
> > (gdb) run
> > Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
> > [Thread debugging using libthread_db enabled]
> > Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> >
> > main () at main.c:21
> > 21        return 0;
> > [Inferior 1 (process 241852) detached]
> > =================================================================
> > ==241817==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b7a3de0b760 at pc 0x55fcb92613fe bp 0x7ffec2d524f0 sp 0x7ffec2d524e0
> > READ of size 8 at 0x7b7a3de0b760 thread T0
> >    #0 0x55fcb92613fd in bpstat_do_actions_1 ../../gdb/breakpoint.c:4898
> >    #1 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
> >    #2 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
> >    #3 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769
> >
> > 0x7b7a3de0b760 is located 0 bytes inside of 56-byte region [0x7b7a3de0b760,0x7b7a3de0b798)
> > freed by thread T0 here:
> >    #0 0x7f1a43522a2d in operator delete(void*, unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:155
> >    #1 0x55fcb925d5cd in bpstat_clear(bpstat**) ../../gdb/breakpoint.c:4646
> >    #2 0x55fcbb69ea6a in clear_thread_inferior_resources ../../gdb/thread.c:185
> >    #3 0x55fcbb69f4cb in set_thread_exited(thread_info*, std::optional<unsigned long>, bool) ../../gdb/thread.c:244
> >    #4 0x55fcba368d64 in operator() ../../gdb/inferior.c:269
> >    #5 0x55fcba375e2b in clear_and_dispose<inferior::clear_thread_list()::<lambda(thread_info*)> > ../../gdb/../gdbsupport/intrusive_list.h:529
> >    #6 0x55fcba368f19 in inferior::clear_thread_list() ../../gdb/inferior.c:265
> >    #7 0x55fcba3694ba in exit_inferior(inferior*) ../../gdb/inferior.c:322
> >    #8 0x55fcba369e35 in detach_inferior(inferior*) ../../gdb/inferior.c:358
> >    #9 0x55fcba319d9f in inf_ptrace_target::detach_success(inferior*) ../../gdb/inf-ptrace.c:214
> >    #10 0x55fcba56a2f6 in linux_nat_target::detach(inferior*, int) ../../gdb/linux-nat.c:1582
> >    #11 0x55fcba62121c in thread_db_target::detach(inferior*, int) ../../gdb/linux-thread-db.c:1381
> >    #12 0x55fcbb5ca49e in target_detach(inferior*, int) ../../gdb/target.c:2557
> >    #13 0x55fcba356ba4 in detach_command(char const*, int) ../../gdb/infcmd.c:2894
> >    #14 0x55fcb9597eea in do_simple_func ../../gdb/cli/cli-decode.c:94
> >    #15 0x55fcb95b10b5 in cmd_func(cmd_list_element*, char const*, int) ../../gdb/cli/cli-decode.c:2831
> >    #16 0x55fcbb6f5282 in execute_command(char const*, int) ../../gdb/top.c:563
> >    #17 0x55fcb95eedb9 in execute_control_command_1 ../../gdb/cli/cli-script.c:526
> >    #18 0x55fcb95f04dd in execute_control_command(command_line*, int) ../../gdb/cli/cli-script.c:702
> >    #19 0x55fcb9261175 in bpstat_do_actions_1 ../../gdb/breakpoint.c:4940
> >    #20 0x55fcb92617da in bpstat_do_actions() ../../gdb/breakpoint.c:5012
> >    #21 0x55fcba3180e7 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:71
> >    #22 0x55fcba3ba1e1 in fetch_inferior_event() ../../gdb/infrun.c:4769
> >
> > previously allocated by thread T0 here:
> >    #0 0x7f1a435218cd in operator new(unsigned long) /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_new_delete.cpp:86
> >    #1 0x55fcb927061f in build_bpstat_chain(address_space const*, unsigned long, target_waitstatus const&) ../../gdb/breakpoint.c:5880
> >    #2 0x55fcba3d63b6 in handle_signal_stop ../../gdb/infrun.c:7083
> >    #3 0x55fcba3d01c7 in handle_inferior_event ../../gdb/infrun.c:6574
> >    #4 0x55fcba3b9918 in fetch_inferior_event() ../../gdb/infrun.c:4713
> >
> > This checks after executing commands of each breakpoint if the bpstat
> > was deleted already, and stops any further processing immediately.
> > Now the result looks like this:
> >
> > (gdb) b main
> > Breakpoint 1 at 0x111d: file main.c, line 21.
> > (gdb) commands
> > Type commands for breakpoint(s) 1, one per line.
> > End with a line saying just "end".
> >>detach
> >>end
> > (gdb) run
> > Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/main
> > [Thread debugging using libthread_db enabled]
> > Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> >
> > main () at main.c:21
> > 21        return 0;
> > [Inferior 1 (process 242940) detached]
> > (gdb)
> >
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=14354


> Thanks for fixing this.  The fix looks mostly OK, I have just a couple
> of comments on the test, see below.

> > ---
> >  gdb/breakpoint.c                              |  6 +++
> >  .../detach-in-breakpoint-commands.exp        | 49 +++++++++++++++++++
> >  2 files changed, 55 insertions(+)
> >  create mode 100644 gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
> >
> > diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> > index 8d35e00be99..10b2284ecfa 100644
> > --- a/gdb/breakpoint.c
> > +++ b/gdb/breakpoint.c
> > @@ -4947,6 +4947,12 @@ bpstat_do_actions_1 (bpstat **bsp)
> >          cmd = cmd->next;
> >      }
> > 
> > +      /* If the command tree somehow stopped the process, e.g. if it
> > +    contains 'kill', then the bpstat might have been deleted already.
> > +    *BSP will have been reset then, so stop in that case.  */
> > +      if (*bsp == nullptr)
> > +    break;
> > +
> >        if (breakpoint_proceeded)
> >      {
> >        if (current_ui->async)
> > diff --git a/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
> > new file mode 100644
> > index 00000000000..20a1c8bfaaf
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
> > @@ -0,0 +1,49 @@
> > +# 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/>.

> I think here you should add:

>   require !use_gdb_stub

> The 'run' command cannot be used with 'remote' targets, so things like:

>   make check \
>     TESTS=gdb.base/detach-in-breakpoint-commands.exp \
>     RUNTESTFLAGS="--target_board=native-remote"

> will fail without that line.

If I understand it correctly, it would have worked if instead of 'run'
I would use 'runto_main' followed by 'continue' instead.
Not sure if I would need a new test source file instead of main.c then.


> You might be interested in the 'Testing All Simple Boards' section of
> gdb/testsuite/README.  This describes how to use:

>   make check-all-boards TESTS=gdb.base/detach-in-breakpoint-commands.exp

> to test with all the different board files.  There's some environment
> setup needed, but I find this really useful for checking new tests.  For
> this patch though, the above suggestion fixes all issues.

> > +
> > +# Test breakpoint commands which detach or kill the inferior.
> > +
> > +standard_testfile main.c
> > +
> > +if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
> > +    return
> > +}
> > +
> > +gdb_test "break main" "Breakpoint $decimal at .*"
> > +
> > +# Test detach in breakpoint commands.
> > +gdb_test \
> > +    [multi_line_input \
> > +    {commands} \
> > +    {  detach} \
> > +    {end}] \
> > +    "End with.*" \
> > +    "detach in commands"
> > +
> > +gdb_test "run" "\[Inferior $decimal \\(process $decimal\\) detached\]" \
> > +    "run - detach"

> I think here, and in the 'run' test below too, the pattern within the
> (...) is too restrictive.  The 'process $decimal' part is created by
> calling target_pid_to_str, and can take any form.  As this isn't
> critical to this test I would suggest:

>   gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) detached\]" \
>       "run - detach"

> And a similar change below too.

> If you are happy to accept these changes, then:

> Approved-By: Andrew Burgess <aburgess@redhat.com>

Pushed with these changes, thanks.


Hannes
  

Patch

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 8d35e00be99..10b2284ecfa 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -4947,6 +4947,12 @@  bpstat_do_actions_1 (bpstat **bsp)
 	    cmd = cmd->next;
 	}
 
+      /* If the command tree somehow stopped the process, e.g. if it
+	 contains 'kill', then the bpstat might have been deleted already.
+	 *BSP will have been reset then, so stop in that case.  */
+      if (*bsp == nullptr)
+	break;
+
       if (breakpoint_proceeded)
 	{
 	  if (current_ui->async)
diff --git a/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
new file mode 100644
index 00000000000..20a1c8bfaaf
--- /dev/null
+++ b/gdb/testsuite/gdb.base/detach-in-breakpoint-commands.exp
@@ -0,0 +1,49 @@ 
+# 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 breakpoint commands which detach or kill the inferior.
+
+standard_testfile main.c
+
+if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
+    return
+}
+
+gdb_test "break main" "Breakpoint $decimal at .*"
+
+# Test detach in breakpoint commands.
+gdb_test \
+    [multi_line_input \
+	 {commands} \
+	 {  detach} \
+	 {end}] \
+    "End with.*" \
+    "detach in commands"
+
+gdb_test "run" "\[Inferior $decimal \\(process $decimal\\) detached\]" \
+    "run - detach"
+
+# Test kill in breakpoint commands.
+gdb_test \
+    [multi_line_input \
+	 {commands} \
+	 {  set confirm off} \
+	 {  kill} \
+	 {end}] \
+    "End with.*" \
+    "kill in commands"
+
+gdb_test "run" "\[Inferior $decimal \\(process $decimal\\) killed\]" \
+    "run - kill"