@@ -160,6 +160,7 @@ abbrev_table::read (struct dwarf2_section_info *section,
case DW_AT_specification:
case DW_AT_abstract_origin:
case DW_AT_extension:
+ case DW_AT_signature:
has_specification_or_origin = true;
break;
@@ -35,6 +35,7 @@ to_string (cooked_index_flag flags)
MAP_ENUM_FLAG (IS_PARENT_DEFERRED),
MAP_ENUM_FLAG (IS_SYNTHESIZED),
MAP_ENUM_FLAG (IS_INLINED),
+ MAP_ENUM_FLAG (IS_NAME_DEFERRED),
};
return flags.to_string (mapping);
@@ -175,7 +176,7 @@ cooked_index_entry::full_name (struct obstack *storage,
cooked_index_full_name_flag name_flags,
const char *default_sep) const
{
- const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name : canonical;
+ const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name () : canonical;
if ((flags & IS_LINKAGE) != 0 || get_parent () == nullptr)
return local_name;
@@ -197,8 +198,8 @@ cooked_index_entry::full_name (struct obstack *storage,
cooked_index_shard::canonicalize_names. */
if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0)
{
- if (strstr (name, "__") != nullptr)
- return name;
+ if (strstr (name (), "__") != nullptr)
+ return name ();
sep = "__";
break;
}
@@ -233,10 +234,15 @@ cooked_index_entry::write_scope (struct obstack *storage,
{
if (get_parent () != nullptr)
get_parent ()->write_scope (storage, sep, flags);
+
+ /* Any entry with an unresolved name will have been filtered out by
+ cooked_index_shard::prune_nameless_entries. */
+ gdb_assert (!name_is_deferred ());
+
/* When computing the Ada linkage name, the entry might not have
been canonicalized yet, so use the 'name'. */
const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0
- ? name
+ ? name ()
: canonical);
obstack_grow (storage, local_name, strlen (local_name));
obstack_grow (storage, sep, strlen (sep));
@@ -47,6 +47,8 @@ enum cooked_index_flag_enum : unsigned char
/* True if this is a function that has DW_AT_inline set in a way
that indicates it was inlined. */
IS_INLINED = 64,
+ /* True if m_name.deferred has a value rather than m_name.resolved. */
+ IS_NAME_DEFERRED = 128,
};
DEF_ENUM_FLAGS_TYPE (enum cooked_index_flag_enum, cooked_index_flag);
@@ -79,6 +81,31 @@ union cooked_index_entry_ref
parent_map::addr_type deferred;
};
+/* Type representing either a resolved or deferred cooked_index_entry
+ name. A deferred name is held as the DW_AT_signature of the DIE
+ that carries the real name. */
+
+union cooked_index_entry_name_ref
+{
+ cooked_index_entry_name_ref (ULONGEST deferred_)
+ {
+ deferred = deferred_;
+ }
+
+ cooked_index_entry_name_ref (const char *resolved_)
+ {
+ resolved = resolved_;
+ }
+
+ const char *resolved;
+ ULONGEST deferred;
+};
+
+/* Type that maps DW_AT_signature values for a TU to the name of the
+ primary type within the TU. */
+
+using signature_to_name_map = gdb::unordered_map<ULONGEST, const char *>;
+
/* Return a string representation of FLAGS. */
std::string to_string (cooked_index_flag flags);
@@ -94,15 +121,16 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
{
cooked_index_entry (sect_offset die_offset_, enum dwarf_tag tag_,
cooked_index_flag flags_,
- enum language lang_, const char *name_,
+ enum language lang_,
+ cooked_index_entry_name_ref name_,
cooked_index_entry_ref parent_entry_,
dwarf2_per_cu *per_cu_)
- : name (name_),
- tag (tag_),
+ : tag (tag_),
flags (flags_),
lang (lang_),
die_offset (die_offset_),
per_cu (per_cu_),
+ m_name (name_),
m_parent_entry (parent_entry_)
{
}
@@ -225,6 +253,36 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
return m_parent_entry.deferred;
}
+ /* Return the entry's name. This may be the name or the linkage
+ name -- two entries are created for DIEs which have both
+ attributes. It points to a string that outlives this entry,
+ which in practice means the mapped DWARF or the storage of one of
+ the cooked_index_shard objects. */
+ const char *name () const
+ {
+ gdb_assert (!name_is_deferred ());
+ return m_name.resolved;
+ }
+
+ /* Return true if this entry's name still has to be resolved. */
+ bool name_is_deferred () const
+ { return (flags & IS_NAME_DEFERRED) != 0; }
+
+ /* Return the signature of the DIE holding this entry's name. */
+ ULONGEST get_deferred_name () const
+ {
+ gdb_assert (name_is_deferred ());
+ return m_name.deferred;
+ }
+
+ /* Resolve deferred name to NAME. */
+ void resolve_name (const char *name)
+ {
+ gdb_assert (name_is_deferred ());
+ flags = flags & ~IS_NAME_DEFERRED;
+ m_name.resolved = name;
+ }
+
/* 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
@@ -252,12 +310,7 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
defined in some CU that is included by many other CUs. */
iteration_status 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
- attributes. */
- const char *name;
- /* The canonical name. This may be equal to NAME. */
+ /* The canonical name. This may be equal to the name. */
const char *canonical = nullptr;
/* The DWARF tag. */
enum dwarf_tag tag;
@@ -284,6 +337,10 @@ struct cooked_index_entry : public allocate_on_obstack<cooked_index_entry>
void write_scope (struct obstack *storage, const char *sep,
cooked_index_full_name_flag flags) const;
+ /* The entry's name, or the signature by which it can be found. See
+ IS_NAME_DEFERRED and the accessors above. */
+ cooked_index_entry_name_ref m_name;
+
/* The parent entry. This is NULL for top-level entries.
Otherwise, it points to the parent entry, such as a namespace or
class. */
@@ -20,6 +20,8 @@
#include "dwarf2/cooked-index-shard.h"
#include "dwarf2/tag.h"
#include "dwarf2/index-common.h"
+#include "dwarf2/read.h"
+#include "dwarf2/error.h"
#include "cp-support.h"
#include "c-lang.h"
#include "ada-lang.h"
@@ -48,7 +50,7 @@ cooked_index_shard::create (sect_offset die_offset,
enum dwarf_tag tag,
cooked_index_flag flags,
enum language lang,
- const char *name,
+ cooked_index_entry_name_ref name,
cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu)
{
@@ -75,7 +77,8 @@ cooked_index_shard::create (sect_offset die_offset,
cooked_index_entry *
cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
cooked_index_flag flags, enum language lang,
- const char *name, cooked_index_entry_ref parent_entry,
+ cooked_index_entry_name_ref name,
+ cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu)
{
cooked_index_entry *result = create (die_offset, tag, flags, lang, name,
@@ -85,6 +88,9 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
if ((flags & IS_PARENT_DEFERRED) != 0)
m_have_deferred_parents = true;
+ if (result->name_is_deferred ())
+ m_have_deferred_names = true;
+
/* An explicitly-tagged main program should always override the
implicit "main" discovery. */
if ((flags & IS_MAIN) != 0)
@@ -107,7 +113,8 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag,
&& parent_entry.resolved == nullptr
&& m_main == nullptr
&& language_may_use_plain_main (lang)
- && streq (name, "main"))
+ && !result->name_is_deferred ()
+ && streq (result->name (), "main"))
m_main = result;
return result;
@@ -125,10 +132,10 @@ cooked_index_shard::handle_gnat_encoded_entry
characters are left as-is. This is done to make name matching a
bit simpler; and for wide characters, it means the choice of Ada
source charset does not affect the indexer directly. */
- std::string canonical = ada_decode (entry->name, false, false);
+ std::string canonical = ada_decode (entry->name (), false, false);
if (canonical.empty ())
{
- entry->canonical = entry->name;
+ entry->canonical = entry->name ();
return;
}
std::vector<std::string_view> names = split_name (canonical.c_str (),
@@ -151,7 +158,7 @@ cooked_index_shard::handle_gnat_encoded_entry
last = create (entry->die_offset, DW_TAG_module,
IS_SYNTHESIZED, language_ada, new_name, parent,
entry->per_cu);
- last->canonical = last->name;
+ last->canonical = last->name ();
new_entries.push_back (last);
*slot = last;
}
@@ -175,7 +182,7 @@ struct cooked_index_entry_name_ptr_hash
std::uint64_t operator () (const cooked_index_entry *entry) const noexcept
{
- return ankerl::unordered_dense::hash<const char *> () (entry->name);
+ return ankerl::unordered_dense::hash<const char *> () (entry->name ());
}
};
@@ -186,12 +193,44 @@ struct cooked_index_entry_name_ptr_eq
bool operator () (const cooked_index_entry *a,
const cooked_index_entry *b) const noexcept
{
- return a->name == b->name;
+ return a->name () == b->name ();
}
};
/* See cooked-index-shard.h. */
+bool
+cooked_index_shard::resolve_deferred_names
+ (const signature_to_name_map &sig_names)
+{
+ bool have_nameless_entries = false;
+ for (cooked_index_entry *entry : m_entries)
+ {
+ if (!entry->name_is_deferred ())
+ continue;
+
+ const ULONGEST signature = entry->get_deferred_name ();
+
+ if (const auto it = sig_names.find (signature);
+ it != sig_names.end ())
+ entry->resolve_name (it->second);
+ else
+ {
+ have_nameless_entries = true;
+ complaint (_(DWARF_ERROR_PREFIX
+ "Cannot find signatured DIE %s referenced from DIE "
+ "at %s [in module %s]"),
+ hex_string (signature),
+ sect_offset_str (entry->die_offset),
+ entry->per_cu->per_bfd ()->filename ());
+ }
+ }
+
+ return have_nameless_entries;
+}
+
+/* See cooked-index-shard.h. */
+
void
cooked_index_shard::resolve_deferred_parents
(const parent_map_map *parent_maps)
@@ -209,6 +248,38 @@ cooked_index_shard::resolve_deferred_parents
/* See cooked-index-shard.h. */
+void
+cooked_index_shard::prune_nameless_entries ()
+{
+ for (cooked_index_entry *entry : m_entries)
+ {
+ /* Remove a parent reference if the parent has no name. This
+ leaves ENTRY as an orphan, but this only happens if the DWARF
+ is corrupted and we failed to find a name for the parent. We
+ can safely check the parent's name at this point because all
+ deferred names and parent links will have been resolved in
+ all shards before this is called on any shard. */
+ if (const cooked_index_entry *parent = entry->get_parent ();
+ parent != nullptr && parent->name_is_deferred ())
+ entry->set_parent (nullptr);
+ }
+
+ /* If we failed to resolve the name of an entry via its signature
+ then remove the entry from the m_entries vector. This should be
+ rare, and should only happen when we have corrupted DWARF. The
+ entries still live on the obstack, so parent pointers are still
+ valid, but removing entries from the index means we don't try to
+ search them when looking for index hits. */
+ m_entries.erase (std::remove_if (m_entries.begin (), m_entries.end (),
+ [] (const cooked_index_entry *e)
+ {
+ return e->name_is_deferred ();
+ }),
+ m_entries.end ());
+}
+
+/* See cooked-index-shard.h. */
+
void
cooked_index_shard::canonicalize_names ()
{
@@ -239,21 +310,25 @@ cooked_index_shard::canonicalize_names ()
/* Deferred parents should not reach this point. */
gdb_assert ((entry->flags & IS_PARENT_DEFERRED) == 0);
+ /* Entries without a name are filtered out during the call to
+ prune_nameless_entries. */
+ gdb_assert (!entry->name_is_deferred ());
+
/* Note that this code must be kept in sync with
cooked_index::get_main -- if canonicalization is required
here, then a check might be required there. */
gdb_assert (entry->canonical == nullptr);
if ((entry->flags & IS_LINKAGE) != 0)
- entry->canonical = entry->name;
+ entry->canonical = entry->name ();
else if (entry->lang == language_ada)
{
/* Newer versions of GNAT emit DW_TAG_module and use a
hierarchical structure. In this case, we don't need to
do any extra work. This can be detected by looking for a
GNAT-encoded name. */
- if (strstr (entry->name, "__") == nullptr)
+ if (strstr (entry->name (), "__") == nullptr)
{
- entry->canonical = entry->name;
+ entry->canonical = entry->name ();
/* If the entry does not have a parent, then there's
nothing extra to do here -- the entry itself is
@@ -300,10 +375,10 @@ cooked_index_shard::canonicalize_names ()
name. */
gdb::unique_xmalloc_ptr<char> canon_name
= (entry->lang == language_cplus
- ? cp_canonicalize_string (entry->name)
- : c_canonicalize_name (entry->name));
+ ? cp_canonicalize_string (entry->name ())
+ : c_canonicalize_name (entry->name ()));
if (canon_name == nullptr)
- entry->canonical = entry->name;
+ entry->canonical = entry->name ();
else
entry->canonical = m_names.insert (std::move (canon_name));
}
@@ -315,7 +390,7 @@ cooked_index_shard::canonicalize_names ()
}
}
else
- entry->canonical = entry->name;
+ entry->canonical = entry->name ();
}
/* Make sure any new Ada entries end up in the results. This isn't
@@ -26,6 +26,7 @@
#include "addrmap.h"
#include "gdbsupport/iterator-range.h"
#include "gdbsupport/string-set.h"
+#include "complaints.h"
/* An index of interesting DIEs. This is "cooked", in contrast to a
mapped .debug_names or .gdb_index, which are "raw". An entry in
@@ -44,7 +45,7 @@ class cooked_index_shard
Entries are owned by this object. The new item is returned. */
cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
cooked_index_flag flags, enum language lang,
- const char *name,
+ cooked_index_entry_name_ref name,
cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu);
@@ -105,7 +106,7 @@ class cooked_index_shard
enum dwarf_tag tag,
cooked_index_flag flags,
enum language lang,
- const char *name,
+ cooked_index_entry_name_ref name,
cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu);
@@ -118,16 +119,50 @@ class cooked_index_shard
(cooked_index_entry *entry, htab_t gnat_entries,
std::vector<cooked_index_entry *> &new_entries);
+ /* Use SIG_NAMES to resolve the deferred names of entries in this shard.
+
+ Return true if any deferred name could not be resolved. */
+ bool resolve_deferred_names (const signature_to_name_map &sig_names);
+
/* Use PARENT_MAPS to resolve the deferred parent links of entries in this
shard. */
void resolve_deferred_parents (const parent_map_map *parent_maps);
+ /* Remove index entries that have no name (for which we failed to
+ resolve the name in resolve_deferred_names). Break any parent link
+ pointing to an entry with no name. */
+ void prune_nameless_entries ();
+
/* Compute the canonical name for the entries in this shard.
Due to how Ada name lookups work, this function may also create new index
entries with full names. */
void canonicalize_names ();
+ /* Called after each step of the finalization process. Store
+ COMPLAINTS so they can be reported later on the main thread. */
+ void merge_finalize_complaints (complaint_collection &&complaints)
+ {
+ if (m_finalize_complaints.empty ())
+ m_finalize_complaints = std::move (complaints);
+ else
+ {
+ /* The current version of gdb::unordered_set doesn't support
+ the merge method that std::unordered_set supports. If we
+ update gdb::unordered_set then we could switch this to use
+ merge(). */
+ m_finalize_complaints.insert (complaints.begin (), complaints.end ());
+ }
+ }
+
+ /* Return the set of complaints emitted during the finalization
+ process. We move these complaints out of the shard as these are
+ only emitted once, and don't need to be stored beyond that. */
+ complaint_collection release_finalize_complaints ()
+ {
+ return std::move (m_finalize_complaints);
+ }
+
/* Storage for the entries. */
auto_obstack m_storage;
@@ -143,9 +178,17 @@ class cooked_index_shard
/* Storage for canonical names. */
gdb::string_set m_names;
+ /* True if at least one entry in this shard has a name that requires
+ deferred resolution. */
+ bool m_have_deferred_names = false;
+
/* True if at least one entry in this shard has a parent link that requires
deferred resolution. */
bool m_have_deferred_parents = false;
+
+ /* Any complaints emitted while finalizing the index are stored
+ here until they can be emitted on the main thread. */
+ complaint_collection m_finalize_complaints;
};
using cooked_index_shard_up = std::unique_ptr<cooked_index_shard>;
@@ -249,6 +249,15 @@ cooked_index_worker::done_reading ()
m_all_parents_map.add_map (*one_result.get_parent_map ());
}
+ {
+ scoped_time_it time_it ("DWARF add signature name map", m_per_command_time);
+
+ /* Combine all of the signature to name maps. */
+ for (cooked_index_worker_result &one_result : m_results)
+ for (const auto &[sig, name] : one_result.get_sig_name_map ())
+ m_all_sig_names_map.emplace (sig, name);
+ }
+
/* Update all the CU inclusion information. */
for (auto &item : m_results)
item.invert_cu_inclusions ();
@@ -68,7 +68,7 @@ class cooked_index_worker_result
cooked-index.h. The new entry is returned. */
cooked_index_entry *add (sect_offset die_offset, enum dwarf_tag tag,
cooked_index_flag flags, enum language lang,
- const char *name,
+ cooked_index_entry_name_ref name,
cooked_index_entry_ref parent_entry,
dwarf2_per_cu *per_cu)
{
@@ -83,6 +83,15 @@ class cooked_index_worker_result
return m_shard->add (name);
}
+ /* Called when processing TUs to record that the primary type within
+ a type unit with SIGNATURE, was called NAME. This information
+ will be used during finalization to fix-up the name of any
+ entries carrying the IS_NAME_DEFERRED flag. */
+ void add_signatured_type_name (ULONGEST signature, const char *name)
+ {
+ m_sig_name_map.emplace (signature, name);
+ }
+
/* Install the current addrmap into the shard being constructed,
then transfer ownership of the index to the caller. */
cooked_index_shard_up release_shard ()
@@ -153,6 +162,12 @@ class cooked_index_worker_result
discovered. */
void invert_cu_inclusions ();
+ /* The signature to name map for this worker. */
+ const signature_to_name_map &get_sig_name_map () const
+ {
+ return m_sig_name_map;
+ }
+
private:
/* The abbrev table cache used by this indexer. */
abbrev_table_cache m_abbrev_table_cache;
@@ -188,6 +203,9 @@ class cooked_index_worker_result
/* Parent map for each CU that is read. */
parent_map m_parent_map;
+ /* Signature to name map for the primary type in a TU. */
+ signature_to_name_map m_sig_name_map;
+
/* A writeable addrmap being constructed by this scanner. */
addrmap_mutable m_addrmap;
@@ -272,6 +290,12 @@ class cooked_index_worker
return &m_all_parents_map;
}
+ /* Return the map containing the complete signature to name information. */
+ const signature_to_name_map &get_sig_name_map () const
+ {
+ return m_all_sig_names_map;
+ }
+
protected:
/* Let cooked_index call the 'set' and 'write_to_cache' methods. */
@@ -319,6 +343,10 @@ class cooked_index_worker
parent relationships. */
parent_map_map m_all_parents_map;
+ /* Map from signature to name of primary type within a TU. This is
+ the combined map, built after all the workers have finished. */
+ signature_to_name_map m_all_sig_names_map;
+
/* Current state of this object. */
cooked_state m_state = cooked_state::INITIAL;
/* Mutex and condition variable used to synchronize. */
@@ -58,7 +58,23 @@ cooked_index::wait (cooked_state desired_state, bool allow_quit)
if (m_state == nullptr)
return;
- if (m_state->wait (desired_state, allow_quit))
+ bool done = m_state->wait (desired_state, allow_quit);
+
+ /* Emit any cached complaints if we have finalized and we are on the
+ main thread. Check for the requested state or the DONE flag
+ here, we might have only asked for MAIN_AVAILABLE, but if the
+ workers are quick then they might be done, in which case we
+ should emit the complaints now. */
+ if (!m_finalize_complaints_emitted
+ && is_main_thread ()
+ && (desired_state >= cooked_state::FINALIZED || done))
+ {
+ m_finalize_complaints_emitted = true;
+ for (const auto &shard : m_shards)
+ re_emit_complaints (shard->release_finalize_complaints ());
+ }
+
+ if (done)
{
/* Only the main thread can modify this. */
gdb_assert (is_main_thread ());
@@ -75,7 +91,40 @@ cooked_index::set_contents ()
m_state->set (cooked_state::MAIN_AVAILABLE);
/* Start the first step of index finalization. */
- this->start_resolve_deferred_parents ();
+ this->start_resolve_deferred_names ();
+}
+
+/* See cooked-index.h. */
+
+void
+cooked_index::start_resolve_deferred_names ()
+{
+ gdb::task_group group ([this] ()
+ {
+ this->start_resolve_deferred_parents ();
+ });
+
+ /* Arrange to call resolve_deferred_names on each shard that has
+ deferred names. */
+ for (const cooked_index_shard_up &shard : m_shards)
+ {
+ if (!shard->m_have_deferred_names)
+ continue;
+
+ group.add_task ([this, this_shard = shard.get ()] ()
+ {
+ complaint_interceptor complaint_handler;
+ scoped_time_it time_it ("DWARF resolve deferred names worker",
+ m_state->m_per_command_time);
+
+ if (this_shard->resolve_deferred_names (m_state->get_sig_name_map ()))
+ m_have_nameless_entries.store (true);
+
+ this_shard->merge_finalize_complaints (complaint_handler.release ());
+ });
+ }
+
+ group.start ();
}
/* See cooked-index.h. */
@@ -85,7 +134,7 @@ cooked_index::start_resolve_deferred_parents ()
{
gdb::task_group group ([this] ()
{
- this->start_canonicalize_names ();
+ this->start_prune_nameless_entries ();
});
/* Arrange to call resolve_deferred_parents on each shard that has at least
@@ -97,10 +146,12 @@ cooked_index::start_resolve_deferred_parents ()
group.add_task ([this, this_shard = shard.get ()] ()
{
+ complaint_interceptor complaint_handler;
scoped_time_it time_it ("DWARF resolve deferred parents worker",
m_state->m_per_command_time);
this_shard->resolve_deferred_parents (m_state->get_parent_map_map ());
+ this_shard->merge_finalize_complaints (complaint_handler.release ());
});
}
@@ -109,6 +160,44 @@ cooked_index::start_resolve_deferred_parents ()
/* See cooked-index.h. */
+void
+cooked_index::start_prune_nameless_entries ()
+{
+ gdb::task_group group ([this] ()
+ {
+ this->start_canonicalize_names ();
+ });
+
+ /* Remove index entries whose name we could not resolve.
+
+ If there is any nameless entry, this step needs to run on all the shards,
+ because there could be children of a nameless entry in other shards,
+ whose parent links we want to break.
+
+ This step normally only runs in case there is something wrong with the
+ DWARF info. */
+ if (m_have_nameless_entries.load ())
+ for (const cooked_index_shard_up &shard : m_shards)
+ {
+ group.add_task ([this, this_shard = shard.get ()] ()
+ {
+ complaint_interceptor complaint_handler;
+
+ scoped_time_it time_it ("DWARF prune nameless entries worker",
+ m_state->m_per_command_time);
+
+ this_shard->prune_nameless_entries ();
+
+ this_shard->merge_finalize_complaints
+ (complaint_handler.release ());
+ });
+ }
+
+ group.start ();
+}
+
+/* See cooked-index.h. */
+
void
cooked_index::start_canonicalize_names ()
{
@@ -124,10 +213,12 @@ cooked_index::start_canonicalize_names ()
{
group.add_task ([this, this_shard = shard.get ()] ()
{
+ complaint_interceptor complaint_handler;
scoped_time_it time_it ("DWARF canonicalize names worker",
m_state->m_per_command_time);
this_shard->canonicalize_names ();
+ this_shard->merge_finalize_complaints (complaint_handler.release ());
});
}
@@ -239,7 +330,7 @@ cooked_index::get_main () const
exception. */
if ((entry->lang != language_ada
&& entry->lang != language_cplus)
- || streq (entry->name, "main"))
+ || streq (entry->name (), "main"))
{
/* There won't be one better than this. */
return entry;
@@ -289,7 +380,7 @@ cooked_index::dump (gdbarch *arch)
gdb_printf (" [%zu] ((cooked_index_entry *) %p)\n", i++, entry);
gdb_printf (" name: %ps\n", styled_string (style,
- entry->name));
+ entry->name ()));
gdb_printf (" canonical: %ps\n", styled_string (style,
entry->canonical));
gdb_printf (" qualified: %ps\n",
@@ -305,7 +396,7 @@ cooked_index::dump (gdbarch *arch)
entry->get_deferred_parent ());
else if (entry->get_parent () != nullptr)
gdb_printf (" parent: ((cooked_index_entry *) %p) [%s]\n",
- entry->get_parent (), entry->get_parent ()->name);
+ entry->get_parent (), entry->get_parent ()->name ());
else
gdb_printf (" parent: ((cooked_index_entry *) 0)\n");
@@ -315,7 +406,7 @@ cooked_index::dump (gdbarch *arch)
const cooked_index_entry *main_entry = this->get_main ();
if (main_entry != nullptr)
gdb_printf (" main: ((cooked_index_entry *) %p) [%s]\n", main_entry,
- main_entry->name);
+ main_entry->name ());
else
gdb_printf (" main: ((cooked_index_entry *) 0)\n");
@@ -31,6 +31,8 @@
#include "dwarf2/cooked-index-shard.h"
#include "dwarf2/cooked-index-worker.h"
+#include <atomic>
+
/* The main index of DIEs.
The index is created by multiple threads. The overall process is
@@ -72,10 +74,16 @@
. compute_main_name cooked_index::set_contents
. | |
. v v
- . wait (MAIN_AVAILABLE) resolve deferred parents
+ . wait (MAIN_AVAILABLE) resolve deferred names
. | |
. v v
- . done canonicalize names
+ . done resolve deferred parents
+ . |
+ . v
+ . prune nameless entries
+ . |
+ . v
+ . canonicalize names
. |
. v
. state = FINALIZED
@@ -176,13 +184,23 @@ class cooked_index : public dwarf_scanner_base
{ wait (cooked_state::CACHE_DONE); }
private:
+ /* Start the "resolve deferred names" step of index finalization. */
+ void start_resolve_deferred_names ();
+
/* Start the "resolve deferred parents" step of index finalization. */
void start_resolve_deferred_parents ();
+ /* Start the "prune nameless entries" step of index finalization.
+
+ This step must run after "resolve deferred names" and "resolve deferred
+ parents", because it relies on names and parents being set. */
+ void start_prune_nameless_entries ();
+
/* Start the "canonicalize names" step of index finalization.
This step must run after "resolve deferred parents", because it depends on
- the parents being set. */
+ the parents being set, and after "prune nameless entries", because it
+ requires all entries to have a name. */
void start_canonicalize_names ();
/* Execute the "write to cache" step at the end of index
@@ -197,6 +215,16 @@ class cooked_index : public dwarf_scanner_base
that the state is CACHE_DONE -- it's important to note that only
the main thread may change the value of this pointer. */
cooked_index_worker_up m_state;
+
+ /* Any complaints raised during finalization are held within the
+ shards in M_SHARDS. Once the main thread has waited for
+ finalization to be complete then the cached complaints are
+ emitted, and this flag is set to true. */
+ bool m_finalize_complaints_emitted = false;
+
+ /* True if some deferred name could not be resolved during the "resolve
+ deferred names" step. */
+ std::atomic<bool> m_have_nameless_entries = false;
};
/* An implementation of quick_symbol_functions for the cooked DWARF
@@ -157,6 +157,7 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
parent_map::addr_type *maybe_defer,
bool *is_enum_class,
bool *is_inlined,
+ std::optional<ULONGEST> *signature,
bool for_specification)
{
bool is_declaration = false;
@@ -234,6 +235,16 @@ cooked_indexer::scan_attributes (dwarf2_per_cu *scanning_per_cu,
attr.get_ref_die_offset () };
break;
+ case DW_AT_signature:
+ /* The DW_AT_signature could also be a direct reference to a
+ type DIE. We don't currently try to capture those
+ signatures as right now we're only capturing this in
+ order to handle the fact that some TUs are parsed after
+ the parallel CU parsing. */
+ if (attr.form == DW_FORM_ref_sig8)
+ signature->emplace (attr.as_signature ());
+ break;
+
case DW_AT_external:
if (attr.as_boolean ())
*flags &= ~IS_STATIC;
@@ -380,7 +391,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, is_inlined, true);
+ is_enum_class, is_inlined, signature, true);
}
if (!for_specification)
@@ -547,6 +558,7 @@ cooked_indexer::index_dies (cutu_reader *reader,
const cooked_index_entry *this_parent_entry = parent_entry;
bool is_enum_class = false;
bool is_inlined = false;
+ std::optional<ULONGEST> signature;
/* The scope of a DW_TAG_entry_point cooked_index_entry is the one of
its surrounding subroutine. */
@@ -556,7 +568,7 @@ cooked_indexer::index_dies (cutu_reader *reader,
= scan_attributes (reader->cu ()->per_cu, reader, info_ptr, info_ptr,
abbrev, &name, &linkage_name, &flags, &sibling,
&this_parent_entry, &defer, &is_enum_class,
- &is_inlined, false);
+ &is_inlined, &signature, false);
/* A DW_TAG_entry_point inherits its static/extern property from
the enclosing subroutine. */
if (abbrev->tag == DW_TAG_entry_point)
@@ -611,19 +623,33 @@ cooked_indexer::index_dies (cutu_reader *reader,
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 scan_attributes failed to find a name then we might still
+ want to create an entry if we think we might later find a
+ name via the signature. In that case the entry holds the
+ signature until the name is resolved. */
+ if (name != nullptr || signature.has_value ())
{
+ cooked_index_flag entry_flags = flags;
+ cooked_index_entry_name_ref name_ref = name;
+ cooked_index_entry_ref parent_ref = this_parent_entry;
+
+ if (name == nullptr)
+ {
+ name_ref = signature.value ();
+ entry_flags |= IS_NAME_DEFERRED;
+ }
+
if (defer != 0)
- this_entry
- = m_index_storage->add (this_die, abbrev->tag,
- flags | IS_PARENT_DEFERRED,
- m_language, name,
- defer, cu_for_entry);
- else
- this_entry
- = m_index_storage->add (this_die, abbrev->tag, flags,
- m_language, name,
- this_parent_entry, cu_for_entry);
+ {
+ parent_ref = defer;
+ entry_flags |= IS_PARENT_DEFERRED;
+ }
+
+ this_entry
+ = m_index_storage->add (this_die, abbrev->tag, entry_flags,
+ m_language, name_ref, parent_ref,
+ cu_for_entry);
}
else if (this_parent_entry != nullptr)
{
@@ -637,6 +663,14 @@ cooked_indexer::index_dies (cutu_reader *reader,
m_die_range_map->add_entry (addr, addr, this_parent_entry);
}
+ /* If THIS_DIE is the primary type within a TU, and has a valid
+ name, then add an entry mapping the signature to the name. */
+ if (const signatured_type *st = cu_for_entry->as_signatured_type ();
+ name != nullptr
+ && st != nullptr
+ && this_die == st->type_offset_in_section)
+ m_index_storage->add_signatured_type_name (st->signature, name);
+
if (linkage_name != nullptr)
{
/* We only want this to be "main" if it has a linkage name
@@ -86,6 +86,7 @@ class cooked_indexer
parent_map::addr_type *maybe_defer,
bool *is_enum_class,
bool *is_inlined,
+ std::optional<ULONGEST> *signature,
bool for_specification);
/* Handle DW_TAG_imported_unit, by scanning the DIE to find
@@ -697,7 +697,7 @@ class debug_names
if ((entry->flags & IS_SYNTHESIZED) != 0)
return;
- m_name_to_value_set[entry->name].emplace_back (entry);
+ m_name_to_value_set[entry->name ()].emplace_back (entry);
}
/* Build all the tables. All symbols must be already inserted.
new file mode 100644
@@ -0,0 +1,155 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# The test sets up a type DIE without a DW_AT_name, the type DIE links
+# to a type within a TU via DW_AT_signature. Normally GDB would
+# use the DW_AT_signature to find the name of the type DIE.
+#
+# However, in this test there is no type DIE with the specified
+# signature!
+#
+# This test checks that GDB can emit a complaint when this situation is
+# encountered. The test also checks that trying to find a method
+# within the type using the qualified name will fail.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+ # Create the DWARF.
+ set asm_file [standard_output_file $::srcfile2]
+ Dwarf::assemble {
+ filename $asm_file
+ add_dummy_cus 0
+ } {
+ upvar version version
+
+ get_func_info foo
+ get_func_info main
+
+ declare_labels method_decl
+
+ tu { version $version } 0xdeadbeefdeadbeef the_type {
+ DW_TAG_type_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ the_type: DW_TAG_class_type {
+ DW_AT_name the_type
+ DW_AT_byte_size 1 sdata
+ }
+ }
+ }
+
+ cu { version $version } {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ DW_TAG_class_type {
+ DW_AT_declaration 1 flag
+ DW_AT_signature 0xdeadbeef01234567 ref_sig8
+ } {
+ method_decl: DW_TAG_subprogram {
+ DW_AT_name method
+ DW_AT_linkage_name _ZN8the_type6methodEv
+ DW_AT_declaration 1 flag
+ }
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_specification %$method_decl
+ DW_AT_low_pc $foo_start DW_FORM_addr
+ DW_AT_high_pc $foo_end DW_FORM_addr
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name main
+ DW_AT_low_pc $main_start DW_FORM_addr
+ DW_AT_high_pc $main_end DW_FORM_addr
+ }
+ }
+ }
+ }
+
+ set testfile_name ${::testfile}-${version}
+ set binfile_name [standard_output_file $testfile_name]
+ if { [build_executable "failed to build" $testfile_name \
+ [list $asm_file $::srcfile] {nodebug}] } {
+ return
+ }
+
+ clean_restart $testfile_name
+
+ set expect_complaint true
+ # If we have a .gdb_index already then the part of this test that
+ # checks for a warning when loading the executable is not going to
+ # work as the warning will have already been generated when the
+ # index was added.
+ if {[get_index_type $testfile_name] == "gdb"} {
+ set expect_complaint false
+ }
+
+ clean_restart
+
+ set host_binfile [gdb_remote_download host $binfile_name]
+ if { [is_remote host] } {
+ # For some remote host boards gdb_remote_download returns an
+ # absolute path, but for others it returns a relative path.
+ # The path reported in the complaint message is always
+ # absolute. Handle this with an optional prefix pattern.
+ set binfile_re "\[^\r\n\]*[string_to_regexp $host_binfile]"
+ } elseif {[section_get $binfile_name ".gnu_debuglink"] ne ""} {
+ set binfile_re "\[^\r\n\]+[string_to_regexp ${testfile_name}.debug]"
+ } else {
+ set binfile_re [string_to_regexp $binfile_name]
+ }
+
+ # Load the executable and check we see the expected complaint from
+ # the DWARF indexer.
+ gdb_test_no_output "maint set dwarf synchronous on"
+ gdb_test_no_output "set complaints 100"
+ set saw_complaint false
+ gdb_test_multiple "file $host_binfile" "file command" -lbl {
+ -re "^\r\nDuring symbol reading: DWARF Error: Cannot find signatured DIE 0xdeadbeef01234567 referenced from DIE at $::hex \\\[in module $binfile_re\\\](?=\r\n)" {
+ set saw_complaint true
+ exp_continue
+ }
+ -re "^\r\n$::gdb_prompt $" {
+ gdb_assert { $saw_complaint == $expect_complaint } $gdb_test_name
+ }
+ }
+
+ if { ![readnow] } {
+ # Check that GDB is unable to find this via the index. If we
+ # load the full symbols then GDB is able to resolve this print
+ # via the linkage name.
+ #
+ # It is unfortunate that there is a difference in behaviour
+ # depending on whether GDB is using the index or not, but
+ # remember, the DWARF is corrupted at this point, so it's
+ # probably OK if things don't fully work, so long as GDB
+ # doesn't crash.
+ gdb_test "print the_type::method" \
+ "^There is no field named method"
+ }
+}
+
+foreach_with_prefix version { 4 5 } {
+ run_test $version
+}
new file mode 100644
@@ -0,0 +1,143 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Like sig-type-unnamed-class.exp, but with split DWARF. The type
+# unit in the DWO file is skeletonless, which is the norm when using
+# split DWARF.
+#
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the DWO compile unit has
+# DW_AT_signature but no DW_AT_name, and the referenced type unit is
+# skeletonless.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c -dw.S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+
+proc run_test { version } {
+ set asm_file [standard_output_file $::srcfile2]
+
+ # Setup some state based on DWARF version.
+ if { $version == 5 } {
+ set dwo_cu_opts [list fission 1 version 5 dwo_id 0xF00D]
+ set skel_cu_opts [list version 5 dwo_id 0xF00D]
+ set dwo_name_attr DW_AT_dwo_name
+ } else {
+ set dwo_cu_opts [list fission 1 version 4]
+ set skel_cu_opts [list version 4]
+ set dwo_name_attr DW_AT_GNU_dwo_name
+ }
+
+ set testfile_name ${::testfile}-${version}
+
+ Dwarf::assemble $asm_file {
+ upvar version version
+ upvar dwo_cu_opts dwo_cu_opts
+ upvar skel_cu_opts skel_cu_opts
+ upvar dwo_name_attr dwo_name_attr
+ upvar testfile_name testfile_name
+
+ declare_labels method_decl
+
+ get_func_info foo
+
+ # Type unit in the DWO file. This is skeletonless: there is no
+ # corresponding skeleton TU in the main file.
+ tu {
+ fission 1
+ version $version
+ } 0xdeadbeef01234567 the_type {
+ type_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ the_type: DW_TAG_class_type {
+ DW_AT_name the_type
+ DW_AT_byte_size 1 DW_FORM_sdata
+ }
+ }
+ }
+
+ set debug_addr_base [debug_addr_label]
+
+ # Compile unit in the DWO file. Contains a class declaration
+ # stub with DW_AT_signature but no DW_AT_name, replicating what
+ # Clang emits with -fdebug-types-section. The class name must
+ # be resolved by following the signature to the type unit.
+ cu $dwo_cu_opts {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ DW_AT_name ${::srcfile}
+ DW_AT_comp_dir .
+ if { $version == 4 } {
+ DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8
+ }
+ } {
+ DW_TAG_class_type {
+ DW_AT_declaration 1 DW_FORM_flag
+ DW_AT_signature 0xdeadbeef01234567 DW_FORM_ref_sig8
+ } {
+ method_decl: DW_TAG_subprogram {
+ DW_AT_name method
+ DW_AT_linkage_name _ZN8the_type6methodEv
+ DW_AT_declaration 1 DW_FORM_flag
+ }
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_specification :$method_decl
+ DW_AT_low_pc $foo_start DW_FORM_GNU_addr_index
+ DW_AT_high_pc $foo_end DW_FORM_GNU_addr_index
+ }
+ }
+ }
+
+ # Skeleton CU in the main file.
+ cu $skel_cu_opts {
+ compile_unit {
+ $dwo_name_attr ${testfile_name}-dw.dwo DW_FORM_strp
+ DW_AT_comp_dir .
+ DW_AT_GNU_addr_base $debug_addr_base
+ if { $version == 4 } {
+ DW_AT_GNU_dwo_id 0xF00D DW_FORM_data8
+ }
+ } {}
+ }
+ }
+
+ set binfile_name [standard_output_file $testfile_name]
+ set obj [standard_output_file "${testfile_name}-dw.o"]
+ if {[build_executable_and_dwo_files "build exec and dwo" $binfile_name \
+ {nodebug} \
+ [list $asm_file {nodebug split-dwo} $obj] \
+ [list $::srcfile {nodebug}]]} {
+ return
+ }
+
+ clean_restart $testfile_name
+
+ # Check that GDB was able to find the parent for "method", and as
+ # a result, can correctly find this field of "the_type".
+ gdb_test "print the_type::method" \
+ [string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+ run_test $version
+}
new file mode 100644
@@ -0,0 +1,106 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Check that the cooked index correctly resolves the parent of a member
+# function when the class declaration stub in the compile unit has
+# DW_AT_signature but no DW_AT_name.
+#
+# This replicates what Clang emits with -fdebug-types-section. The
+# compile unit contains an unnamed DW_TAG_class_type declaration with
+# only DW_AT_declaration, DW_AT_signature, and child member function
+# declarations.
+#
+# The class name must be resolved by following the signature to the
+# type unit. Without this, the member function definitions (which use
+# DW_AT_specification to point at the child declarations) end up with
+# no parent, and qualified lookup fails.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile main-foo.c .S
+
+# Build the test program using VERSION for the CU/TU DWARF version.
+proc run_test { version } {
+ # Create the DWARF.
+ set asm_file [standard_output_file $::srcfile2]
+ Dwarf::assemble {
+ filename $asm_file
+ add_dummy_cus 0
+ } {
+ upvar version version
+
+ get_func_info foo
+ get_func_info main
+
+ declare_labels method_decl
+
+ tu { version $version } 0xdeadbeef01234567 the_type {
+ DW_TAG_type_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ the_type: DW_TAG_class_type {
+ DW_AT_name the_type
+ DW_AT_byte_size 1 sdata
+ }
+ }
+ }
+
+ cu { version $version } {
+ compile_unit {
+ DW_AT_language @DW_LANG_C_plus_plus
+ } {
+ DW_TAG_class_type {
+ DW_AT_declaration 1 flag
+ DW_AT_signature 0xdeadbeef01234567 ref_sig8
+ } {
+ method_decl: DW_TAG_subprogram {
+ DW_AT_name method
+ DW_AT_linkage_name _ZN8the_type6methodEv
+ DW_AT_declaration 1 flag
+ }
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_specification %$method_decl
+ DW_AT_low_pc $foo_start DW_FORM_addr
+ DW_AT_high_pc $foo_end DW_FORM_addr
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name main
+ DW_AT_low_pc $main_start DW_FORM_addr
+ DW_AT_high_pc $main_end DW_FORM_addr
+ }
+ }
+ }
+ }
+
+ if { [prepare_for_testing "failed to prepare" ${::testfile}-${version} \
+ [list $asm_file $::srcfile] {nodebug}] } {
+ return
+ }
+
+ # Check that GDB was able to find the parent for "method", and as
+ # a result, can correctly find this field of "the_type".
+ gdb_test "print the_type::method" \
+ [string_to_regexp " <the_type::method()>"]
+}
+
+foreach_with_prefix version { 4 5 } {
+ run_test $version
+}