Fix crash when breakpoint condition causes inferior exit

Message ID 20251221170657.227525-1-ssbssa@yahoo.de
State New
Headers
Series Fix crash when breakpoint condition causes inferior exit |

Commit Message

Hannes Domani Dec. 21, 2025, 5:06 p.m. UTC
  When using a breakpoint condition that causes an inferior exit, gdb
crashes with a null pointer access:

(gdb) b main if callexit()
Breakpoint 1 at 0x114b: file callexit.c, line 32.
(gdb) r
Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
[Inferior 1 (process 218586) exited normally]
../../gdb/infcall.c:895:50: runtime error: member call on null pointer of type 'struct thread_fsm'
../../gdb/infcall.c:895:50: runtime error: member access within null pointer of type 'struct thread_fsm'

Fix this by checking the thread_fsm pointer beforehand, now the result
looks like this:

(gdb) b main if callexit()
Breakpoint 1 at 0x114b: file callexit.c, line 32.
(gdb) r
Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
[Inferior 1 (process 220707) exited normally]
❌ Error in testing condition for breakpoint 1:
The program being debugged exited while in a function called from GDB.
Evaluation of the expression containing the function
(callexit) will be abandoned.
❌ No registers.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=16156
---
 gdb/infcall.c                                |  3 +-
 gdb/testsuite/gdb.base/exit-in-condition.exp | 36 ++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)
 create mode 100644 gdb/testsuite/gdb.base/exit-in-condition.exp
  

Comments

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

> When using a breakpoint condition that causes an inferior exit, gdb
> crashes with a null pointer access:
>
> (gdb) b main if callexit()
> Breakpoint 1 at 0x114b: file callexit.c, line 32.
> (gdb) r
> Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> [Inferior 1 (process 218586) exited normally]
> ../../gdb/infcall.c:895:50: runtime error: member call on null pointer of type 'struct thread_fsm'
> ../../gdb/infcall.c:895:50: runtime error: member access within null pointer of type 'struct thread_fsm'
>
> Fix this by checking the thread_fsm pointer beforehand, now the result
> looks like this:
>
> (gdb) b main if callexit()
> Breakpoint 1 at 0x114b: file callexit.c, line 32.
> (gdb) r
> Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> [Inferior 1 (process 220707) exited normally]
> ❌ Error in testing condition for breakpoint 1:
> The program being debugged exited while in a function called from GDB.
> Evaluation of the expression containing the function
> (callexit) will be abandoned.
> ❌ No registers.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=16156

Thanks for fixing this.  I have a tiny nit with the code, and some
comments on the test (list your other patch).  See below.

> ---
>  gdb/infcall.c                                |  3 +-
>  gdb/testsuite/gdb.base/exit-in-condition.exp | 36 ++++++++++++++++++++
>  2 files changed, 38 insertions(+), 1 deletion(-)
>  create mode 100644 gdb/testsuite/gdb.base/exit-in-condition.exp
>
> diff --git a/gdb/infcall.c b/gdb/infcall.c
> index 67053ae0f08..27757c38630 100644
> --- a/gdb/infcall.c
> +++ b/gdb/infcall.c
> @@ -892,7 +892,8 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
>       async_enable_stdin.  */
>    if (current_ui->prompt_state == PROMPT_BLOCKED)
>      {
> -      if (call_thread->thread_fsm ()->finished_p ())
> +      if (call_thread->thread_fsm ()

This should be:

  if (call_thread->thread_fsm () != nullptr
      && call_thread->thread_fsm ()->finished_p ())

A comment explaining that thread_fsm() can return NULL if the thread has
exited wouldn't hurt, but with the test isn't a requirement as far as
I'm concerned.

> +	  && call_thread->thread_fsm ()->finished_p ())
>  	async_disable_stdin ();
>        else
>  	async_enable_stdin ();
> diff --git a/gdb/testsuite/gdb.base/exit-in-condition.exp b/gdb/testsuite/gdb.base/exit-in-condition.exp
> new file mode 100644
> index 00000000000..ad0b7f99f93
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/exit-in-condition.exp
> @@ -0,0 +1,36 @@
> +# 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 exit call within breakpoint condition.
> +
> +# Some targets can't do function calls, so don't even bother with this
> +# test.
> +require {!target_info exists gdb,cannot_call_functions}

As with your other patch:

  require !use_gdb_stub


> +
> +standard_testfile callexit.c
> +
> +if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
> +    return
> +}
> +
> +gdb_test "break main if callexit()" "Breakpoint $decimal at .*"
> +
> +gdb_test "run" \
> +    "\[Inferior $decimal \\(process $decimal\\) exited normally\].*

And for the same reasons as your other recent patch, update the first
line of this pattern to:

  \[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\].*

> +Error in testing condition for breakpoint $decimal:.*
> +The program being debugged exited while in a function called from GDB..*

The first period on this line should be escaped, so:

  The program being debugged exited while in a function called from GDB\\..*

> +Evaluation of the expression containing the function.*
> +\\(callexit\\) will be abandoned..*

And escape the period here too.

> +No registers."

And here too.

With those fixes:

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

Thanks,
Andrew

> -- 
> 2.51.0
  
Hannes Domani Dec. 22, 2025, 11:28 a.m. UTC | #2
Am Montag, 22. Dezember 2025 um 11:48:55 MEZ hat Andrew Burgess <aburgess@redhat.com> Folgendes geschrieben:

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

> > When using a breakpoint condition that causes an inferior exit, gdb
> > crashes with a null pointer access:
> >
> > (gdb) b main if callexit()
> > Breakpoint 1 at 0x114b: file callexit.c, line 32.
> > (gdb) r
> > Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
> > [Thread debugging using libthread_db enabled]
> > Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> > [Inferior 1 (process 218586) exited normally]
> > ../../gdb/infcall.c:895:50: runtime error: member call on null pointer of type 'struct thread_fsm'
> > ../../gdb/infcall.c:895:50: runtime error: member access within null pointer of type 'struct thread_fsm'
> >
> > Fix this by checking the thread_fsm pointer beforehand, now the result
> > looks like this:
> >
> > (gdb) b main if callexit()
> > Breakpoint 1 at 0x114b: file callexit.c, line 32.
> > (gdb) r
> > Starting program: /home/src/lappy/binutils-gdb.git/gdb/testsuite/gdb.base/callexit
> > [Thread debugging using libthread_db enabled]
> > Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> > [Inferior 1 (process 220707) exited normally]
> > ❌ Error in testing condition for breakpoint 1:
> > The program being debugged exited while in a function called from GDB.
> > Evaluation of the expression containing the function
> > (callexit) will be abandoned.
> > ❌ No registers.
> >
> > Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=16156

> Thanks for fixing this.  I have a tiny nit with the code, and some
> comments on the test (list your other patch).  See below.

> > ---
> >  gdb/infcall.c                                |  3 +-
> >  gdb/testsuite/gdb.base/exit-in-condition.exp | 36 ++++++++++++++++++++
> >  2 files changed, 38 insertions(+), 1 deletion(-)
> >  create mode 100644 gdb/testsuite/gdb.base/exit-in-condition.exp
> >
> > diff --git a/gdb/infcall.c b/gdb/infcall.c
> > index 67053ae0f08..27757c38630 100644
> > --- a/gdb/infcall.c
> > +++ b/gdb/infcall.c
> > @@ -892,7 +892,8 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
> >      async_enable_stdin.  */
> >    if (current_ui->prompt_state == PROMPT_BLOCKED)
> >      {
> > -      if (call_thread->thread_fsm ()->finished_p ())
> > +      if (call_thread->thread_fsm ()

> This should be:

>   if (call_thread->thread_fsm () != nullptr
>       && call_thread->thread_fsm ()->finished_p ())

> A comment explaining that thread_fsm() can return NULL if the thread has
> exited wouldn't hurt, but with the test isn't a requirement as far as
> I'm concerned.

> > +      && call_thread->thread_fsm ()->finished_p ())
> >      async_disable_stdin ();
> >        else
> >      async_enable_stdin ();
> > diff --git a/gdb/testsuite/gdb.base/exit-in-condition.exp b/gdb/testsuite/gdb.base/exit-in-condition.exp
> > new file mode 100644
> > index 00000000000..ad0b7f99f93
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.base/exit-in-condition.exp
> > @@ -0,0 +1,36 @@
> > +# 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 exit call within breakpoint condition.
> > +
> > +# Some targets can't do function calls, so don't even bother with this
> > +# test.
> > +require {!target_info exists gdb,cannot_call_functions}

> As with your other patch:

>   require !use_gdb_stub


> > +
> > +standard_testfile callexit.c
> > +
> > +if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
> > +    return
> > +}
> > +
> > +gdb_test "break main if callexit()" "Breakpoint $decimal at .*"
> > +
> > +gdb_test "run" \
> > +    "\[Inferior $decimal \\(process $decimal\\) exited normally\].*

> And for the same reasons as your other recent patch, update the first
> line of this pattern to:

>   \[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\].*

> > +Error in testing condition for breakpoint $decimal:.*
> > +The program being debugged exited while in a function called from GDB..*

> The first period on this line should be escaped, so:

>   The program being debugged exited while in a function called from GDB\\..*

> > +Evaluation of the expression containing the function.*
> > +\\(callexit\\) will be abandoned..*

> And escape the period here too.

> > +No registers."

> And here too.

> With those fixes:

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

Pushed with those fixes, thanks.


Hannes
  

Patch

diff --git a/gdb/infcall.c b/gdb/infcall.c
index 67053ae0f08..27757c38630 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -892,7 +892,8 @@  run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
      async_enable_stdin.  */
   if (current_ui->prompt_state == PROMPT_BLOCKED)
     {
-      if (call_thread->thread_fsm ()->finished_p ())
+      if (call_thread->thread_fsm ()
+	  && call_thread->thread_fsm ()->finished_p ())
 	async_disable_stdin ();
       else
 	async_enable_stdin ();
diff --git a/gdb/testsuite/gdb.base/exit-in-condition.exp b/gdb/testsuite/gdb.base/exit-in-condition.exp
new file mode 100644
index 00000000000..ad0b7f99f93
--- /dev/null
+++ b/gdb/testsuite/gdb.base/exit-in-condition.exp
@@ -0,0 +1,36 @@ 
+# 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 exit call within breakpoint condition.
+
+# Some targets can't do function calls, so don't even bother with this
+# test.
+require {!target_info exists gdb,cannot_call_functions}
+
+standard_testfile callexit.c
+
+if { [prepare_for_testing "prepare" $testfile $srcfile] != 0 } {
+    return
+}
+
+gdb_test "break main if callexit()" "Breakpoint $decimal at .*"
+
+gdb_test "run" \
+    "\[Inferior $decimal \\(process $decimal\\) exited normally\].*
+Error in testing condition for breakpoint $decimal:.*
+The program being debugged exited while in a function called from GDB..*
+Evaluation of the expression containing the function.*
+\\(callexit\\) will be abandoned..*
+No registers."