[PATCH v4 7/8] Handle inline functions with dwz

Tom Tromey
2026-02-24T01:20:49Z

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.

This version updates an assert in the .debug_names writer, and adds a
regression test for that.

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 | 224 ++++++++++++++++++-------------
gdb/dwarf2/read.c | 49 +++++--
gdb/dwarf2/read.h | 37 +++++
gdb/testsuite/gdb.cp/breakpoint-locs.exp | 8 ++
10 files changed, 348 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 ef2427d0a71..723e027172e 100644
--- a/gdb/dwarf2/cooked-index-worker.c
+++ b/gdb/dwarf2/cooked-index-worker.c
@@ -105,6 +105,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 *included : iter.second)
+ included->add_includer (iter.first);
+}
+
+/* See cooked-index-worker.h. */
+
void
cooked_index_worker::start ()
{
@@ -239,6 +249,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 d46415bf8bc..1799c0b9e08 100644
--- a/gdb/dwarf2/cooked-index-worker.h
+++ b/gdb/dwarf2/cooked-index-worker.h
@@ -66,17 +66,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,
@@ -154,6 +143,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;
@@ -198,6 +197,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 3221ccd34f8..26be0aff7bf 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;
@@ -146,6 +156,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;
@@ -281,6 +292,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;
}
}

@@ -359,7 +380,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)
@@ -427,8 +448,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);

@@ -526,6 +546,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. */
@@ -534,7 +555,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)
@@ -583,18 +605,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)
@@ -607,11 +636,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..0e4bec11928 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,137 @@ 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
+ || (entry->flags & IS_INLINED) != 0);
+
+ /* 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 +1272,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 +1311,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 e48745d7969..504c8422e04 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);
@@ -3393,7 +3398,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);
}
}
@@ -3413,7 +3418,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);
}

@@ -14191,15 +14196,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
@@ -14208,7 +14213,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
@@ -14264,8 +14269,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;
}
}
@@ -18028,6 +18037,30 @@ dwarf2_per_cu::ensure_lang (dwarf2_per_objfile *per_objfile)
true, std::nullopt, abbrev_table_cache);
}

+/* 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 50bab220888..33ea33692cd 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -251,6 +251,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
@@ -280,6 +289,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; }

@@ -431,6 +447,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. */
diff --git a/gdb/testsuite/gdb.cp/breakpoint-locs.exp b/gdb/testsuite/gdb.cp/breakpoint-locs.exp
index 5895cd4549d..2ab046d3be8 100644
--- a/gdb/testsuite/gdb.cp/breakpoint-locs.exp
+++ b/gdb/testsuite/gdb.cp/breakpoint-locs.exp
@@ -27,3 +27,11 @@ if { [prepare_for_testing "failed to prepare" $testfile "$srcfile $srcfile2"\
}

gdb_test "break N1::C1::baz" "\\(2 locations\\)"
+
+if {[have_index $binfile] == ""} {
+ set index_dir [host_standard_output_file ""]
+ # This is a regression test for a crash that occurred in the
+ # dwz+inlining series. See PR symtab/30728.
+ gdb_test_no_output "save gdb-index -dwarf-5 $index_dir" \
+ "save gdb-index"
+}

--
2.49.0