Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
@ 2026-05-27  9:57 Tom de Vries
  2026-05-28 16:43 ` Tom Tromey
  0 siblings, 1 reply; 6+ messages in thread
From: Tom de Vries @ 2026-05-27  9:57 UTC (permalink / raw)
  To: gdb-patches

I wrote a patch containing:
...
      dw2_instantiate_symtab (cu->per_cu, ...);
...
and ran into a use-after-free at a following use of cu.

The problem is that dw2_instantiate_symtab contains:
...
      free_cached_comp_units freer (per_objfile);
...
and that the destructor does:
...
  ~free_cached_comp_units ()
  {
    m_per_objfile->remove_all_cus ();
  }
...
which also frees the cu we used in the cu->per_cu argument to
dw2_instantiate_symtab.

Detect this situation using an assert in the free_cached_comp_units
constructor.

Tested on aarch64-linux.
---
 gdb/dwarf2/read.c | 3 +++
 gdb/dwarf2/read.h | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c761b732819..0dd0e944597 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -902,6 +902,9 @@ class free_cached_comp_units
   explicit free_cached_comp_units (dwarf2_per_objfile *per_objfile)
     : m_per_objfile (per_objfile)
   {
+    /* The destructor frees all cached comp units, including ones currently
+       cached, so check that there are no currently cached comp units.  */
+    gdb_assert (m_per_objfile->nr_of_cus () == 0);
   }
 
   ~free_cached_comp_units ()
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 15dd2abf3a1..603fe089d6a 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -969,6 +969,12 @@ struct dwarf2_per_objfile
   /* Free all cached compilation units.  */
   void remove_all_cus ();
 
+  /* Return the number of cached compilation units.  */
+  size_t nr_of_cus () const
+  {
+    return m_dwarf2_cus.size ();
+  }
+
   /* Increase the age counter on each CU compilation unit and free
      any that are too old.  */
   void age_comp_units ();

base-commit: a8740b7533b4df34d4d7bc5be9bb2108d019fce5
-- 
2.51.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
  2026-05-27  9:57 [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor Tom de Vries
@ 2026-05-28 16:43 ` Tom Tromey
  2026-05-30 12:52   ` Tom de Vries
  0 siblings, 1 reply; 6+ messages in thread
From: Tom Tromey @ 2026-05-28 16:43 UTC (permalink / raw)
  To: Tom de Vries; +Cc: gdb-patches

>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:

Tom> Detect this situation using an assert in the free_cached_comp_units
Tom> constructor.

Seems fine to me.

Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
  2026-05-28 16:43 ` Tom Tromey
@ 2026-05-30 12:52   ` Tom de Vries
  2026-06-01  8:02     ` Tom de Vries
  0 siblings, 1 reply; 6+ messages in thread
From: Tom de Vries @ 2026-05-30 12:52 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 5/28/26 6:43 PM, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
> 
> Tom> Detect this situation using an assert in the free_cached_comp_units
> Tom> constructor.
> 
> Seems fine to me.

Thanks for the review.

I committed this, but afterwards ran into trouble on x86_64-linux with 
test-cases gdb.ada/uninitialized-variable-record.exp and 
gdb.ada/uninitialized_vars.exp on x86_64-linux, so I've reverted this.

Thanks,
- Tom

> 
> Approved-By: Tom Tromey <tom@tromey.com>
> 
> Tom


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
  2026-05-30 12:52   ` Tom de Vries
@ 2026-06-01  8:02     ` Tom de Vries
  2026-06-01 15:18       ` Simon Marchi
  0 siblings, 1 reply; 6+ messages in thread
From: Tom de Vries @ 2026-06-01  8:02 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

On 5/30/26 2:52 PM, Tom de Vries wrote:
> On 5/28/26 6:43 PM, Tom Tromey wrote:
>>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
>>
>> Tom> Detect this situation using an assert in the free_cached_comp_units
>> Tom> constructor.
>>
>> Seems fine to me.
> 
> Thanks for the review.
> 
> I committed this, but afterwards ran into trouble on x86_64-linux with 
> test-cases gdb.ada/uninitialized-variable-record.exp and gdb.ada/ 
> uninitialized_vars.exp on x86_64-linux, so I've reverted this.
> 

I've investigated this, and found that this is due to calls to load_cu 
in places other than dw2_do_instantiate_symtab.

For test-case gdb.ada/uninitialized_vars.exp, it's 
dwarf2_fetch_die_loc_cu_off.

I do wonder if ~free_cached_comp_units is a bit overeager, and should 
refrain from deleting cached comp units that were present at 
construction time.

Anyway, the assert detected the use-after-free I created, but just 
doesn't hold in general.

Thanks,
- Tom

> Thanks,
> - Tom
> 
>>
>> Approved-By: Tom Tromey <tom@tromey.com>
>>
>> Tom
> 


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
  2026-06-01  8:02     ` Tom de Vries
@ 2026-06-01 15:18       ` Simon Marchi
  2026-06-08 14:20         ` Tom de Vries
  0 siblings, 1 reply; 6+ messages in thread
From: Simon Marchi @ 2026-06-01 15:18 UTC (permalink / raw)
  To: Tom de Vries, Tom Tromey; +Cc: gdb-patches

On 6/1/26 4:02 AM, Tom de Vries wrote:
> On 5/30/26 2:52 PM, Tom de Vries wrote:
>> On 5/28/26 6:43 PM, Tom Tromey wrote:
>>>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
>>>
>>> Tom> Detect this situation using an assert in the free_cached_comp_units
>>> Tom> constructor.
>>>
>>> Seems fine to me.
>>
>> Thanks for the review.
>>
>> I committed this, but afterwards ran into trouble on x86_64-linux with test-cases gdb.ada/uninitialized-variable-record.exp and gdb.ada/ uninitialized_vars.exp on x86_64-linux, so I've reverted this.
>>
> 
> I've investigated this, and found that this is due to calls to load_cu in places other than dw2_do_instantiate_symtab.
> 
> For test-case gdb.ada/uninitialized_vars.exp, it's dwarf2_fetch_die_loc_cu_off.
> 
> I do wonder if ~free_cached_comp_units is a bit overeager, and should refrain from deleting cached comp units that were present at construction time.
> 
> Anyway, the assert detected the use-after-free I created, but just doesn't hold in general.

Let's try to understand why things are the way they are currently.

Why do we want to delete the just created dwarf2_cus in
dw2_instantiate_symtab?  I presume it's because the chances of them
being useful again are slim.  We created the GDB types and symbols, we
don't need to keep the DIE structure  loaded in memory, so it's better
to free up the memory.

The cases where the dwarf2_cus are needed again later appear to be when
evaluating a some DWARF operator that refers to other DIEs directly,
such as DW_OP_call*, DW_OP_implicit_pointer, DW_OP_GNU_variable_value,
etc.

 - dwarf2_fetch_die_loc_sect_off
 - dwarf2_fetch_die_loc_cu_off
 - dwarf2_fetch_constant_bytes
 - dwarf2_fetch_die_type_sect_off

In those cases we re-load the right dwarf2_cu in memory to be able to
look up the DIE and get what we want from it.  In those cases, we don't
free up the just-loaded dwarf2_cu right away, because it presumably has
good chances of being needed again in the near future, for other
operators.  We instead use the "age_comp_units" mechanism, which frees
the dwarf2_cus once they have been sitting there unused for a while.

I am unable to reproduce the gdb.ada failures, but the case that you
looked at appears to be one where a dwarf2_cu was loaded by one of those
"dwarf2_fetch_*" functions, while evaluating a DWARF expression, and
then dw2_instantiate_symtab was called to expand a compunit into full
symbols.  So free_cached_comp_units deletes the dwarf2_cu previously
cached by the "dwarf2_fetch_*" functions.  I don't think this is wrong
(as in a correctness bug), so I don't think the assert was right, it
might just be ineffcient cache usage.  I don't know if it's problematic
enough to be worth fixing, but if you can think of a simple solution for
dw2_instantiate_symtab not to delete the pre-existing dwarf2_cus, we
could consider it.

It would perhaps be good to investigate whether this "age comp units"
machinery is still working as initially intended.  It's possible that
will all the refactors we've done over the years, it's not working as
intended.  And because it's just a cache, it would not break any tests,
but it could cause peformance problems.  While searching the code, I
looked where `cu->last_used` is reset, to prevent the CU from being
freed by age_comp_units.  It is reset in maybe_queue_comp_unit, which is
itself called in:

 - follow_die_sig_1
 - process_imported_unit_die
 - follow_die_offset

Note that those are all used in cross-CU reference cases, when a CU
needs something from another CU.

`age_comp_units()` is called in:

 - dw2_do_instantiate_symtab, when done expanding the symtab(s)
 - dwarf2_fetch_die_loc_sect_off, when done fetching the location info

Some fishy things I spotted:

 - The age_comp_units() call in dw2_do_instantiate_symtab suggests that
   the comp unit aging system was also meant to be used when expanding
   symtabs.  The fact that all the places that reset `cu->last_used` are
   some that handle cross-CU references also suggests this.  The idea
   might have been that if a CU refers to another CU (via DW_AT_import
   for instance), then there is a good chance that subsequent CUs will
   also refer to that second CUs.  So when you're done expanding the
   first CU, better keep that second CU in cache for when you'll be
   expanding more CUs.  However, if we free up all cached CUs in
   dw2_instantiate_symtab via free_cached_comp_units, doesn't it defeat
   the purpose?  Why call age_comp_units()  in dw2_do_instantiate_symtab
   if we're going to free them all up in the caller anyway?

 - Calling dwarf2_fetch_die_loc_sect_off ages the comp units, but
   shouldn't it also reset the `cu->last_used` field of the CU from
   which we found the info?  Otherwise, repeated calls to
   dwarf2_fetch_die_loc_sect_off targetting the same CU will cause that
   CU to be freed, even if we just used it and will keep needing it
   (causing it to be re-loaded from scratch the next time).

Simon

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor
  2026-06-01 15:18       ` Simon Marchi
@ 2026-06-08 14:20         ` Tom de Vries
  0 siblings, 0 replies; 6+ messages in thread
From: Tom de Vries @ 2026-06-08 14:20 UTC (permalink / raw)
  To: Simon Marchi, Tom Tromey; +Cc: gdb-patches

On 6/1/26 5:18 PM, Simon Marchi wrote:
> On 6/1/26 4:02 AM, Tom de Vries wrote:
>> On 5/30/26 2:52 PM, Tom de Vries wrote:
>>> On 5/28/26 6:43 PM, Tom Tromey wrote:
>>>>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
>>>>
>>>> Tom> Detect this situation using an assert in the free_cached_comp_units
>>>> Tom> constructor.
>>>>
>>>> Seems fine to me.
>>>
>>> Thanks for the review.
>>>
>>> I committed this, but afterwards ran into trouble on x86_64-linux with test-cases gdb.ada/uninitialized-variable-record.exp and gdb.ada/ uninitialized_vars.exp on x86_64-linux, so I've reverted this.
>>>
>>
>> I've investigated this, and found that this is due to calls to load_cu in places other than dw2_do_instantiate_symtab.
>>
>> For test-case gdb.ada/uninitialized_vars.exp, it's dwarf2_fetch_die_loc_cu_off.
>>
>> I do wonder if ~free_cached_comp_units is a bit overeager, and should refrain from deleting cached comp units that were present at construction time.
>>
>> Anyway, the assert detected the use-after-free I created, but just doesn't hold in general.
> 
> Let's try to understand why things are the way they are currently.
> 
> Why do we want to delete the just created dwarf2_cus in
> dw2_instantiate_symtab?  I presume it's because the chances of them
> being useful again are slim.  We created the GDB types and symbols, we
> don't need to keep the DIE structure  loaded in memory, so it's better
> to free up the memory.
> 
> The cases where the dwarf2_cus are needed again later appear to be when
> evaluating a some DWARF operator that refers to other DIEs directly,
> such as DW_OP_call*, DW_OP_implicit_pointer, DW_OP_GNU_variable_value,
> etc.
> 
>   - dwarf2_fetch_die_loc_sect_off
>   - dwarf2_fetch_die_loc_cu_off
>   - dwarf2_fetch_constant_bytes
>   - dwarf2_fetch_die_type_sect_off
> 
> In those cases we re-load the right dwarf2_cu in memory to be able to
> look up the DIE and get what we want from it.  In those cases, we don't
> free up the just-loaded dwarf2_cu right away, because it presumably has
> good chances of being needed again in the near future, for other
> operators.  We instead use the "age_comp_units" mechanism, which frees
> the dwarf2_cus once they have been sitting there unused for a while.
> 
> I am unable to reproduce the gdb.ada failures, but the case that you
> looked at appears to be one where a dwarf2_cu was loaded by one of those
> "dwarf2_fetch_*" functions, while evaluating a DWARF expression, and
> then dw2_instantiate_symtab was called to expand a compunit into full
> symbols.  So free_cached_comp_units deletes the dwarf2_cu previously
> cached by the "dwarf2_fetch_*" functions.  I don't think this is wrong
> (as in a correctness bug), so I don't think the assert was right, it
> might just be ineffcient cache usage.  I don't know if it's problematic
> enough to be worth fixing, but if you can think of a simple solution for
> dw2_instantiate_symtab not to delete the pre-existing dwarf2_cus, we
> could consider it.
> 
> It would perhaps be good to investigate whether this "age comp units"
> machinery is still working as initially intended.  It's possible that
> will all the refactors we've done over the years, it's not working as
> intended.  And because it's just a cache, it would not break any tests,
> but it could cause peformance problems.  While searching the code, I
> looked where `cu->last_used` is reset, to prevent the CU from being
> freed by age_comp_units.  It is reset in maybe_queue_comp_unit, which is
> itself called in:
> 
>   - follow_die_sig_1
>   - process_imported_unit_die
>   - follow_die_offset
> 
> Note that those are all used in cross-CU reference cases, when a CU
> needs something from another CU.
> 
> `age_comp_units()` is called in:
> 
>   - dw2_do_instantiate_symtab, when done expanding the symtab(s)
>   - dwarf2_fetch_die_loc_sect_off, when done fetching the location info
> 
> Some fishy things I spotted:
> 
>   - The age_comp_units() call in dw2_do_instantiate_symtab suggests that
>     the comp unit aging system was also meant to be used when expanding
>     symtabs.  The fact that all the places that reset `cu->last_used` are
>     some that handle cross-CU references also suggests this.  The idea
>     might have been that if a CU refers to another CU (via DW_AT_import
>     for instance), then there is a good chance that subsequent CUs will
>     also refer to that second CUs.  So when you're done expanding the
>     first CU, better keep that second CU in cache for when you'll be
>     expanding more CUs.  However, if we free up all cached CUs in
>     dw2_instantiate_symtab via free_cached_comp_units, doesn't it defeat
>     the purpose?  Why call age_comp_units()  in dw2_do_instantiate_symtab
>     if we're going to free them all up in the caller anyway?
> 
>   - Calling dwarf2_fetch_die_loc_sect_off ages the comp units, but
>     shouldn't it also reset the `cu->last_used` field of the CU from
>     which we found the info?  Otherwise, repeated calls to
>     dwarf2_fetch_die_loc_sect_off targetting the same CU will cause that
>     CU to be freed, even if we just used it and will keep needing it
>     (causing it to be re-loaded from scratch the next time).
> 

Hi Simon,

thanks for the comments.

I'm not sure when I'll have the time to follow up on all this, so I've 
filed a review PR ( https://sourceware.org/bugzilla/show_bug.cgi?id=34243 ).

Thanks,
- Tom

> Simon


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-06-08 14:21 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-27  9:57 [PATCH] [gdb/symtab] Add assert in free_cached_comp_units constructor Tom de Vries
2026-05-28 16:43 ` Tom Tromey
2026-05-30 12:52   ` Tom de Vries
2026-06-01  8:02     ` Tom de Vries
2026-06-01 15:18       ` Simon Marchi
2026-06-08 14:20         ` Tom de Vries

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox