[v2] Forget the last displayed sal when the referenced objfile is destroyed

Message ID 20251222165805.88826-1-ssbssa@yahoo.de
State New
Headers
Series [v2] Forget the last displayed sal when the referenced objfile is destroyed |

Commit Message

Hannes Domani Dec. 22, 2025, 4:55 p.m. UTC
  When creating a new line-breakpoint after rerunning with a disabled or
removed 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 in the objfile
destructor, if it is pointing to that objfile.
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
---
Changes since v1:
- Clear last_displayed_symtab_info in the objfile destructor.
- Some test improvements.
---
 gdb/objfiles.c                                |  8 +++
 .../gdb.base/solib-breakpoints-rerun.exp      | 49 +++++++++++++++++++
 2 files changed, 57 insertions(+)
 create mode 100644 gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
  

Comments

Guinevere Larsen Jan. 8, 2026, 7:44 p.m. UTC | #1
On 12/22/25 1:55 PM, Hannes Domani wrote:
> When creating a new line-breakpoint after rerunning with a disabled or
> removed 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 in the objfile
> destructor, if it is pointing to that objfile.
> 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
> ---
> Changes since v1:
> - Clear last_displayed_symtab_info in the objfile destructor.
> - Some test improvements.
> ---

Hi! Thanks for working on this!

I took a look over this patch and everything makes sense, and I 
confirmed that this does fix the issue, so it looks reasonable,

Reviewed-By: Guinevere Larsen <guinevere@redhat.com>

Hopefully this gets approved soon.
  
Simon Marchi Jan. 8, 2026, 8:04 p.m. UTC | #2
On 12/22/25 11:55 AM, Hannes Domani wrote:
> When creating a new line-breakpoint after rerunning with a disabled or
> removed 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 in the objfile
> destructor, if it is pointing to that objfile.
> 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
> ---
> Changes since v1:
> - Clear last_displayed_symtab_info in the objfile destructor.
> - Some test improvements.
> ---
>  gdb/objfiles.c                                |  8 +++
>  .../gdb.base/solib-breakpoints-rerun.exp      | 49 +++++++++++++++++++
>  2 files changed, 57 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
> 
> diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> index 5c5b04c6458..0f9c9a046a2 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>
>  
> @@ -501,6 +502,13 @@ objfile::~objfile ()
>       and if so, call clear_current_source_symtab_and_line.  */
>    clear_current_source_symtab_and_line (this);
>  
> +  /* Check if last_displayed_symtab_info belongs to this objfile,
> +     and if so, call clear_last_displayed_sal.  */
> +  struct symtab *last_displayed_symtab = get_last_displayed_symtab ();
> +  if (last_displayed_symtab != nullptr
> +      && last_displayed_symtab->compunit ()->objfile () == this)
> +    clear_last_displayed_sal ();

Modern C++ nit:

  /* Check if last_displayed_symtab_info belongs to this objfile,
     and if so, call clear_last_displayed_sal.  */
  if (symtab *last_displayed_symtab = get_last_displayed_symtab ();
      last_displayed_symtab != nullptr
      && last_displayed_symtab->compunit ()->objfile () == this)
    clear_last_displayed_sal ();

> +
>    /* Rebuild section map next time we need it.  */
>    auto info = objfiles_pspace_data.get (pspace ());
>    if (info != nullptr)
> 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..d6089ca5dfb
> --- /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 removed
> +# 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_breakpoint solib_main
> +
> +gdb_test "run" "Breakpoint $decimal, solib_main.*" "run with breakpoint"
> +
> +delete_breakpoints
> +
> +gdb_test "set confirm off" ""
> +gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\]" \
> +    "run without breakpoint"

You can probably use "with confirm off -- run".


> +
> +set bp_location [gdb_get_line_number "result = solib_main"]
> +gdb_test "break $bp_location" "Breakpoint $decimal at .*"

Please add a comment stating that GDB would crash (use-after-free, with
ASan) during that break command, and why (high level, the one sentence
version).

LGTM with those fixed.

Approved-By: Simon Marchi <simon.marchi@efficios.com>

Simon
  
Hannes Domani Jan. 9, 2026, 11:20 a.m. UTC | #3
Am Donnerstag, 8. Januar 2026 um 21:05:00 MEZ hat Simon Marchi <simark@simark.ca> Folgendes geschrieben:

> On 12/22/25 11:55 AM, Hannes Domani wrote:
> > When creating a new line-breakpoint after rerunning with a disabled or
> > removed 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 in the objfile
> > destructor, if it is pointing to that objfile.
> > 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
> > ---
> > Changes since v1:
> > - Clear last_displayed_symtab_info in the objfile destructor.
> > - Some test improvements.
> > ---
> >  gdb/objfiles.c                                |  8 +++
> >  .../gdb.base/solib-breakpoints-rerun.exp      | 49 +++++++++++++++++++
> >  2 files changed, 57 insertions(+)
> >  create mode 100644 gdb/testsuite/gdb.base/solib-breakpoints-rerun.exp
> >
> > diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> > index 5c5b04c6458..0f9c9a046a2 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>
> > 
> > @@ -501,6 +502,13 @@ objfile::~objfile ()
> >      and if so, call clear_current_source_symtab_and_line.  */
> >    clear_current_source_symtab_and_line (this);
> > 
> > +  /* Check if last_displayed_symtab_info belongs to this objfile,
> > +    and if so, call clear_last_displayed_sal.  */
> > +  struct symtab *last_displayed_symtab = get_last_displayed_symtab ();
> > +  if (last_displayed_symtab != nullptr
> > +      && last_displayed_symtab->compunit ()->objfile () == this)
> > +    clear_last_displayed_sal ();

> Modern C++ nit:

>   /* Check if last_displayed_symtab_info belongs to this objfile,
>     and if so, call clear_last_displayed_sal.  */
>   if (symtab *last_displayed_symtab = get_last_displayed_symtab ();
>       last_displayed_symtab != nullptr
>       && last_displayed_symtab->compunit ()->objfile () == this)
>     clear_last_displayed_sal ();

> > +
> >    /* Rebuild section map next time we need it.  */
> >    auto info = objfiles_pspace_data.get (pspace ());
> >    if (info != nullptr)
> > 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..d6089ca5dfb
> > --- /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 removed
> > +# 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_breakpoint solib_main
> > +
> > +gdb_test "run" "Breakpoint $decimal, solib_main.*" "run with breakpoint"
> > +
> > +delete_breakpoints
> > +
> > +gdb_test "set confirm off" ""
> > +gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\]" \
> > +    "run without breakpoint"

> You can probably use "with confirm off -- run".


> > +
> > +set bp_location [gdb_get_line_number "result = solib_main"]
> > +gdb_test "break $bp_location" "Breakpoint $decimal at .*"

> Please add a comment stating that GDB would crash (use-after-free, with
> ASan) during that break command, and why (high level, the one sentence

> version).


> LGTM with those fixed.

> Approved-By: Simon Marchi <simon.marchi@efficios.com>

Pushed with these changes, thanks.


Hannes
  

Patch

diff --git a/gdb/objfiles.c b/gdb/objfiles.c
index 5c5b04c6458..0f9c9a046a2 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>
 
@@ -501,6 +502,13 @@  objfile::~objfile ()
      and if so, call clear_current_source_symtab_and_line.  */
   clear_current_source_symtab_and_line (this);
 
+  /* Check if last_displayed_symtab_info belongs to this objfile,
+     and if so, call clear_last_displayed_sal.  */
+  struct symtab *last_displayed_symtab = get_last_displayed_symtab ();
+  if (last_displayed_symtab != nullptr
+      && last_displayed_symtab->compunit ()->objfile () == this)
+    clear_last_displayed_sal ();
+
   /* Rebuild section map next time we need it.  */
   auto info = objfiles_pspace_data.get (pspace ());
   if (info != nullptr)
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..d6089ca5dfb
--- /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 removed
+# 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_breakpoint solib_main
+
+gdb_test "run" "Breakpoint $decimal, solib_main.*" "run with breakpoint"
+
+delete_breakpoints
+
+gdb_test "set confirm off" ""
+gdb_test "run" "\[Inferior $decimal \\(\[^\r\n\]*\\) exited normally\]" \
+    "run without breakpoint"
+
+set bp_location [gdb_get_line_number "result = solib_main"]
+gdb_test "break $bp_location" "Breakpoint $decimal at .*"