* [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