Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH v2 0/8] Correctly handle inline functions with dwz
@ 2026-01-26 21:33 Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 1/8] Don't call add_dependence from index_imported_unit Tom Tromey
                   ` (11 more replies)
  0 siblings, 12 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

The new indexer does not correctly handle inline functions when 'dwz'
is used to compress the DWARF.  This series fixes the bug, cleaning up
a number of other things on the way.

I've separately regression tested each patch in this series on x86-64
Fedora 41.  I've also regression tested the series as a whole with the
dwz, gdb-index, and debug-names boards.

Even with that I messed up somehow, so v1 didn't actually fix the bug
in question.  I must have modified the patches after testing..?

Anyway in v2 I've moved the line recording the CU inclusion and added
a comment explaining the placement.  I re-ran the aformentioned tests
and didn't touch anything.

Signed-off-by: Tom Tromey <tom@tromey.com>
---
Changes in v2:
- Fixed fix from Tom de Vries for IS_INLINED debug display
- Moved line to record inclusion
- Link to v1: https://inbox.sourceware.org/gdb-patches/20260125-dw-inline-fixup-pr-symtab-30728-2-v1-0-e9973a4a401a@tromey.com

---
Tom Tromey (8):
      Don't call add_dependence from index_imported_unit
      Skip partial units in process_psymtab_comp_unit
      Don't consider DW_TAG_inlined_subroutine as interesting
      Combine two cases in cooked_index_functions::search
      Remove C++ special case from process_imported_unit_die
      Have iterate_over_one_compunit_symtab search included symtabs
      Handle inline functions with dwz
      Update .debug_names documentation

 gdb/doc/gdb.texinfo              |  37 +++++++
 gdb/dwarf2/abbrev.c              |   1 -
 gdb/dwarf2/cooked-index-entry.c  |  24 +++++
 gdb/dwarf2/cooked-index-entry.h  |  39 ++++++-
 gdb/dwarf2/cooked-index-worker.c |  14 +++
 gdb/dwarf2/cooked-index-worker.h |  26 +++--
 gdb/dwarf2/cooked-indexer.c      |  60 +++++++----
 gdb/dwarf2/cooked-indexer.h      |   8 +-
 gdb/dwarf2/cu.h                  |   6 +-
 gdb/dwarf2/index-write.c         | 223 ++++++++++++++++++++++-----------------
 gdb/dwarf2/read.c                |  85 +++++++++------
 gdb/dwarf2/read.h                |  37 +++++++
 gdb/symfile-debug.c              |  27 +++--
 13 files changed, 410 insertions(+), 177 deletions(-)
---
base-commit: 8b601b78f71e6ae7b5aed7c0f8ac30d469f16325
change-id: 20260125-dw-inline-fixup-pr-symtab-30728-2-7c31bf38fbe3

Best regards,
-- 
Tom Tromey <tom@tromey.com>


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

* [PATCH v2 1/8] Don't call add_dependence from index_imported_unit
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 2/8] Skip partial units in process_psymtab_comp_unit Tom Tromey
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

cooked_indexer::index_imported_unit calls add_dependence.  However,
this field is only needed during full symbol reading.  Remove this
call.
---
 gdb/dwarf2/cooked-indexer.c | 6 +-----
 gdb/dwarf2/cu.h             | 6 +++---
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
index 1d5b5bcca40..b5ccf4588c4 100644
--- a/gdb/dwarf2/cooked-indexer.c
+++ b/gdb/dwarf2/cooked-indexer.c
@@ -431,11 +431,7 @@ cooked_indexer::index_imported_unit (cutu_reader *reader,
   cutu_reader *new_reader
     = ensure_cu_exists (reader, *target, true);
   if (new_reader != nullptr)
-    {
-      index_dies (new_reader, new_reader->info_ptr (), nullptr, false);
-
-      reader->cu ()->add_dependence (new_reader->cu ()->per_cu);
-    }
+    index_dies (new_reader, new_reader->info_ptr (), nullptr, false);
 
   return info_ptr;
 }
diff --git a/gdb/dwarf2/cu.h b/gdb/dwarf2/cu.h
index 97c0b87121b..2e13ad0c397 100644
--- a/gdb/dwarf2/cu.h
+++ b/gdb/dwarf2/cu.h
@@ -288,9 +288,9 @@ struct dwarf2_cu
      symbols are being read.  */
   buildsym_compunit_up m_builder;
 
-  /* A set of pointers to dwarf2_per_cu objects for compilation units referenced
-     by this one.  Only used during full symbol processing; partial symbol
-     tables do not have dependencies.  */
+  /* A set of pointers to dwarf2_per_cu objects for compilation units
+     referenced by this one.  Only used during full symbol
+     processing.  */
   gdb::unordered_set<dwarf2_per_cu *> m_dependencies;
 
 public:

-- 
2.49.0


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

* [PATCH v2 2/8] Skip partial units in process_psymtab_comp_unit
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 1/8] Don't call add_dependence from index_imported_unit Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 3/8] Don't consider DW_TAG_inlined_subroutine as interesting Tom Tromey
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

process_psymtab_comp_unit passes 'false' for the 'skip_partial'
argument to the cutu_reader constructor, but then proceeds to skip
partial units.  This patch simplifies the code by passing 'true'
instead.
---
 gdb/dwarf2/read.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 036ec7e6337..b8f758ae8c7 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -3436,7 +3436,7 @@ cooked_index_worker_debug_info::process_unit
       const abbrev_table_cache &abbrev_table_cache
 	= storage->get_abbrev_table_cache ();
       auto new_reader = std::make_unique<cutu_reader> (*this_cu, *per_objfile,
-						       nullptr, nullptr, false,
+						       nullptr, nullptr, true,
 						       std::nullopt,
 						       &abbrev_table_cache);
 
@@ -3451,7 +3451,7 @@ cooked_index_worker_debug_info::process_unit
 
   if (this_cu->is_debug_types ())
     process_type_unit (reader, storage);
-  else if (reader->top_level_die ()->tag != DW_TAG_partial_unit)
+  else
     {
       bool nope = false;
       if (this_cu->scanned.compare_exchange_strong (nope, true))

-- 
2.49.0


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

* [PATCH v2 3/8] Don't consider DW_TAG_inlined_subroutine as interesting
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 1/8] Don't call add_dependence from index_imported_unit Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 2/8] Skip partial units in process_psymtab_comp_unit Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 4/8] Combine two cases in cooked_index_functions::search Tom Tromey
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

tag_interesting_for_index returns true for DW_TAG_inlined_subroutine
-- but this tag only appears where the function is actually inlined,
which is not interesting for the index.
---
 gdb/dwarf2/abbrev.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/gdb/dwarf2/abbrev.c b/gdb/dwarf2/abbrev.c
index 49564db275b..44d5c87a5f7 100644
--- a/gdb/dwarf2/abbrev.c
+++ b/gdb/dwarf2/abbrev.c
@@ -46,7 +46,6 @@ tag_interesting_for_index (dwarf_tag tag)
     case DW_TAG_enumerator:
     case DW_TAG_imported_declaration:
     case DW_TAG_imported_unit:
-    case DW_TAG_inlined_subroutine:
     case DW_TAG_interface_type:
     case DW_TAG_module:
     case DW_TAG_namespace:

-- 
2.49.0


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

* [PATCH v2 4/8] Combine two cases in cooked_index_functions::search
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (2 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 3/8] Don't consider DW_TAG_inlined_subroutine as interesting Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 5/8] Remove C++ special case from process_imported_unit_die Tom Tromey
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This combines a couple of 'if' statements in
cooked_index_functions::search.  This simplifies the code a little and
also makes a subsequent patch a bit simpler as well.
---
 gdb/dwarf2/read.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index b8f758ae8c7..8f2608b4607 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -14236,25 +14236,22 @@ cooked_index_functions::search
 	      || !entry->matches (domain))
 	    continue;
 
-	  if (lang_matcher != nullptr)
+	  /* If LANG_MATCHER is non-NULL, try to skip CUs with a
+	     non-matching language.  The other case here is a bit of a
+	     hack to support .gdb_index.  Since .gdb_index does not
+	     record languages, and since we want to know the language
+	     to avoid excessive CU expansion due to false matches, if
+	     we see a symbol with an unknown language we find the CU's
+	     language.  Only the .gdb_index reader creates such
+	     symbols.  */
+	  enum language entry_lang = entry->lang;
+	  if (lang_matcher != nullptr || entry_lang == language_unknown)
 	    {
-	      /* Try to skip CUs with non-matching language.  */
 	      entry->per_cu->ensure_lang (per_objfile);
-	      if (!entry->per_cu->maybe_multi_language ()
+	      if (lang_matcher != nullptr
+		  && !entry->per_cu->maybe_multi_language ()
 		  && !lang_matcher (entry->per_cu->lang ()))
 		continue;
-	    }
-
-	  /* This is a bit of a hack to support .gdb_index.  Since
-	     .gdb_index does not record languages, and since we want
-	     to know the language to avoid excessive CU expansion due
-	     to false matches, if we see a symbol with an unknown
-	     language we find the CU's language.  Only the .gdb_index
-	     reader creates such symbols.  */
-	  enum language entry_lang = entry->lang;
-	  if (entry_lang == language_unknown)
-	    {
-	      entry->per_cu->ensure_lang (per_objfile);
 	      entry_lang = entry->per_cu->lang ();
 	    }
 

-- 
2.49.0


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

* [PATCH v2 5/8] Remove C++ special case from process_imported_unit_die
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (3 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 4/8] Combine two cases in cooked_index_functions::search Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-26 21:33 ` [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs Tom Tromey
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

process_imported_unit_die has a special case for C++, added as a
performance improvement.

While I somewhat agree with the general idea of this snippet --
importing a compilation unit seems like a strange thing to do, given
that partial units exist -- I think there are two issues with it.

First, it is specific to C++.  I don't see any real reason that this
should be the case.

Second, it does not have a corresponding bit of code in the indexer.
This means that the cooked index's view of the DWARF differs from the
full reader's view here.  This causes regressions in this series,
because the indexer assumes that reading a CU will cause all the
imported CUs to be read as a side effect -- but that does not happen
here.

I think fixing this in the indexer is not trivial.  The reason for
this is that the indexer works in parallel, and once a reader has
acquired the "scan" bit for a CU, it is obligated to read it.
However, in this case this would require making a new cooked indexer.

Instead, because (1) this is weird and rare DWARF anyway, and (2) this
is just a performance optimization, I propose removing this.
---
 gdb/dwarf2/read.c | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 8f2608b4607..7f6198aa4d4 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -5015,15 +5015,6 @@ process_imported_unit_die (struct die_info *die, struct dwarf2_cu *cu)
       dwarf2_per_cu *per_cu
 	= dwarf2_find_containing_unit ({ &section, sect_off }, per_objfile);
 
-      /* We're importing a C++ compilation unit with tag DW_TAG_compile_unit
-	 into another compilation unit, at root level.  Regard this as a hint,
-	 and ignore it.  This is a best effort, it only works if unit_type and
-	 lang are already set.  */
-      if (die->parent && die->parent->parent == NULL
-	  && per_cu->unit_type (false) == DW_UT_compile
-	  && per_cu->lang (false) == language_cplus)
-	return;
-
       /* If necessary, add it to the queue and load its DIEs.  */
       if (maybe_queue_comp_unit (cu, per_cu, per_objfile))
 	load_full_comp_unit (per_cu, per_objfile, false, cu->lang ());

-- 
2.49.0


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

* [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (4 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 5/8] Remove C++ special case from process_imported_unit_die Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-01-30 22:04   ` Simon Marchi
  2026-01-26 21:33 ` [PATCH v2 7/8] Handle inline functions with dwz Tom Tromey
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

A latent bug in the search-via-psyms series was that it neglected to
update iterate_over_one_compunit_symtab to search included symtabs.

I think lookups that needed this search used to work by accident -- an
included CU would be expanded but not searched, but a search of all
compunits() would then find it.

This patch corrects the oversight.  I'm not sure if this bug is
readily visible without the next patch.
---
 gdb/symfile-debug.c | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 6562dcad433..aecbd1f6deb 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -171,17 +171,12 @@ objfile::forget_cached_source_info ()
    the specified compunit symtab is also searched.  */
 
 static bool
-iterate_over_one_compunit_symtab (const char *name,
+iterate_over_one_compunit_symtab (const char *base_name,
+				  const char *name,
 				  const char *real_path,
 				  compunit_symtab *cust,
 				  gdb::function_view<bool (symtab *)> callback)
 {
-  const char *base_name = lbasename (name);
-
-  /* Skip included compunits.  */
-  if (cust->user != nullptr)
-    return false;
-
   for (symtab *s : cust->filetabs ())
     {
       if (compare_filenames_for_search (s->filename (), name))
@@ -224,6 +219,16 @@ iterate_over_one_compunit_symtab (const char *name,
 	}
     }
 
+  if (cust->includes != nullptr)
+    {
+      for (compunit_symtab **iter = cust->includes;
+	   *iter != nullptr;
+	   ++iter)
+	if (iterate_over_one_compunit_symtab (base_name, name, real_path,
+					      *iter, callback))
+	  return true;
+    }
+
   return false;
 }
 
@@ -257,10 +262,14 @@ objfile::map_symtabs_matching_filename
 
   auto listener = [&] (compunit_symtab *symtab)
   {
+    /* Skip included compunits.  */
+    if (symtab->user != nullptr)
+      return true;
+
     /* CALLBACK returns false to keep going and true to continue, so
        we have to invert the result here, for search.  */
-    return !iterate_over_one_compunit_symtab (name, real_path, symtab,
-					      callback);
+    return !iterate_over_one_compunit_symtab (name_basename, name, real_path,
+					      symtab, callback);
   };
 
   for (const auto &iter : qf)

-- 
2.49.0


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

* [PATCH v2 7/8] Handle inline functions with dwz
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (5 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-02-10  3:51   ` Simon Marchi
  2026-01-26 21:33 ` [PATCH v2 8/8] Update .debug_names documentation Tom Tromey
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

Currently, gdb does not properly handle inline functions when dwz is
used.  This can be seen by running gdb.cp/breakpoint-locs.exp with the
cc-with-dwz target board.

The problem here is that inline functions need special handling in the
dwz case.

First, recall that a DWARF partial unit cannot, in general, be read in
isolation, as it may not have a language.  To handle this, gdb defers
scanning partial units directly, and instead scans them in the context
of some including CU.

Entries coming from the PU are attributed to this reading CU.  If
multiple CUs import a PU, gdb has the reader threads race to see which
one does the actual reading.

However, if an inline function is moved into a partial unit, then that
means it has potentially been inlined somewhere in every including CU.

Thus, when linespec searches for this function, each such including CU
should be expanded.  But because gdb only attributed the function's
index entry to one CU, only that particular one is expanded.

This patch fixes this bug.  All inclusions of a PU are recorded.
Entries coming from a PU are attributed to that PU.  For most entries
coming from the PU, a single "canonical" outer CU is chosen to expand.
However, when an inline function is found, all such CUs are expanded.

A change was also needed to the index writer to handle this case.
There, entries coming from a PU should note the correct including CU.
This must be done because, with .debug_names or .gdb_index, gdb does
not have information about unit imports.  Handling inline functions
here means writing out a separate entry for each outermost CU that
includes the function's PU.

I did consider changing the cooked indexer to create an internal table
more similar to what would be created by the .debug_names (e.g.)
reader: that is, multiple entries for each inline function.  However,
this seemed more expensive at read time, and a main goal of the cooked
indexer is to be fast.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30728
---
 gdb/dwarf2/cooked-index-entry.c  |  24 +++++
 gdb/dwarf2/cooked-index-entry.h  |  39 ++++++-
 gdb/dwarf2/cooked-index-worker.c |  14 +++
 gdb/dwarf2/cooked-index-worker.h |  26 +++--
 gdb/dwarf2/cooked-indexer.c      |  54 +++++++---
 gdb/dwarf2/cooked-indexer.h      |   8 +-
 gdb/dwarf2/index-write.c         | 223 ++++++++++++++++++++++-----------------
 gdb/dwarf2/read.c                |  49 +++++++--
 gdb/dwarf2/read.h                |  37 +++++++
 9 files changed, 339 insertions(+), 135 deletions(-)

diff --git a/gdb/dwarf2/cooked-index-entry.c b/gdb/dwarf2/cooked-index-entry.c
index c24dcf445fe..2f61e5c4c57 100644
--- a/gdb/dwarf2/cooked-index-entry.c
+++ b/gdb/dwarf2/cooked-index-entry.c
@@ -18,6 +18,7 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "dwarf2/cooked-index-entry.h"
+#include "dwarf2/read.h"
 #include "dwarf2/tag.h"
 #include "gdbsupport/selftest.h"
 
@@ -33,6 +34,7 @@ to_string (cooked_index_flag flags)
     MAP_ENUM_FLAG (IS_TYPE_DECLARATION),
     MAP_ENUM_FLAG (IS_PARENT_DEFERRED),
     MAP_ENUM_FLAG (IS_SYNTHESIZED),
+    MAP_ENUM_FLAG (IS_INLINED),
   };
 
   return flags.to_string (mapping);
@@ -240,6 +242,28 @@ cooked_index_entry::write_scope (struct obstack *storage,
   obstack_grow (storage, sep, strlen (sep));
 }
 
+/* See cooked_index_entry.h.  */
+
+void
+cooked_index_entry::force_set_language () const
+{
+  gdb_assert (lang == language_unknown);
+  lang = per_cu->lang ();
+}
+
+/* See cooked-index-entry.h.  */
+
+bool
+cooked_index_entry::visit_defining_cus (per_cu_callback callback) const
+{
+  if ((flags & IS_INLINED) == 0)
+    return callback (per_cu->canonical_outermost_cu ());
+
+  return per_cu->recursively_visit_cus (callback);
+}
+
+/* See cooked-index-entry.h.  */
+
 INIT_GDB_FILE (dwarf2_entry)
 {
 #if GDB_SELF_TEST
diff --git a/gdb/dwarf2/cooked-index-entry.h b/gdb/dwarf2/cooked-index-entry.h
index a64b2674056..9eed2ca9c46 100644
--- a/gdb/dwarf2/cooked-index-entry.h
+++ b/gdb/dwarf2/cooked-index-entry.h
@@ -44,6 +44,9 @@ enum cooked_index_flag_enum : unsigned char
   /* True if this entry was synthesized by gdb (as opposed to coming
      directly from the DWARF).  */
   IS_SYNTHESIZED = 32,
+  /* True if this is a function that has DW_AT_inline set in a way
+     that indicates it was inlined.  */
+  IS_INLINED = 64,
 };
 DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag);
 
@@ -222,6 +225,31 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
     return m_parent_entry.deferred;
   }
 
+  /* Force the language to be set to the CU's language.  This may only
+     be called when the language is unknown, which can only happen
+     with .gdb_index.  This method is const because it is called from
+     a location that normally handles const entries.  */
+  void force_set_language () const;
+
+  /* Type of callback used when visiting defining CUs.  */
+  using per_cu_callback = gdb::function_view<bool (dwarf2_per_cu *)>;
+
+  /* Calls CALLBACK for each CU that "defines" this entry.
+
+     If any call to CALLBACK returns false, this immediately returns
+     false (skipping the remaining calls); otherwise returns true.
+
+     For most entries, there is a single defining CU, which is the
+     canonical outermost includer of the CU holding the entry.  In the
+     most typical case (i.e., where "dwz" has not been used), this is
+     just the entry's CU.
+
+     However, conceptually an inlined subroutine is defined in each
+     such outermost includer -- if a subroutine is inlined in many
+     places, and then "dwz" is run, the IS_INLINED subroutine may be
+     defined in some CU that is included by many other CUs.  */
+  bool visit_defining_cus (per_cu_callback callback) const;
+
   /* The name as it appears in DWARF.  This always points into one of
      the mapped DWARF sections.  Note that this may be the name or the
      linkage name -- two entries are created for DIEs which have both
@@ -233,11 +261,16 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
   enum dwarf_tag tag;
   /* Any flags attached to this entry.  */
   cooked_index_flag flags;
-  /* The language of this symbol.  */
-  ENUM_BITFIELD (language) lang : LANGUAGE_BITS;
+  /* The language of this symbol.  This is mutable because there is a
+     situation where the language is initially unknown, and then only
+     filled in later.  In particular this can happen when using
+     .gdb_index.  See also cooked_index_functions::search.  */
+  mutable ENUM_BITFIELD (language) lang : LANGUAGE_BITS;
   /* The offset of this DIE.  */
   sect_offset die_offset;
-  /* The CU from which this entry originates.  */
+  /* The CU from which this entry originates.  This may point to a
+     partial CU, which can't be expanded in isolation; see
+     visit_defining_cus.  */
   dwarf2_per_cu *per_cu;
 
 private:
diff --git a/gdb/dwarf2/cooked-index-worker.c b/gdb/dwarf2/cooked-index-worker.c
index 24be57edfb5..cc2c4d22c17 100644
--- a/gdb/dwarf2/cooked-index-worker.c
+++ b/gdb/dwarf2/cooked-index-worker.c
@@ -107,6 +107,16 @@ cooked_index_worker_result::emit_complaints_and_exceptions
 
 /* See cooked-index-worker.h.  */
 
+void
+cooked_index_worker_result::invert_cu_inclusions ()
+{
+  for (auto &iter : m_inclusions)
+    for (dwarf2_per_cu *includee : iter.second)
+      includee->add_includer (iter.first);
+}
+
+/* See cooked-index-worker.h.  */
+
 void
 cooked_index_worker::start ()
 {
@@ -241,6 +251,10 @@ cooked_index_worker::done_reading ()
       m_all_parents_map.add_map (*one_result.get_parent_map ());
   }
 
+  /* Update all the CU inclusion information.  */
+  for (auto &item : m_results)
+    item.invert_cu_inclusions ();
+
   dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
   cooked_index *table
     = (gdb::checked_static_cast<cooked_index *>
diff --git a/gdb/dwarf2/cooked-index-worker.h b/gdb/dwarf2/cooked-index-worker.h
index 847b8ce5589..0ff847514bd 100644
--- a/gdb/dwarf2/cooked-index-worker.h
+++ b/gdb/dwarf2/cooked-index-worker.h
@@ -62,17 +62,6 @@ class cooked_index_worker_result
 
   /* Add an entry to the index.  The arguments describe the entry; see
      cooked-index.h.  The new entry is returned.  */
-  cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
-			   cooked_index_flag flags,
-			   const char *name,
-			   cooked_index_entry_ref parent_entry,
-			   dwarf2_per_cu *per_cu)
-  {
-    return m_shard->add (die_offset, tag, flags, per_cu->lang (),
-			 name, parent_entry, per_cu);
-  }
-
-  /* Overload that allows the language to be specified.  */
   cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
 			   cooked_index_flag flags, enum language lang,
 			   const char *name,
@@ -150,6 +139,16 @@ class cooked_index_worker_result
   void emit_complaints_and_exceptions
        (gdb::unordered_set<gdb_exception> &seen_exceptions);
 
+  /* Record that the FROM CU included the TO CU.  */
+  void record_inclusion (dwarf2_per_cu *from, dwarf2_per_cu *to)
+  {
+    m_inclusions[from].push_back (to);
+  }
+
+  /* Update CUs with all the inclusion information that we've
+     discovered.  */
+  void invert_cu_inclusions ();
+
 private:
   /* The abbrev table cache used by this indexer.  */
   abbrev_table_cache m_abbrev_table_cache;
@@ -194,6 +193,11 @@ class cooked_index_worker_result
 
   /* Exceptions that we're storing to emit later.  */
   std::vector<gdb_exception> m_exceptions;
+
+  /* Map from a CU to a list of all the CUs that it directly
+     includes.  */
+  gdb::unordered_map<dwarf2_per_cu *,
+		     std::vector<dwarf2_per_cu *>> m_inclusions;
 };
 
 /* The possible states of the index.  See the explanatory comment
diff --git a/gdb/dwarf2/cooked-indexer.c b/gdb/dwarf2/cooked-indexer.c
index b5ccf4588c4..239da646450 100644
--- a/gdb/dwarf2/cooked-indexer.c
+++ b/gdb/dwarf2/cooked-indexer.c
@@ -27,9 +27,8 @@
 /* See cooked-indexer.h.  */
 
 cooked_indexer::cooked_indexer (cooked_index_worker_result *storage,
-				dwarf2_per_cu *per_cu, enum language language)
+				enum language language)
   : m_index_storage (storage),
-    m_per_cu (per_cu),
     m_language (language),
     m_die_range_map (storage->get_parent_map ())
 {
@@ -102,6 +101,17 @@ cooked_indexer::ensure_cu_exists (cutu_reader *reader,
      Doing this check here avoids self-imports as well.  */
   if (for_scanning)
     {
+      /* Record the inclusion precisely here.  Inclusions are done to
+	 ensure that imported units cause the correct expansions when
+	 searched; and in particular that all including units are
+	 expanded when finding an inline function.  So, this must be
+	 done for all units that are importing "for scanning".
+	 However, doing this any later than this spot would be too
+	 late -- because we want to do this for all importing units,
+	 not just the one that happens to win the race to do the
+	 scanning.  */
+      m_index_storage->record_inclusion (reader->cu ()->per_cu, per_cu);
+
       bool nope = false;
       if (!per_cu->scanned.compare_exchange_strong (nope, true))
 	return nullptr;
@@ -147,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 				 const cooked_index_entry **parent_entry,
 				 parent_map::addr_type *maybe_defer,
 				 bool *is_enum_class,
+				 bool *is_inlined,
 				 bool for_specification)
 {
   bool is_declaration = false;
@@ -282,6 +293,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 				  scanning_per_cu, abbrev->tag);
 	    }
 	  break;
+
+	case DW_AT_inline:
+	  if (abbrev->tag == DW_TAG_subprogram)
+	    {
+	      std::optional<ULONGEST> val = attr.unsigned_constant ();
+	      if (val.has_value () && (*val == DW_INL_inlined
+				       || *val == DW_INL_declared_inlined))
+		*is_inlined = true;
+	    }
+	  break;
 	}
     }
 
@@ -360,7 +381,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
 	scan_attributes (scanning_per_cu, new_reader, new_info_ptr,
 			 new_info_ptr, new_abbrev, name, linkage_name,
 			 flags, nullptr, parent_entry, maybe_defer,
-			 is_enum_class, true);
+			 is_enum_class, is_inlined, true);
     }
 
   if (!for_specification)
@@ -428,8 +449,7 @@ cooked_indexer::index_imported_unit (cutu_reader *reader,
   if (!target.has_value ())
     return info_ptr;
 
-  cutu_reader *new_reader
-    = ensure_cu_exists (reader, *target, true);
+  cutu_reader *new_reader = ensure_cu_exists (reader, *target, true);
   if (new_reader != nullptr)
     index_dies (new_reader, new_reader->info_ptr (), nullptr, false);
 
@@ -527,6 +547,7 @@ cooked_indexer::index_dies (cutu_reader *reader,
       sect_offset sibling {};
       const cooked_index_entry *this_parent_entry = parent_entry;
       bool is_enum_class = false;
+      bool is_inlined = false;
 
       /* The scope of a DW_TAG_entry_point cooked_index_entry is the one of
 	 its surrounding subroutine.  */
@@ -535,7 +556,8 @@ cooked_indexer::index_dies (cutu_reader *reader,
       info_ptr
 	= scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr,
 			   abbrev, &name, &linkage_name, &flags, &sibling,
-			   &this_parent_entry, &defer, &is_enum_class, false);
+			   &this_parent_entry, &defer, &is_enum_class,
+			   &is_inlined, false);
       /* A DW_TAG_entry_point inherits its static/extern property from
 	 the enclosing subroutine.  */
       if (abbrev->tag == DW_TAG_entry_point)
@@ -584,18 +606,25 @@ cooked_indexer::index_dies (cutu_reader *reader,
 	    }
 	}
 
+      if (is_inlined)
+	flags |= IS_INLINED;
+
       cooked_index_entry *this_entry = nullptr;
+      /* Always use the reader's CU for the entry CU.  */
+      dwarf2_per_cu *cu_for_entry = reader->cu ()->per_cu;
       if (name != nullptr)
 	{
 	  if (defer != 0)
 	    this_entry
 	      = m_index_storage->add (this_die, abbrev->tag,
-				      flags | IS_PARENT_DEFERRED, name,
-				      defer, m_per_cu);
+				      flags | IS_PARENT_DEFERRED,
+				      m_language, name,
+				      defer, cu_for_entry);
 	  else
 	    this_entry
-	      = m_index_storage->add (this_die, abbrev->tag, flags, name,
-				      this_parent_entry, m_per_cu);
+	      = m_index_storage->add (this_die, abbrev->tag, flags,
+				      m_language, name,
+				      this_parent_entry, cu_for_entry);
 	}
 
       if (linkage_name != nullptr)
@@ -608,11 +637,10 @@ cooked_indexer::index_dies (cutu_reader *reader,
 	     have linkage name present but name is absent.  */
 	  if (name != nullptr
 	      || (abbrev->tag != DW_TAG_subprogram
-		  && abbrev->tag != DW_TAG_inlined_subroutine
 		  && abbrev->tag != DW_TAG_entry_point))
 	    flags = flags | IS_LINKAGE;
-	  m_index_storage->add (this_die, abbrev->tag, flags,
-				linkage_name, nullptr, m_per_cu);
+	  m_index_storage->add (this_die, abbrev->tag, flags, m_language,
+				linkage_name, nullptr, cu_for_entry);
 	}
 
       if (abbrev->has_children)
diff --git a/gdb/dwarf2/cooked-indexer.h b/gdb/dwarf2/cooked-indexer.h
index 5cb5bae33f1..563742c90f6 100644
--- a/gdb/dwarf2/cooked-indexer.h
+++ b/gdb/dwarf2/cooked-indexer.h
@@ -37,7 +37,7 @@ struct section_and_offset;
 class cooked_indexer
 {
 public:
-  cooked_indexer (cooked_index_worker_result *storage, dwarf2_per_cu *per_cu,
+  cooked_indexer (cooked_index_worker_result *storage,
 		  enum language language);
 
   DISABLE_COPY_AND_ASSIGN (cooked_indexer);
@@ -85,6 +85,7 @@ class cooked_indexer
 				   const cooked_index_entry **parent_entry,
 				   parent_map::addr_type *maybe_defer,
 				   bool *is_enum_class,
+				   bool *is_inlined,
 				   bool for_specification);
 
   /* Handle DW_TAG_imported_unit, by scanning the DIE to find
@@ -104,11 +105,6 @@ class cooked_indexer
 
   /* The storage object, where the results are kept.  */
   cooked_index_worker_result *m_index_storage;
-  /* The CU that we are reading on behalf of.  This object might be
-     asked to index one CU but to treat the results as if they come
-     from some including CU; in this case the including CU would be
-     recorded here.  */
-  dwarf2_per_cu *m_per_cu;
   /* The language that we're assuming when reading.  */
   enum language m_language;
 
diff --git a/gdb/dwarf2/index-write.c b/gdb/dwarf2/index-write.c
index c4e75898f4c..6d79dbc9f49 100644
--- a/gdb/dwarf2/index-write.c
+++ b/gdb/dwarf2/index-write.c
@@ -683,6 +683,10 @@ class debug_names
     /* The next available abbrev number.  */
     int next_abbrev = 1;
 
+    /* Storage for the CU list for inline entries.  This is outside
+       the loops to try to avoid repeated allocations.  */
+    std::vector<dwarf2_per_cu *> per_cus;
+
     for (auto &[name, these_entries] : m_name_to_value_set)
       {
 	/* Sort the items within each bucket.  This ensures that the
@@ -711,108 +715,136 @@ class debug_names
 
 	for (const cooked_index_entry *entry : these_entries)
 	  {
-	    unit_kind kind = (entry->per_cu->is_debug_types ()
-			      ? unit_kind::tu
-			      : unit_kind::cu);
-	    /* Some Ada parentage is synthesized by the reader and so
-	       must be ignored here.  */
-	    const cooked_index_entry *parent = entry->get_parent ();
-	    if (parent != nullptr && (parent->flags & IS_SYNTHESIZED) != 0)
-	      parent = nullptr;
-
-	    int &abbrev = m_indexkey_to_abbrev[index_key (entry->tag,
-							  kind,
-							  entry->flags,
-							  entry->lang,
-							  parent != nullptr)];
-	    if (abbrev == 0)
+	    /* Accumulate all the relevant CUs.  This is needed for
+	       the DW_TAG_inlined_subroutine special case.  */
+	    per_cus.clear ();
+	    entry->visit_defining_cus ([&] (dwarf2_per_cu *one_cu)
+	      {
+		per_cus.push_back (one_cu);
+		return true;
+	      });
+	    /* Make sure the output is stable.  */
+	    std::sort (per_cus.begin (), per_cus.end (),
+		       [] (const dwarf2_per_cu *lhs, const dwarf2_per_cu *rhs)
+		       {
+			 return lhs->index < rhs->index;
+		       });
+
+	    for (dwarf2_per_cu *one_cu : per_cus)
 	      {
-		abbrev = next_abbrev++;
+		unit_kind kind = (entry->per_cu->is_debug_types ()
+				  ? unit_kind::tu
+				  : unit_kind::cu);
+		/* Some Ada parentage is synthesized by the reader and so
+		   must be ignored here.  */
+		const cooked_index_entry *parent = entry->get_parent ();
+		if (parent != nullptr && (parent->flags & IS_SYNTHESIZED) != 0)
+		  parent = nullptr;
+
+		int &abbrev = m_indexkey_to_abbrev[index_key (entry->tag,
+							      kind,
+							      entry->flags,
+							      entry->lang,
+							      parent != nullptr)];
+		if (abbrev == 0)
+		  {
+		    abbrev = next_abbrev++;
+
+		    /* Abbrev number and tag.  */
+		    m_abbrev_table.append_unsigned_leb128 (abbrev);
+		    m_abbrev_table.append_unsigned_leb128 (entry->tag);
+
+		    /* Unit index.  */
+		    m_abbrev_table.append_unsigned_leb128
+		      (kind == unit_kind::cu
+		       ? DW_IDX_compile_unit
+		       : DW_IDX_type_unit);
+		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+
+		    /* DIE offset.  */
+		    m_abbrev_table.append_unsigned_leb128 (DW_IDX_die_offset);
+		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_ref_addr);
+
+		    /* Language.  */
+		    m_abbrev_table.append_unsigned_leb128
+		      (DW_IDX_GNU_language);
+		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+
+		    /* Internal linkage flag.  */
+		    if (!tag_is_type (entry->tag)
+			&& (entry->flags & IS_STATIC) != 0)
+		      {
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_IDX_GNU_internal);
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_FORM_flag_present);
+		      }
+
+		    /* Main subprogram flag.  */
+		    if ((entry->flags & IS_MAIN) != 0)
+		      {
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_IDX_GNU_main);
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_FORM_flag_present);
+		      }
+
+		    /* Linkage name flag.  */
+		    if ((entry->flags & IS_LINKAGE) != 0)
+		      {
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_IDX_GNU_linkage_name);
+			m_abbrev_table.append_unsigned_leb128
+			  (DW_FORM_flag_present);
+		      }
+
+		    /* Parent offset.  */
+		    if (parent != nullptr)
+		      {
+			m_abbrev_table.append_unsigned_leb128 (DW_IDX_parent);
+			m_abbrev_table.append_unsigned_leb128 (DW_FORM_data4);
+		      }
+
+		    /* Terminate attributes list.  */
+		    m_abbrev_table.append_unsigned_leb128 (0);
+		    m_abbrev_table.append_unsigned_leb128 (0);
+		  }
 
-		/* Abbrev number and tag.  */
-		m_abbrev_table.append_unsigned_leb128 (abbrev);
-		m_abbrev_table.append_unsigned_leb128 (entry->tag);
+		/* Record the offset in the pool at which this entry will
+		   reside.  */
+		const auto offset_inserted
+		  = (m_entry_pool_offsets.emplace (entry, m_entry_pool.size ())
+		     .second);
+		gdb_assert (offset_inserted);
+
+		/* Write the entry to the pool, starting with the
+		   abbrev number.  */
+		m_entry_pool.append_unsigned_leb128 (abbrev);
 
 		/* Unit index.  */
-		m_abbrev_table.append_unsigned_leb128
-		  (kind == unit_kind::cu
-		   ? DW_IDX_compile_unit
-		   : DW_IDX_type_unit);
-		m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
+		const auto it = m_cu_index_htab.find (one_cu);
+		gdb_assert (it != m_cu_index_htab.cend ());
+		m_entry_pool.append_unsigned_leb128 (it->second);
 
 		/* DIE offset.  */
-		m_abbrev_table.append_unsigned_leb128 (DW_IDX_die_offset);
-		m_abbrev_table.append_unsigned_leb128 (DW_FORM_ref_addr);
+		m_entry_pool.append_uint (dwarf5_offset_size (),
+					  m_dwarf5_byte_order,
+					  to_underlying (entry->die_offset));
 
 		/* Language.  */
-		m_abbrev_table.append_unsigned_leb128 (DW_IDX_GNU_language);
-		m_abbrev_table.append_unsigned_leb128 (DW_FORM_udata);
-
-		/* Internal linkage flag.  */
-		if (!tag_is_type (entry->tag)
-		    && (entry->flags & IS_STATIC) != 0)
-		  {
-		    m_abbrev_table.append_unsigned_leb128 (DW_IDX_GNU_internal);
-		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_flag_present);
-		  }
-
-		/* Main subprogram flag.  */
-		if ((entry->flags & IS_MAIN) != 0)
-		  {
-		    m_abbrev_table.append_unsigned_leb128 (DW_IDX_GNU_main);
-		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_flag_present);
-		  }
-
-		/* Linkage name flag.  */
-		if ((entry->flags & IS_LINKAGE) != 0)
-		  {
-		    m_abbrev_table.append_unsigned_leb128 (DW_IDX_GNU_linkage_name);
-		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_flag_present);
-		  }
+		m_entry_pool.append_unsigned_leb128
+		  (entry->per_cu->dw_lang ());
 
 		/* Parent offset.  */
 		if (parent != nullptr)
 		  {
-		    m_abbrev_table.append_unsigned_leb128 (DW_IDX_parent);
-		    m_abbrev_table.append_unsigned_leb128 (DW_FORM_data4);
-		  }
-
-		/* Terminate attributes list.  */
-		m_abbrev_table.append_unsigned_leb128 (0);
-		m_abbrev_table.append_unsigned_leb128 (0);
-	      }
-
-	    /* Record the offset in the pool at which this entry will
-	       reside.  */
-	    const auto offset_inserted
-	      = (m_entry_pool_offsets.emplace (entry, m_entry_pool.size ())
-		 .second);
-	    gdb_assert (offset_inserted);
-
-	    /* Write the entry to the pool, starting with the abbrev number.  */
-	    m_entry_pool.append_unsigned_leb128 (abbrev);
-
-	    /* Unit index.  */
-	    const auto it = m_cu_index_htab.find (entry->per_cu);
-	    gdb_assert (it != m_cu_index_htab.cend ());
-	    m_entry_pool.append_unsigned_leb128 (it->second);
-
-	    /* DIE offset.  */
-	    m_entry_pool.append_uint (dwarf5_offset_size (),
-				      m_dwarf5_byte_order,
-				      to_underlying (entry->die_offset));
+		    m_offsets_to_patch.emplace_back
+		      (m_entry_pool.size (), parent);
 
-	    /* Language.  */
-	    m_entry_pool.append_unsigned_leb128 (entry->per_cu->dw_lang ());
-
-	    /* Parent offset.  */
-	    if (parent != nullptr)
-	      {
-		m_offsets_to_patch.emplace_back (m_entry_pool.size (), parent);
-
-		/* Write a dummy number, this gets patched later.  */
-		m_entry_pool.append_uint (4, m_dwarf5_byte_order,
-					  0xfafafafa);
+		    /* Write a dummy number, this gets patched later.  */
+		    m_entry_pool.append_uint (4, m_dwarf5_byte_order,
+					      0xfafafafa);
+		  }
 	      }
 	  }
 
@@ -1239,9 +1271,6 @@ write_cooked_index (cooked_index *table,
 
   for (const cooked_index_entry *entry : table->all_entries ())
     {
-      const auto it = cu_index_htab.find (entry->per_cu);
-      gdb_assert (it != cu_index_htab.cend ());
-
       const char *name = entry->full_name (symtab->obstack ());
 
       if (entry->lang == language_ada)
@@ -1281,8 +1310,14 @@ write_cooked_index (cooked_index *table,
       else
 	kind = GDB_INDEX_SYMBOL_KIND_OTHER;
 
-      symtab->add_index_entry (name, (entry->flags & IS_STATIC) != 0,
-			       kind, it->second);
+      entry->visit_defining_cus ([&] (dwarf2_per_cu *per_cu)
+        {
+	  const auto it = cu_index_htab.find (per_cu);
+	  gdb_assert (it != cu_index_htab.cend ());
+	  symtab->add_index_entry (name, (entry->flags & IS_STATIC) != 0,
+				   kind, it->second);
+	  return true;
+	});
     }
 }
 
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 7f6198aa4d4..8761a223734 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -1642,6 +1642,11 @@ static struct compunit_symtab *
 dw2_instantiate_symtab (dwarf2_per_cu *per_cu, dwarf2_per_objfile *per_objfile,
 			bool skip_partial)
 {
+  /* Expand the corresponding canonical outermost CU.  This will
+     expand the desired CU as a side effect when following the
+     DW_TAG_imported_unit.  */
+  per_cu = per_cu->canonical_outermost_cu ();
+
   if (!per_objfile->symtab_set_p (per_cu))
     {
       free_cached_comp_units freer (per_objfile);
@@ -3457,7 +3462,7 @@ cooked_index_worker_debug_info::process_unit
       if (this_cu->scanned.compare_exchange_strong (nope, true))
 	{
 	  gdb_assert (storage != nullptr);
-	  cooked_indexer indexer (storage, this_cu, reader->cu ()->lang ());
+	  cooked_indexer indexer (storage, reader->cu ()->lang ());
 	  indexer.make_index (reader);
 	}
     }
@@ -3477,7 +3482,7 @@ cooked_index_worker_debug_info::process_type_unit
     return;
 
   gdb_assert (storage != nullptr);
-  cooked_indexer indexer (storage, per_cu, cu->lang ());
+  cooked_indexer indexer (storage, cu->lang ());
   indexer.make_index (reader);
 }
 
@@ -14235,15 +14240,15 @@ cooked_index_functions::search
 	     we see a symbol with an unknown language we find the CU's
 	     language.  Only the .gdb_index reader creates such
 	     symbols.  */
-	  enum language entry_lang = entry->lang;
-	  if (lang_matcher != nullptr || entry_lang == language_unknown)
+	  if (lang_matcher != nullptr || entry->lang == language_unknown)
 	    {
 	      entry->per_cu->ensure_lang (per_objfile);
 	      if (lang_matcher != nullptr
 		  && !entry->per_cu->maybe_multi_language ()
 		  && !lang_matcher (entry->per_cu->lang ()))
 		continue;
-	      entry_lang = entry->per_cu->lang ();
+	      else if (entry->lang == language_unknown)
+		entry->force_set_language ();
 	    }
 
 	  /* We've found the base name of the symbol; now walk its
@@ -14252,7 +14257,7 @@ cooked_index_functions::search
 	  bool found = true;
 
 	  const cooked_index_entry *parent = entry->get_parent ();
-	  const language_defn *lang_def = language_def (entry_lang);
+	  const language_defn *lang_def = language_def (entry->lang);
 	  for (int i = name_vec.size () - 1; i > 0; --i)
 	    {
 	      /* If we ran out of entries, or if this segment doesn't
@@ -14308,8 +14313,12 @@ cooked_index_functions::search
 	  else if (!symbol_matcher (full_name))
 	    continue;
 
-	  if (!dw2_search_one (entry->per_cu, per_objfile, cus_to_skip,
-			       file_matcher, listener, nullptr))
+	  bool check = entry->visit_defining_cus ([&] (dwarf2_per_cu *per_cu)
+	    {
+	      return dw2_search_one (per_cu, per_objfile, cus_to_skip,
+				     file_matcher, listener, nullptr);
+	    });
+	  if (!check)
 	    return false;
 	}
     }
@@ -18066,6 +18075,30 @@ dwarf2_per_cu::ensure_lang (dwarf2_per_objfile *per_objfile)
 		      true, std::nullopt, nullptr);
 }
 
+/* See read.h.  */
+
+bool
+dwarf2_per_cu::recursively_visit_cus (per_cu_callback callback)
+{
+  if (including_cus.empty ())
+    return callback (this);
+  for (dwarf2_per_cu *iter : including_cus)
+    if (!iter->recursively_visit_cus (callback))
+      return false;
+  return true;
+}
+
+/* See read.h.  */
+
+dwarf2_per_cu *
+dwarf2_per_cu::canonical_outermost_cu ()
+{
+  dwarf2_per_cu *iter = this;
+  while (!iter->including_cus.empty ())
+    iter = *iter->including_cus.begin ();
+  return iter;
+}
+
 /* Return the unit from ALL_UNITS that potentially contains TARGET.
 
    Since the unit lengths may not be known yet, this function doesn't check that
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 172d718e6b7..bedddf8c77f 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -252,6 +252,15 @@ struct dwarf2_per_cu
   /* Backlink to the owner of this.  */
   dwarf2_per_bfd *m_per_bfd;
 
+  /* Compare two CUs.  */
+  struct compare
+  {
+    bool operator() (const dwarf2_per_cu *lhs, const dwarf2_per_cu *rhs) const
+    {
+      return lhs->index < rhs->index;
+    }
+  };
+
 public:
   /* The file and directory for this CU.  This is cached so that we
      don't need to re-examine the DWO in some situations.  This may be
@@ -281,6 +290,13 @@ struct dwarf2_per_cu
      http://sourceware.org/bugzilla/show_bug.cgi?id=15021.  */
   std::vector<dwarf2_per_cu *> imported_symtabs;
 
+  /* Pointer to all the CUs that include this CU via
+     DW_TAG_imported_unit.  This is used for the special handling of
+     inline functions appearing in partial units.  An ordered set is
+     used to ensure that the canonical CU is the same across
+     invocations of gdb.  */
+  std::set<dwarf2_per_cu *, compare> including_cus;
+
   bool is_debug_types () const
   { return m_is_debug_types; }
 
@@ -432,6 +448,27 @@ struct dwarf2_per_cu
 
   /* Free any cached file names.  */
   void free_cached_file_names ();
+
+  /* Indicate that INCLUDER includes this CU via
+     DW_TAG_imported_unit.  */
+  void add_includer (dwarf2_per_cu *includer)
+  {
+    including_cus.emplace (includer);
+  }
+
+  /* Type of callback used when visiting defining CUs.  */
+  using per_cu_callback = gdb::function_view<bool (dwarf2_per_cu *)>;
+
+  /* Calls CALLBACK for each CU that is the outermost includer of
+     ONE_CU.  If ONE_CU has no includers, it calls CALLBACK on ONE_CU.
+     If any call to CALLBACK returns false, this immediately returns
+     false (skipping the remaining calls); otherwise returns true.  */
+  bool recursively_visit_cus (per_cu_callback callback);
+
+  /* Return a canonical outermost CU corresponding to this CU.  If
+     this CU is standalone (not included by other CUs), then this
+     method will simply return 'this'.  */
+  dwarf2_per_cu *canonical_outermost_cu ();
 };
 
 /* Entry in the signatured_types hash table.  */

-- 
2.49.0


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

* [PATCH v2 8/8] Update .debug_names documentation
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (6 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 7/8] Handle inline functions with dwz Tom Tromey
@ 2026-01-26 21:33 ` Tom Tromey
  2026-02-10  4:26   ` Simon Marchi
  2026-01-26 21:42 ` [PATCH v2 0/8] Correctly handle inline functions with dwz Simon Marchi
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 21:33 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This updates the .debug_names documentation to explain some DWARF
issues that we've handled in gdb.

This list still isn't exhaustive.  I think there are some situations
where gdb may examine a declaration (which DWARF says not to do), but
I didn't document this as I don't recall the details.
---
 gdb/doc/gdb.texinfo | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 80f49e21b7e..1b3813286e6 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -23297,6 +23297,43 @@ source name.
 
 @end table
 
+@value{GDBN} also has some special handling for cases not considered
+in the DWARF specification.
+
+@itemize @bullet
+@item
+The @code{DW_IDX_parent} for a C-style enumerator does not point at
+the entry for @code{enum} itself, but rather the parent of the type.
+The reason for this is that C-style enumerators are injected into the
+containing scope, and so their name is not qualified by the
+@code{enum}; and furthermore there is no way to distinguish between
+C-style enumerators and @code{enum class}-style enumerators in
+@samp{.debug_names}.
+
+@item
+Similarly, @code{DW_IDX_parent} is omitted for any linkage name
+entries that are written.
+
+@item
+Definitions in partial units are handled differently.  These most
+typically are seen in the output of @code{dwz}.
+
+In general, a DWARF partial unit cannot be read in isolation, but only
+by reading it in the context of some other unit that references it via
+@code{DW_TAG_imported_unit}.
+
+Therefore, an ordinary definition in a partial unit is attributed to
+one of the outermost containing units.  This is done by referencing
+this containing CU in the @code{DW_IDX_compile_unit} attribute.
+
+A further special case applies to @code{DW_TAG_inlined_subroutine}
+entries.  An inlined subroutine appearing in a partial unit may be
+inlined in all of the outermost compilation units that directly or
+indirectly include the partial unit.  Therefore, in this case,
+@value{GDBN} will emit a separate index entry for the entry, once for
+each such containing unit.
+@end itemize
+
 @node Symbol Errors
 @section Errors Reading Symbol Files
 

-- 
2.49.0


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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (7 preceding siblings ...)
  2026-01-26 21:33 ` [PATCH v2 8/8] Update .debug_names documentation Tom Tromey
@ 2026-01-26 21:42 ` Simon Marchi
  2026-01-26 23:31   ` Tom Tromey
  2026-01-26 22:05 ` Tom de Vries
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-01-26 21:42 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches

On 1/26/26 4:33 PM, Tom Tromey wrote:
> The new indexer does not correctly handle inline functions when 'dwz'
> is used to compress the DWARF.  This series fixes the bug, cleaning up
> a number of other things on the way.
> 
> I've separately regression tested each patch in this series on x86-64
> Fedora 41.  I've also regression tested the series as a whole with the
> dwz, gdb-index, and debug-names boards.
> 
> Even with that I messed up somehow, so v1 didn't actually fix the bug
> in question.  I must have modified the patches after testing..?
> 
> Anyway in v2 I've moved the line recording the CU inclusion and added
> a comment explaining the placement.  I re-ran the aformentioned tests
> and didn't touch anything.
> 
> Signed-off-by: Tom Tromey <tom@tromey.com>

I will test this series on my CI that runs the testsuite with the
various DWARF boards.

In the mean time, there are some things that pre-commit suggests to fix:

codespell................................................................Failed
- hook id: codespell
- exit code: 65

gdb/dwarf2/cooked-index-worker.c:114: includee ==> include

check-whitespace.........................................................Failed
- hook id: check-whitespace
- exit code: 2

gdb/dwarf2/index-write.c:1314: indent with spaces.
+        {

Simon

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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (8 preceding siblings ...)
  2026-01-26 21:42 ` [PATCH v2 0/8] Correctly handle inline functions with dwz Simon Marchi
@ 2026-01-26 22:05 ` Tom de Vries
  2026-01-30 22:06 ` Simon Marchi
  2026-02-06 19:14 ` Tom Tromey
  11 siblings, 0 replies; 24+ messages in thread
From: Tom de Vries @ 2026-01-26 22:05 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches

On 1/26/26 10:33 PM, Tom Tromey wrote:
> Anyway in v2 I've moved the line recording the CU inclusion and added
> a comment explaining the placement.

Hi,

just to confirm, this fixes the problem I reported for v1.

Thanks,
- Tom

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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-26 21:42 ` [PATCH v2 0/8] Correctly handle inline functions with dwz Simon Marchi
@ 2026-01-26 23:31   ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-26 23:31 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

Simon> gdb/dwarf2/cooked-index-worker.c:114: includee ==> include

I think this isn't really a misspelling -- I intended to write that --
but I changed it to placate the checker.

Simon> gdb/dwarf2/index-write.c:1314: indent with spaces.

Also fixed locally.

I'm not going to re-send the series just for these.

Tom

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

* Re: [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs
  2026-01-26 21:33 ` [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs Tom Tromey
@ 2026-01-30 22:04   ` Simon Marchi
  2026-01-31 15:04     ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-01-30 22:04 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-01-26 16:33, Tom Tromey wrote:
> @@ -257,10 +262,14 @@ objfile::map_symtabs_matching_filename
>  
>    auto listener = [&] (compunit_symtab *symtab)
>    {
> +    /* Skip included compunits.  */
> +    if (symtab->user != nullptr)
> +      return true;

It would be nice for the comment to say why we skip included compunits:
because iterate_over_one_compunit_symtab searches recursively.

Simon

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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (9 preceding siblings ...)
  2026-01-26 22:05 ` Tom de Vries
@ 2026-01-30 22:06 ` Simon Marchi
  2026-03-05 18:53   ` Tom Tromey
  2026-02-06 19:14 ` Tom Tromey
  11 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-01-30 22:06 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-01-26 16:33, Tom Tromey wrote:
> The new indexer does not correctly handle inline functions when 'dwz'
> is used to compress the DWARF.  This series fixes the bug, cleaning up
> a number of other things on the way.
> 
> I've separately regression tested each patch in this series on x86-64
> Fedora 41.  I've also regression tested the series as a whole with the
> dwz, gdb-index, and debug-names boards.
> 
> Even with that I messed up somehow, so v1 didn't actually fix the bug
> in question.  I must have modified the patches after testing..?
> 
> Anyway in v2 I've moved the line recording the CU inclusion and added
> a comment explaining the placement.  I re-ran the aformentioned tests
> and didn't touch anything.

I read up to patch 6 for now, they all look good to me and good on their
own.  If you want to merge those right away, you can use my Approved-By
for them.

Simon

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

* Re: [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs
  2026-01-30 22:04   ` Simon Marchi
@ 2026-01-31 15:04     ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-01-31 15:04 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

Simon> On 2026-01-26 16:33, Tom Tromey wrote:
>> @@ -257,10 +262,14 @@ objfile::map_symtabs_matching_filename
>> 
>> auto listener = [&] (compunit_symtab *symtab)
>> {
>> +    /* Skip included compunits.  */
>> +    if (symtab->user != nullptr)
>> +      return true;

Simon> It would be nice for the comment to say why we skip included compunits:
Simon> because iterate_over_one_compunit_symtab searches recursively.

I made this change.

Tom

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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
                   ` (10 preceding siblings ...)
  2026-01-30 22:06 ` Simon Marchi
@ 2026-02-06 19:14 ` Tom Tromey
  11 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-02-06 19:14 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

>>>>> "Tom" == Tom Tromey <tom@tromey.com> writes:

Tom> Anyway in v2 I've moved the line recording the CU inclusion and added
Tom> a comment explaining the placement.  I re-ran the aformentioned tests
Tom> and didn't touch anything.

I rebased this over some recent changes, but it only required a fairly
minor conflict resolution, so I'm not planning to re-send it just for
that.

Tom

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

* Re: [PATCH v2 7/8] Handle inline functions with dwz
  2026-01-26 21:33 ` [PATCH v2 7/8] Handle inline functions with dwz Tom Tromey
@ 2026-02-10  3:51   ` Simon Marchi
  0 siblings, 0 replies; 24+ messages in thread
From: Simon Marchi @ 2026-02-10  3:51 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-01-26 16:33, Tom Tromey wrote:
> Currently, gdb does not properly handle inline functions when dwz is
> used.  This can be seen by running gdb.cp/breakpoint-locs.exp with the
> cc-with-dwz target board.
> 
> The problem here is that inline functions need special handling in the
> dwz case.
> 
> First, recall that a DWARF partial unit cannot, in general, be read in
> isolation, as it may not have a language.  To handle this, gdb defers
> scanning partial units directly, and instead scans them in the context
> of some including CU.
> 
> Entries coming from the PU are attributed to this reading CU.  If
> multiple CUs import a PU, gdb has the reader threads race to see which
> one does the actual reading.
> 
> However, if an inline function is moved into a partial unit, then that
> means it has potentially been inlined somewhere in every including CU.
> 
> Thus, when linespec searches for this function, each such including CU
> should be expanded.  But because gdb only attributed the function's
> index entry to one CU, only that particular one is expanded.
> 
> This patch fixes this bug.  All inclusions of a PU are recorded.
> Entries coming from a PU are attributed to that PU.  For most entries
> coming from the PU, a single "canonical" outer CU is chosen to expand.
> However, when an inline function is found, all such CUs are expanded.
> 
> A change was also needed to the index writer to handle this case.
> There, entries coming from a PU should note the correct including CU.
> This must be done because, with .debug_names or .gdb_index, gdb does
> not have information about unit imports.  Handling inline functions
> here means writing out a separate entry for each outermost CU that
> includes the function's PU.
> 
> I did consider changing the cooked indexer to create an internal table
> more similar to what would be created by the .debug_names (e.g.)
> reader: that is, multiple entries for each inline function.  However,
> this seemed more expensive at read time, and a main goal of the cooked
> indexer is to be fast.

The description make sense to me.  I think the dwz-ing of the DWARF
should be seen as an internal implementation detail to factor common
things, but from the "outside", it's the real CUs/TUs that count.
Semantically, we must do "as-if" there was no factoring out.

Simon

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-01-26 21:33 ` [PATCH v2 8/8] Update .debug_names documentation Tom Tromey
@ 2026-02-10  4:26   ` Simon Marchi
  2026-02-10 16:14     ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-02-10  4:26 UTC (permalink / raw)
  To: Tom Tromey, gdb-patches



On 2026-01-26 16:33, Tom Tromey wrote:
> This updates the .debug_names documentation to explain some DWARF
> issues that we've handled in gdb.
> 
> This list still isn't exhaustive.  I think there are some situations
> where gdb may examine a declaration (which DWARF says not to do), but
> I didn't document this as I don't recall the details.
> ---
>  gdb/doc/gdb.texinfo | 37 +++++++++++++++++++++++++++++++++++++
>  1 file changed, 37 insertions(+)
> 
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 80f49e21b7e..1b3813286e6 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -23297,6 +23297,43 @@ source name.
>  
>  @end table
>  
> +@value{GDBN} also has some special handling for cases not considered
> +in the DWARF specification.
> +
> +@itemize @bullet
> +@item
> +The @code{DW_IDX_parent} for a C-style enumerator does not point at
> +the entry for @code{enum} itself, but rather the parent of the type.
> +The reason for this is that C-style enumerators are injected into the
> +containing scope, and so their name is not qualified by the
> +@code{enum}; and furthermore there is no way to distinguish between
> +C-style enumerators and @code{enum class}-style enumerators in
> +@samp{.debug_names}.

Ok, so IIUC: there is a way to distinguish between C enums and C++
enum-class enums in the debug info itself, but not in .debug_names.  So
there is no way just from the index to tell if an enumerator name should
be visible from the enclosing scope of not.

It sounds like another fix I could bring up to the committee, if we can
find a good solution.

Note: I found this proposal that suggested adding enumerators to the
index, and it was rejected, "Size impact would be too great.".

https://dwarfstd.org/issues/230224.1.html

So, is putting enumerators in .debug_names an extension to the standard
in itself?

I don't really understand the logic of not putting the non-enum-class
enumerators in the index.  If you have:

enum Foo {
  Foo_Bar = 12,
};

then you want "print Foo_Bar" to work, which requires "Foo_bar" to be
find-able.

> +
> +@item
> +Similarly, @code{DW_IDX_parent} is omitted for any linkage name
> +entries that are written.

Is this just an optimization? Because for a linkage name, you don't need
to know the parent, since there is not need to build the fully qualified
name?

I don't know if this is actually an extension or not.  Is it mandantory
to include a DW_IDX_parent (if there is an indexed parent), according to
the spec?  As usual, DWARF is not clear about that.

I suppose that this works in conjunction with DW_IDX_GNU_linkage_name?
It is currently being proposed for standardization here:

https://dwarfstd.org/issues/251122.1.html

If accepted, I could propose a change that says clearly "for entries
with DW_IDX_linkage_name, a DW_IDX_parent is not required".

> +
> +@item
> +Definitions in partial units are handled differently.  These most
> +typically are seen in the output of @code{dwz}.
> +
> +In general, a DWARF partial unit cannot be read in isolation, but only
> +by reading it in the context of some other unit that references it via
> +@code{DW_TAG_imported_unit}.
> +
> +Therefore, an ordinary definition in a partial unit is attributed to
> +one of the outermost containing units.  This is done by referencing
> +this containing CU in the @code{DW_IDX_compile_unit} attribute.

It's not explicitly said in the spec, but this is how I understand it is
expected to work.  There is no way to for an index entry to reference a
PU anyway.

> +A further special case applies to @code{DW_TAG_inlined_subroutine}
> +entries.  An inlined subroutine appearing in a partial unit may be
> +inlined in all of the outermost compilation units that directly or
> +indirectly include the partial unit.  Therefore, in this case,
> +@value{GDBN} will emit a separate index entry for the entry, once for
> +each such containing unit.
> +@end itemize

This question probably relates more to the previous patch, but I don't
quite understand why this applies to inlined functions but not to other
entities found in the PU.  You'd think that if you have a static
(file-local) variable defined in a PU, it means that all including CUs
have this variable defined.  So wouldn't you want an entry at that name
for all these CUs?

Simon

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-02-10  4:26   ` Simon Marchi
@ 2026-02-10 16:14     ` Tom Tromey
  2026-02-10 17:03       ` Simon Marchi
  0 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-02-10 16:14 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

>> +The @code{DW_IDX_parent} for a C-style enumerator does not point at
>> +the entry for @code{enum} itself, but rather the parent of the type.
>> +The reason for this is that C-style enumerators are injected into the
>> +containing scope, and so their name is not qualified by the
>> +@code{enum}; and furthermore there is no way to distinguish between
>> +C-style enumerators and @code{enum class}-style enumerators in
>> +@samp{.debug_names}.

Simon> Ok, so IIUC: there is a way to distinguish between C enums and C++
Simon> enum-class enums in the debug info itself, but not in .debug_names.  So
Simon> there is no way just from the index to tell if an enumerator name should
Simon> be visible from the enclosing scope of not.

Simon> It sounds like another fix I could bring up to the committee, if we can
Simon> find a good solution.

The gdb fix of changing the parentage seems reasonable.  More on the
rationale below.

Simon> So, is putting enumerators in .debug_names an extension to the standard
Simon> in itself?

Oh maybe so.

Simon> I don't really understand the logic of not putting the non-enum-class
Simon> enumerators in the index.

Indeed but there are many mysteries in DWARF.

Perhaps omitting enum class enumerators would be ok.  It might require
some changes to gdb.  I don't plan to work on this, I barely think the
indexes are worthwhile any more.

>> +Similarly, @code{DW_IDX_parent} is omitted for any linkage name
>> +entries that are written.

Simon> Is this just an optimization? Because for a linkage name, you don't need
Simon> to know the parent, since there is not need to build the fully qualified
Simon> name?

gdb could of course ignore the parentage of a linkage name entry.

But my view is that the name table is there not to tell gdb the DIE
parentage in some abstract way, but instead to indicate the names of
entities.  That's because I see the index as an enhancement to lookup
and not as a summary version of the .debug_info.

This view informs both the enum case and the linkage name case.

That is, my thought is that the DW_IDX_parent stuff is there to explain
to the debugger how to do a name lookup -- it describes the names, not
the DWARF.

So, a linkage name doesn't have a parent in this sense, because it is
already a fully-qualified name.  And likewise an ordinary enumerator is
hoisted into the outer scope.

>> +A further special case applies to @code{DW_TAG_inlined_subroutine}
>> +entries.  An inlined subroutine appearing in a partial unit may be
>> +inlined in all of the outermost compilation units that directly or
>> +indirectly include the partial unit.  Therefore, in this case,
>> +@value{GDBN} will emit a separate index entry for the entry, once for
>> +each such containing unit.
>> +@end itemize

Simon> This question probably relates more to the previous patch, but I don't
Simon> quite understand why this applies to inlined functions but not to other
Simon> entities found in the PU.  You'd think that if you have a static
Simon> (file-local) variable defined in a PU, it means that all including CUs
Simon> have this variable defined.  So wouldn't you want an entry at that name
Simon> for all these CUs?

For a variable to be shared in a PU, either it will just be a
declaration and each including CU will have a separate definition that
refers to it; or alternatively every such variable will have the same
address.  But in the latter case they are the same variable.

Inline functions are different.  An inline function will have a
DW_TAG_subprogram DIE somewhere (the CU level or appropriate namespace
scope or whatever) that is a marker indicating "this function is inlined
somewhere in this CU".

If you want to find out where it was inlined, the reader has to scan
that CU looking for DW_TAG_inlined_subroutine.

In the dwz case, the marker DIE might well be moved into a PU.  This
isn't like the variable case because there isn't necessarily an address,
just an DW_AT_inline on the DIE.

If this DIE is in a PU, then potentially the function has been inlined
somewhere in all including CUs.

BTW the DWARF approach to inline functions will also hamper incremental
CU expansion (if we ever get there).  At the very least when finding an
inline function gdb will have to scan all DIEs in the CUs just in case.

Tom

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-02-10 16:14     ` Tom Tromey
@ 2026-02-10 17:03       ` Simon Marchi
  2026-02-10 19:52         ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-02-10 17:03 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches



On 2026-02-10 11:14, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
> 
>>> +The @code{DW_IDX_parent} for a C-style enumerator does not point at
>>> +the entry for @code{enum} itself, but rather the parent of the type.
>>> +The reason for this is that C-style enumerators are injected into the
>>> +containing scope, and so their name is not qualified by the
>>> +@code{enum}; and furthermore there is no way to distinguish between
>>> +C-style enumerators and @code{enum class}-style enumerators in
>>> +@samp{.debug_names}.
> 
> Simon> Ok, so IIUC: there is a way to distinguish between C enums and C++
> Simon> enum-class enums in the debug info itself, but not in .debug_names.  So
> Simon> there is no way just from the index to tell if an enumerator name should
> Simon> be visible from the enclosing scope of not.
> 
> Simon> It sounds like another fix I could bring up to the committee, if we can
> Simon> find a good solution.
> 
> The gdb fix of changing the parentage seems reasonable.  More on the
> rationale below.
> 
> Simon> So, is putting enumerators in .debug_names an extension to the standard
> Simon> in itself?
> 
> Oh maybe so.
> 
> Simon> I don't really understand the logic of not putting the non-enum-class
> Simon> enumerators in the index.
> 
> Indeed but there are many mysteries in DWARF.
> 
> Perhaps omitting enum class enumerators would be ok.  It might require
> some changes to gdb.  I don't plan to work on this, I barely think the
> indexes are worthwhile any more.
> 
>>> +Similarly, @code{DW_IDX_parent} is omitted for any linkage name
>>> +entries that are written.
> 
> Simon> Is this just an optimization? Because for a linkage name, you don't need
> Simon> to know the parent, since there is not need to build the fully qualified
> Simon> name?
> 
> gdb could of course ignore the parentage of a linkage name entry.
> 
> But my view is that the name table is there not to tell gdb the DIE
> parentage in some abstract way, but instead to indicate the names of
> entities.  That's because I see the index as an enhancement to lookup
> and not as a summary version of the .debug_info.
> 
> This view informs both the enum case and the linkage name case.
> 
> That is, my thought is that the DW_IDX_parent stuff is there to explain
> to the debugger how to do a name lookup -- it describes the names, not
> the DWARF.
> 
> So, a linkage name doesn't have a parent in this sense, because it is
> already a fully-qualified name.  And likewise an ordinary enumerator is
> hoisted into the outer scope.

Ok, so with that in mind, do we even need the DW_IDX_linkage_name
attribute?  Ah, perhaps yes because the consumer needs to know if it
should try to demangle it or not?

>>> +A further special case applies to @code{DW_TAG_inlined_subroutine}
>>> +entries.  An inlined subroutine appearing in a partial unit may be
>>> +inlined in all of the outermost compilation units that directly or
>>> +indirectly include the partial unit.  Therefore, in this case,
>>> +@value{GDBN} will emit a separate index entry for the entry, once for
>>> +each such containing unit.
>>> +@end itemize
> 
> Simon> This question probably relates more to the previous patch, but I don't
> Simon> quite understand why this applies to inlined functions but not to other
> Simon> entities found in the PU.  You'd think that if you have a static
> Simon> (file-local) variable defined in a PU, it means that all including CUs
> Simon> have this variable defined.  So wouldn't you want an entry at that name
> Simon> for all these CUs?
> 
> For a variable to be shared in a PU, either it will just be a
> declaration and each including CU will have a separate definition that
> refers to it; or alternatively every such variable will have the same
> address.  But in the latter case they are the same variable.

You can't have a DW_TAG_variable in a PU that will represent a definition
in all the included CUs?  For instance, if you have

  static const char *my_var;

inside a C header file included by many C source files.  And then, in
the DWARF, we "factor out" that DW_TAG_variable into a PU that is
"DW_TAG_imported_unit"-imported by many CUs.  In that case, wouldn't we
have a my_var definition in all CUs?

> Inline functions are different.  An inline function will have a
> DW_TAG_subprogram DIE somewhere (the CU level or appropriate namespace
> scope or whatever) that is a marker indicating "this function is inlined
> somewhere in this CU".
> 
> If you want to find out where it was inlined, the reader has to scan
> that CU looking for DW_TAG_inlined_subroutine.
> 
> In the dwz case, the marker DIE might well be moved into a PU.  This
> isn't like the variable case because there isn't necessarily an address,
> just an DW_AT_inline on the DIE.
> 
> If this DIE is in a PU, then potentially the function has been inlined
> somewhere in all including CUs.

Ok, so a use case for this would be: the user types "break
my_inlined_func", and then you want to find all CUs where
my_inlined_func is defined (even through a PU), to find all the inline
sites, to put breakpoints on all of them?

> 
> BTW the DWARF approach to inline functions will also hamper incremental
> CU expansion (if we ever get there).  At the very least when finding an
> inline function gdb will have to scan all DIEs in the CUs just in case.

That's a bummer, I hope we can find a way around this.

Simon

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-02-10 17:03       ` Simon Marchi
@ 2026-02-10 19:52         ` Tom Tromey
  2026-02-10 20:25           ` Simon Marchi
  0 siblings, 1 reply; 24+ messages in thread
From: Tom Tromey @ 2026-02-10 19:52 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

>> For a variable to be shared in a PU, either it will just be a
>> declaration and each including CU will have a separate definition that
>> refers to it; or alternatively every such variable will have the same
>> address.  But in the latter case they are the same variable.

Simon> You can't have a DW_TAG_variable in a PU that will represent a definition
Simon> in all the included CUs?  For instance, if you have

Simon>   static const char *my_var;

Simon> inside a C header file included by many C source files.  And then, in
Simon> the DWARF, we "factor out" that DW_TAG_variable into a PU that is
Simon> "DW_TAG_imported_unit"-imported by many CUs.  In that case, wouldn't we
Simon> have a my_var definition in all CUs?

This approach would make a different variable in each CU.  They would
all share a name but have different addresses.  So while it's possible
to share some of this in a PU (like, the name, type, and source
location), each CU would still end up with a separate DIE describing the
address.

Simon> Ok, so a use case for this would be: the user types "break
Simon> my_inlined_func", and then you want to find all CUs where
Simon> my_inlined_func is defined (even through a PU), to find all the inline
Simon> sites, to put breakpoints on all of them?

Yes.  That's the failure addressed by the series.

>> BTW the DWARF approach to inline functions will also hamper incremental
>> CU expansion (if we ever get there).  At the very least when finding an
>> inline function gdb will have to scan all DIEs in the CUs just in case.

Simon> That's a bummer, I hope we can find a way around this.

Since the reader pre-loads all the DIEs I am thinking it probably won't
be super expensive to also iterate over them.  Or alternatively we could
have the DIE-loading process notice all the spots where an inlined
function is seen.

Tom

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-02-10 19:52         ` Tom Tromey
@ 2026-02-10 20:25           ` Simon Marchi
  2026-02-10 20:46             ` Tom Tromey
  0 siblings, 1 reply; 24+ messages in thread
From: Simon Marchi @ 2026-02-10 20:25 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches



On 2026-02-10 14:52, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
> 
>>> For a variable to be shared in a PU, either it will just be a
>>> declaration and each including CU will have a separate definition that
>>> refers to it; or alternatively every such variable will have the same
>>> address.  But in the latter case they are the same variable.
> 
> Simon> You can't have a DW_TAG_variable in a PU that will represent a definition
> Simon> in all the included CUs?  For instance, if you have
> 
> Simon>   static const char *my_var;
> 
> Simon> inside a C header file included by many C source files.  And then, in
> Simon> the DWARF, we "factor out" that DW_TAG_variable into a PU that is
> Simon> "DW_TAG_imported_unit"-imported by many CUs.  In that case, wouldn't we
> Simon> have a my_var definition in all CUs?
> 
> This approach would make a different variable in each CU.  They would
> all share a name but have different addresses.  So while it's possible
> to share some of this in a PU (like, the name, type, and source
> location), each CU would still end up with a separate DIE describing the
> address.

Ah, thanks.

> 
> Simon> Ok, so a use case for this would be: the user types "break
> Simon> my_inlined_func", and then you want to find all CUs where
> Simon> my_inlined_func is defined (even through a PU), to find all the inline
> Simon> sites, to put breakpoints on all of them?
> 
> Yes.  That's the failure addressed by the series.

Perfect, thanks.

>>> BTW the DWARF approach to inline functions will also hamper incremental
>>> CU expansion (if we ever get there).  At the very least when finding an
>>> inline function gdb will have to scan all DIEs in the CUs just in case.
> 
> Simon> That's a bummer, I hope we can find a way around this.
> 
> Since the reader pre-loads all the DIEs I am thinking it probably won't
> be super expensive to also iterate over them.  Or alternatively we could
> have the DIE-loading process notice all the spots where an inlined
> function is seen.

I was actually wondering if we could avoid pre-loading all the DIEs from
the start.  But if we need to to a full scan for something like this...

IWBN to measure how long it takes to just read the DIEs, and then to
iterate over all DIEs doing nothing.  Then we'll know if we need to care
about that part or not.

Simon

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

* Re: [PATCH v2 8/8] Update .debug_names documentation
  2026-02-10 20:25           ` Simon Marchi
@ 2026-02-10 20:46             ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-02-10 20:46 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

>> Since the reader pre-loads all the DIEs I am thinking it probably won't
>> be super expensive to also iterate over them.  Or alternatively we could
>> have the DIE-loading process notice all the spots where an inlined
>> function is seen.

Simon> I was actually wondering if we could avoid pre-loading all the
Simon> DIEs from the start.

This is the approach the cooked indexer takes, so I looked at it for the
full reader.  However the full reader has a few things that make it
harder.

I was mostly looking at this in the context of lazy loading... and there
are even more things that make fully lazy loading hard, like the
"abstract_to_concrete" map and the way that rust_union_quirks currently
works.

Anyway, probably the main issue with lazy DIE loading is that there's
code in the full reader that uses die_info::parent.  This by itself
isn't so bad but it wouldn't work if it's combined with random-access
DIE lookups.

Some of these uses are due to the weird/bad way the full reader computes
names.  Though IIRC there are some uses that are harder to get rid of.

Tom

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

* Re: [PATCH v2 0/8] Correctly handle inline functions with dwz
  2026-01-30 22:06 ` Simon Marchi
@ 2026-03-05 18:53   ` Tom Tromey
  0 siblings, 0 replies; 24+ messages in thread
From: Tom Tromey @ 2026-03-05 18:53 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Tom Tromey, gdb-patches

>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:

>> Anyway in v2 I've moved the line recording the CU inclusion and added
>> a comment explaining the placement.  I re-ran the aformentioned tests
>> and didn't touch anything.

Simon> I read up to patch 6 for now, they all look good to me and good on their
Simon> own.  If you want to merge those right away, you can use my Approved-By
Simon> for them.

Are you planning to review the last two patches?

If not, I'm planning to merge this series.

thanks,
Tom

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

end of thread, other threads:[~2026-03-05 18:53 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-26 21:33 [PATCH v2 0/8] Correctly handle inline functions with dwz Tom Tromey
2026-01-26 21:33 ` [PATCH v2 1/8] Don't call add_dependence from index_imported_unit Tom Tromey
2026-01-26 21:33 ` [PATCH v2 2/8] Skip partial units in process_psymtab_comp_unit Tom Tromey
2026-01-26 21:33 ` [PATCH v2 3/8] Don't consider DW_TAG_inlined_subroutine as interesting Tom Tromey
2026-01-26 21:33 ` [PATCH v2 4/8] Combine two cases in cooked_index_functions::search Tom Tromey
2026-01-26 21:33 ` [PATCH v2 5/8] Remove C++ special case from process_imported_unit_die Tom Tromey
2026-01-26 21:33 ` [PATCH v2 6/8] Have iterate_over_one_compunit_symtab search included symtabs Tom Tromey
2026-01-30 22:04   ` Simon Marchi
2026-01-31 15:04     ` Tom Tromey
2026-01-26 21:33 ` [PATCH v2 7/8] Handle inline functions with dwz Tom Tromey
2026-02-10  3:51   ` Simon Marchi
2026-01-26 21:33 ` [PATCH v2 8/8] Update .debug_names documentation Tom Tromey
2026-02-10  4:26   ` Simon Marchi
2026-02-10 16:14     ` Tom Tromey
2026-02-10 17:03       ` Simon Marchi
2026-02-10 19:52         ` Tom Tromey
2026-02-10 20:25           ` Simon Marchi
2026-02-10 20:46             ` Tom Tromey
2026-01-26 21:42 ` [PATCH v2 0/8] Correctly handle inline functions with dwz Simon Marchi
2026-01-26 23:31   ` Tom Tromey
2026-01-26 22:05 ` Tom de Vries
2026-01-30 22:06 ` Simon Marchi
2026-03-05 18:53   ` Tom Tromey
2026-02-06 19:14 ` Tom Tromey

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