Forget the last displayed sal when obfiles are purged

Message ID 20251222132340.53858-1-ssbssa@yahoo.de
State New
Headers
Series Forget the last displayed sal when obfiles are purged |

Commit Message

Hannes Domani Dec. 22, 2025, 1:23 p.m. UTC
  When creating a new line-breakpoint after rerunning with a disabled
breakpoint in a solib, you currently get this use-after-free crash:

(gdb) break solib_main
Breakpoint 1 at 0x1030
(gdb) run
Starting program: /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/outputs/gdb.base/solib-breakpoints-rerun/solib-breakpoints-rerun
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".

Breakpoint 1, solib_main (arg=100) at /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/../../../gdb/testsuite/gdb.base/solib1.c:7
7	  int ans = arg*arg;		/* HERE */
(gdb) disable
(gdb) set confirm off
(gdb) run
Starting program: /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/outputs/gdb.base/solib-breakpoints-rerun/solib-breakpoints-rerun
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
[Inferior 1 (process 50868) exited normally]
(gdb) break 18
=================================================================
==50836==ERROR: AddressSanitizer: heap-use-after-free on address 0x7d65a543d488 at pc 0x55f022334c29 bp 0x7ffe9c7f45f0 sp 0x7ffe9c7f45e0
READ of size 8 at 0x7d65a543d488 thread T0
    #0 0x55f022334c28 in symtab::filename() const ../../gdb/symtab.h:1747
    #1 0x55f023a32b7d in create_sals_line_offset ../../gdb/linespec.c:2013
    #2 0x55f023a37937 in convert_linespec_to_sals ../../gdb/linespec.c:2294
    #3 0x55f023a3cf79 in parse_linespec ../../gdb/linespec.c:2652
    #4 0x55f023a415b2 in location_spec_to_sals ../../gdb/linespec.c:3050
    #5 0x55f023a423b0 in decode_line_full(location_spec*, int, program_space*, symtab*, int, linespec_result*, char const*, char const*) ../../gdb/linespec.c:3126
    #6 0x55f022826e1b in parse_breakpoint_sals ../../gdb/breakpoint.c:9069
    #7 0x55f0228291c2 in create_breakpoint(gdbarch*, location_spec*, char const*, int, int, char const*, bool, int, int, bptype, int, auto_boolean, breakpoint_ops const*, int, int, int, unsigned int) ../../gdb/breakpoint.c:9312
    #8 0x55f02282c298 in break_command_1 ../../gdb/breakpoint.c:9471
    #9 0x55f02282d0bb in break_command(char const*, int) ../../gdb/breakpoint.c:9541
    #10 0x55f022b05f56 in do_simple_func ../../gdb/cli/cli-decode.c:94

0x7d65a543d488 is located 904 bytes inside of 4064-byte region [0x7d65a543d100,0x7d65a543e0e0)
freed by thread T0 here:
    #0 0x7f55aa91f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51
    #1 0x55f0221f6403 in xfree<void> ../../gdb/../gdbsupport/gdb-xfree.h:37
    #2 0x55f027b64851 in call_freefun ../../libiberty/obstack.c:103
    #3 0x55f027b66283 in _obstack_free ../../libiberty/obstack.c:280
    #4 0x55f0221f7a07 in auto_obstack::~auto_obstack() ../../gdb/../gdbsupport/gdb_obstack.h:126
    #5 0x55f023f3dd83 in objfile::~objfile() ../../gdb/objfiles.c:509
    #6 0x55f023f511d8 in std::default_delete<objfile>::operator()(objfile*) const /usr/include/c++/15.2.1/bits/unique_ptr.h:93
    #7 0x55f023f4b8cd in std::unique_ptr<objfile, std::default_delete<objfile> >::~unique_ptr() /usr/include/c++/15.2.1/bits/unique_ptr.h:399
    #8 0x55f0240b6dab in owning_intrusive_list<objfile, intrusive_base_node<objfile> >::erase(intrusive_list_iterator<objfile, intrusive_base_node<objfile> >) ../../gdb/../gdbsupport/owning_intrusive_list.h:113
    #9 0x55f0240adb68 in program_space::remove_objfile(objfile*) ../../gdb/progspace.c:202
    #10 0x55f023f3cfcf in objfile::unlink() ../../gdb/objfiles.c:409
    #11 0x55f023f40faf in objfile_purge_solibs(program_space*) ../../gdb/objfiles.c:687
    #12 0x55f02487ec19 in no_shared_libraries(program_space*) ../../gdb/solib.c:1359
    #13 0x55f024b37e1e in target_pre_inferior() ../../gdb/target.c:2474
    #14 0x55f0238ac7cd in run_command_1 ../../gdb/infcmd.c:381
    #15 0x55f0238ae438 in run_command ../../gdb/infcmd.c:510
    #16 0x55f022b05f56 in do_simple_func ../../gdb/cli/cli-decode.c:94

previously allocated by thread T0 here:
    #0 0x7f55aa920cb5 in malloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:67
    #1 0x55f02241d780 in xmalloc ../../gdb/alloc.c:52
    #2 0x55f027b6461f in call_chunkfun ../../libiberty/obstack.c:94
    #3 0x55f027b649d1 in _obstack_begin_worker ../../libiberty/obstack.c:141
    #4 0x55f027b650c5 in _obstack_begin ../../libiberty/obstack.c:164
    #5 0x55f0221f775a in auto_obstack::auto_obstack() ../../gdb/../gdbsupport/gdb_obstack.h:123
    #6 0x55f023f39c91 in objfile::objfile(gdb::ref_ptr<bfd, gdb_bfd_ref_policy>, program_space*, char const*, enum_flags<objfile_flag>) ../../gdb/objfiles.c:257
    #7 0x55f023f3ccaa in objfile::make(gdb::ref_ptr<bfd, gdb_bfd_ref_policy>, program_space*, char const*, enum_flags<objfile_flag>, objfile*) ../../gdb/objfiles.c:392
    #8 0x55f02498b1d2 in symbol_file_add_with_addrs ../../gdb/symfile.c:1069
    #9 0x55f02498c099 in symbol_file_add_from_bfd(gdb::ref_ptr<bfd, gdb_bfd_ref_policy> const&, char const*, enum_flags<symfile_add_flag>, std::vector<other_sections, std::allocator<other_sections> >*, enum_flags<objfile_flag>, objfile*) ../../gdb/symfile.c:1156
    #10 0x55f02487255b in solib_read_symbols(solib&, enum_flags<symfile_add_flag>) ../../gdb/solib.c:660
    #11 0x55f024876c0c in solib_add(char const*, int, int) ../../gdb/solib.c:993
    #12 0x55f02487f1f8 in handle_solib_event() ../../gdb/solib.c:1399
    #13 0x55f0227df91f in bpstat_stop_status(address_space const*, unsigned long, thread_info*, target_waitstatus const&, bpstat*) ../../gdb/breakpoint.c:5962
    #14 0x55f023944fa8 in handle_signal_stop ../../gdb/infrun.c:7130
    #15 0x55f02393e2a1 in handle_inferior_event ../../gdb/infrun.c:6574
    #16 0x55f0239279f2 in fetch_inferior_event() ../../gdb/infrun.c:4713
    #17 0x55f023885fe3 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:42
    #18 0x55f023af706e in handle_target_event ../../gdb/linux-nat.c:4449
    #19 0x55f027c2d2f0 in handle_file_event ../../gdbsupport/event-loop.cc:551
    #20 0x55f027c2e4ff in gdb_wait_for_event ../../gdbsupport/event-loop.cc:672

It happened because last_displayed_symtab_info of stack.c still contained
a reference to a symtab that was already freed in the 2nd run.
This fixes it by clearing last_displayed_symtab_info when the solibs are
purged, now setting of the 2nd breakpoint works:

(gdb) break 18
Breakpoint 2 at 0x555555555141: file /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/../../../gdb/testsuite/gdb.base/so-impl-ld.c, line 18.
(gdb)

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32668
---
 gdb/objfiles.c                                |  3 ++
 .../gdb.base/solib-breakpoints-rerun.exp      | 49 +++++++++++++++++++
 2 files changed, 52 insertions(+)
 create mode 100644 gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
  

Comments

Andrew Burgess Dec. 22, 2025, 4:13 p.m. UTC | #1
Hannes Domani <ssbssa@yahoo.de> writes:

> When creating a new line-breakpoint after rerunning with a disabled
> breakpoint in a solib, you currently get this use-after-free crash:
>
> (gdb) break solib_main
> Breakpoint 1 at 0x1030
> (gdb) run
> Starting program: /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/outputs/gdb.base/solib-breakpoints-rerun/solib-breakpoints-rerun
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
>
> Breakpoint 1, solib_main (arg=100) at /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/../../../gdb/testsuite/gdb.base/solib1.c:7
> 7	  int ans = arg*arg;		/* HERE */
> (gdb) disable
> (gdb) set confirm off
> (gdb) run
> Starting program: /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/outputs/gdb.base/solib-breakpoints-rerun/solib-breakpoints-rerun
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/usr/lib/../lib/libthread_db.so.1".
> [Inferior 1 (process 50868) exited normally]
> (gdb) break 18
> =================================================================
> ==50836==ERROR: AddressSanitizer: heap-use-after-free on address 0x7d65a543d488 at pc 0x55f022334c29 bp 0x7ffe9c7f45f0 sp 0x7ffe9c7f45e0
> READ of size 8 at 0x7d65a543d488 thread T0
>     #0 0x55f022334c28 in symtab::filename() const ../../gdb/symtab.h:1747
>     #1 0x55f023a32b7d in create_sals_line_offset ../../gdb/linespec.c:2013
>     #2 0x55f023a37937 in convert_linespec_to_sals ../../gdb/linespec.c:2294
>     #3 0x55f023a3cf79 in parse_linespec ../../gdb/linespec.c:2652
>     #4 0x55f023a415b2 in location_spec_to_sals ../../gdb/linespec.c:3050
>     #5 0x55f023a423b0 in decode_line_full(location_spec*, int, program_space*, symtab*, int, linespec_result*, char const*, char const*) ../../gdb/linespec.c:3126
>     #6 0x55f022826e1b in parse_breakpoint_sals ../../gdb/breakpoint.c:9069
>     #7 0x55f0228291c2 in create_breakpoint(gdbarch*, location_spec*, char const*, int, int, char const*, bool, int, int, bptype, int, auto_boolean, breakpoint_ops const*, int, int, int, unsigned int) ../../gdb/breakpoint.c:9312
>     #8 0x55f02282c298 in break_command_1 ../../gdb/breakpoint.c:9471
>     #9 0x55f02282d0bb in break_command(char const*, int) ../../gdb/breakpoint.c:9541
>     #10 0x55f022b05f56 in do_simple_func ../../gdb/cli/cli-decode.c:94
>
> 0x7d65a543d488 is located 904 bytes inside of 4064-byte region [0x7d65a543d100,0x7d65a543e0e0)
> freed by thread T0 here:
>     #0 0x7f55aa91f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51
>     #1 0x55f0221f6403 in xfree<void> ../../gdb/../gdbsupport/gdb-xfree.h:37
>     #2 0x55f027b64851 in call_freefun ../../libiberty/obstack.c:103
>     #3 0x55f027b66283 in _obstack_free ../../libiberty/obstack.c:280
>     #4 0x55f0221f7a07 in auto_obstack::~auto_obstack() ../../gdb/../gdbsupport/gdb_obstack.h:126
>     #5 0x55f023f3dd83 in objfile::~objfile() ../../gdb/objfiles.c:509
>     #6 0x55f023f511d8 in std::default_delete<objfile>::operator()(objfile*) const /usr/include/c++/15.2.1/bits/unique_ptr.h:93
>     #7 0x55f023f4b8cd in std::unique_ptr<objfile, std::default_delete<objfile> >::~unique_ptr() /usr/include/c++/15.2.1/bits/unique_ptr.h:399
>     #8 0x55f0240b6dab in owning_intrusive_list<objfile, intrusive_base_node<objfile> >::erase(intrusive_list_iterator<objfile, intrusive_base_node<objfile> >) ../../gdb/../gdbsupport/owning_intrusive_list.h:113
>     #9 0x55f0240adb68 in program_space::remove_objfile(objfile*) ../../gdb/progspace.c:202
>     #10 0x55f023f3cfcf in objfile::unlink() ../../gdb/objfiles.c:409
>     #11 0x55f023f40faf in objfile_purge_solibs(program_space*) ../../gdb/objfiles.c:687
>     #12 0x55f02487ec19 in no_shared_libraries(program_space*) ../../gdb/solib.c:1359
>     #13 0x55f024b37e1e in target_pre_inferior() ../../gdb/target.c:2474
>     #14 0x55f0238ac7cd in run_command_1 ../../gdb/infcmd.c:381
>     #15 0x55f0238ae438 in run_command ../../gdb/infcmd.c:510
>     #16 0x55f022b05f56 in do_simple_func ../../gdb/cli/cli-decode.c:94
>
> previously allocated by thread T0 here:
>     #0 0x7f55aa920cb5 in malloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:67
>     #1 0x55f02241d780 in xmalloc ../../gdb/alloc.c:52
>     #2 0x55f027b6461f in call_chunkfun ../../libiberty/obstack.c:94
>     #3 0x55f027b649d1 in _obstack_begin_worker ../../libiberty/obstack.c:141
>     #4 0x55f027b650c5 in _obstack_begin ../../libiberty/obstack.c:164
>     #5 0x55f0221f775a in auto_obstack::auto_obstack() ../../gdb/../gdbsupport/gdb_obstack.h:123
>     #6 0x55f023f39c91 in objfile::objfile(gdb::ref_ptr<bfd, gdb_bfd_ref_policy>, program_space*, char const*, enum_flags<objfile_flag>) ../../gdb/objfiles.c:257
>     #7 0x55f023f3ccaa in objfile::make(gdb::ref_ptr<bfd, gdb_bfd_ref_policy>, program_space*, char const*, enum_flags<objfile_flag>, objfile*) ../../gdb/objfiles.c:392
>     #8 0x55f02498b1d2 in symbol_file_add_with_addrs ../../gdb/symfile.c:1069
>     #9 0x55f02498c099 in symbol_file_add_from_bfd(gdb::ref_ptr<bfd, gdb_bfd_ref_policy> const&, char const*, enum_flags<symfile_add_flag>, std::vector<other_sections, std::allocator<other_sections> >*, enum_flags<objfile_flag>, objfile*) ../../gdb/symfile.c:1156
>     #10 0x55f02487255b in solib_read_symbols(solib&, enum_flags<symfile_add_flag>) ../../gdb/solib.c:660
>     #11 0x55f024876c0c in solib_add(char const*, int, int) ../../gdb/solib.c:993
>     #12 0x55f02487f1f8 in handle_solib_event() ../../gdb/solib.c:1399
>     #13 0x55f0227df91f in bpstat_stop_status(address_space const*, unsigned long, thread_info*, target_waitstatus const&, bpstat*) ../../gdb/breakpoint.c:5962
>     #14 0x55f023944fa8 in handle_signal_stop ../../gdb/infrun.c:7130
>     #15 0x55f02393e2a1 in handle_inferior_event ../../gdb/infrun.c:6574
>     #16 0x55f0239279f2 in fetch_inferior_event() ../../gdb/infrun.c:4713
>     #17 0x55f023885fe3 in inferior_event_handler(inferior_event_type) ../../gdb/inf-loop.c:42
>     #18 0x55f023af706e in handle_target_event ../../gdb/linux-nat.c:4449
>     #19 0x55f027c2d2f0 in handle_file_event ../../gdbsupport/event-loop.cc:551
>     #20 0x55f027c2e4ff in gdb_wait_for_event ../../gdbsupport/event-loop.cc:672
>
> It happened because last_displayed_symtab_info of stack.c still contained
> a reference to a symtab that was already freed in the 2nd run.
> This fixes it by clearing last_displayed_symtab_info when the solibs are
> purged, now setting of the 2nd breakpoint works:
>
> (gdb) break 18
> Breakpoint 2 at 0x555555555141: file /home/src/lappy/binutils-gdb.git/build/gdb/testsuite/../../../gdb/testsuite/gdb.base/so-impl-ld.c, line 18.
> (gdb)
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32668
> ---
>  gdb/objfiles.c                                |  3 ++
>  .../gdb.base/solib-breakpoints-rerun.exp      | 49 +++++++++++++++++++
>  2 files changed, 52 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
>
> diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> index 5c5b04c6458..247f0765391 100644
> --- a/gdb/objfiles.c
> +++ b/gdb/objfiles.c
> @@ -47,6 +47,7 @@
>  #include "gdb_bfd.h"
>  #include "btrace.h"
>  #include "gdbsupport/pathstuff.h"
> +#include "stack.h"
>  
>  #include <algorithm>
>  
> @@ -685,6 +686,8 @@ objfile_purge_solibs (program_space *pspace)
>        if (!(objf.flags & OBJF_USERLOADED) && (objf.flags & OBJF_SHARED))
>  	objf.unlink ();
>      }
> +
> +  clear_last_displayed_sal ();

In objfile::~objfile we have this code:

  /* Check to see if the current_source_symtab belongs to this objfile,
     and if so, call clear_current_source_symtab_and_line.  */
  clear_current_source_symtab_and_line (this);

I know we have both a last displayed symtab, and a current symtab, so I
guess you're handling the "other" one, though I don't have time to check
right now.

I wonder if it would be better to handle the last displayed symtab at
the same time as the current symtab?

Also, the clear_current_source_symtab_and_line call seems to only clear
things if this was the objfile that was removed, which should avoid
clearing it unnecessarily maybe?

Would like to hear your thoughts.

>  }
>  
>  /* See objfiles.h.  */
> diff --git a/gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp b/gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
> new file mode 100644
> index 00000000000..2769d1630fc
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/solib-breakpoints-rerun.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 setting new line-breakpoint after re-running with disabled
> +# breakpoint inside solib.
> +
> +require !use_gdb_stub
> +require allow_shlib_tests
> +
> +standard_testfile so-impl-ld.c
> +set libfile "solib1"
> +set libsrc  $srcdir/$subdir/$libfile.c
> +set lib_sl  [standard_output_file $libfile.sl]
> +
> +set lib_opts  {debug shlib}
> +set exec_opts [list debug shlib=$lib_sl]
> +
> +if { [build_executable "build shlib" $lib_sl $libsrc $lib_opts] != 0 } {
> +    return
> +}
> +
> +if { [prepare_for_testing "prepare" $testfile $srcfile $exec_opts] != 0 } {
> +    return
> +}
> +
> +gdb_test "break solib_main" "Breakpoint $decimal at .*"

You could use 'gdb_breakpoint solib_main' here.

> +
> +gdb_test "run" "Breakpoint $decimal, solib_main.*" "run with breakpoint"
> +
> +gdb_test "disable" ""

You could use 'delete_breakpoints' here I think.

Thanks,
Andrew


> +
> +gdb_test "set confirm off" ""
> +gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\]" \
> +    "run with disabled breakpoint"
> +
> +set bp_location [gdb_get_line_number "result = solib_main"]
> +gdb_test "break $bp_location" "Breakpoint $decimal at .*"
> -- 
> 2.52.0
  

Patch

diff --git a/gdb/objfiles.c b/gdb/objfiles.c
index 5c5b04c6458..247f0765391 100644
--- a/gdb/objfiles.c
+++ b/gdb/objfiles.c
@@ -47,6 +47,7 @@ 
 #include "gdb_bfd.h"
 #include "btrace.h"
 #include "gdbsupport/pathstuff.h"
+#include "stack.h"
 
 #include <algorithm>
 
@@ -685,6 +686,8 @@  objfile_purge_solibs (program_space *pspace)
       if (!(objf.flags & OBJF_USERLOADED) && (objf.flags & OBJF_SHARED))
 	objf.unlink ();
     }
+
+  clear_last_displayed_sal ();
 }
 
 /* See objfiles.h.  */
diff --git a/gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp b/gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
new file mode 100644
index 00000000000..2769d1630fc
--- /dev/null
+++ b/gdb/testsuite/gdb.base/solib-breakpoints-rerun.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 setting new line-breakpoint after re-running with disabled
+# breakpoint inside solib.
+
+require !use_gdb_stub
+require allow_shlib_tests
+
+standard_testfile so-impl-ld.c
+set libfile "solib1"
+set libsrc  $srcdir/$subdir/$libfile.c
+set lib_sl  [standard_output_file $libfile.sl]
+
+set lib_opts  {debug shlib}
+set exec_opts [list debug shlib=$lib_sl]
+
+if { [build_executable "build shlib" $lib_sl $libsrc $lib_opts] != 0 } {
+    return
+}
+
+if { [prepare_for_testing "prepare" $testfile $srcfile $exec_opts] != 0 } {
+    return
+}
+
+gdb_test "break solib_main" "Breakpoint $decimal at .*"
+
+gdb_test "run" "Breakpoint $decimal, solib_main.*" "run with breakpoint"
+
+gdb_test "disable" ""
+
+gdb_test "set confirm off" ""
+gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\]" \
+    "run with disabled breakpoint"
+
+set bp_location [gdb_get_line_number "result = solib_main"]
+gdb_test "break $bp_location" "Breakpoint $decimal at .*"