From: simon.marchi@polymtl.ca
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@efficios.com>
Subject: [PATCH 8/8] gdb/dwarf: read foreign type units
Date: Mon, 16 Mar 2026 19:19:26 -0400 [thread overview]
Message-ID: <20260316232042.368080-9-simon.marchi@polymtl.ca> (raw)
In-Reply-To: <20260316232042.368080-1-simon.marchi@polymtl.ca>
From: Simon Marchi <simon.marchi@efficios.com>
In DWARF 5, foreign type units are type units present in .dwo files.
Type units in .dwo files don't have a matching skeleton in the main
file. When an .debug_names index is present, it can't include those
type units in the regular type unit list, since that list specifies
offsets in the main file. They are instead listed in the foreign TU
list, which is basically just a list of type signatures.
In order to help the debugger locate these units (i.e. find the .dwo
file containing them), individual index entries referencing foreign type
units may also include a reference to a compile unit that the debugger
can follow to find the appropriate .dwo file.
This patch implements reading the .debug_names foreign TU list and using
these "hint" CUs to locate foreign type units. I use the term "hint"
throughout the code, but I don't mind another name if someone has a
better idea.
The first part is to read the actual foreign TU list from the
.debug_names index and create signatured_type objects out of them. This
is done in the new function create_foreign_type_units_from_debug_names.
Append the newly created signatured_type to the
mapped_debug_names_reader::foreign_type_units vector, which will be used
later to resolve DW_IDX_type_unit indices. Populate the
dwarf2_per_bfd::signatured_types set, which contains signatured_types
indexed by signature. And finally, transfer ownership of the object to
the dwarf2_per_bfd::all_units vector.
Previously, all dwarf2_per_cu (including signatured_type) objects were
created with a non-nullptr section. With foreign type units, we don't
know the section at creation time. We also don't know the offset into
section nor the size of the unit. Therefore, add a
dwarf2_per_bfd::allocate_signatured_type overload that takes just the
signature. Remove the "section != nullptr" assert from the
dwarf2_per_cu constructor, but add it to the other allocate_* methods.
Since the new create_foreign_type_units_from_debug_names function adds
items to the dwarf2_per_bfd::all_units vector, the vector needs to be
sorted after create_foreign_type_units_from_debug_names runs. Remove
the finalize_all_units call from create_all_units, making the callers
responsible to call it.
The next step is to read the hint CU attributes when scanning the index
entries. Rework mapped_debug_names_reader::scan_one_entry to remember
which kind of unit references it saw for the entry (comp, type and/or
foreign type) and then figure out what this means. The logic is:
- Did the entry reference a foreign type unit? If so, it's a foreign
type unit. Does it also reference a hint CU? If not, drop the
entry, there's nothing useful we can do with it.
- Otherwise, did the entry reference a (non-foreign) type unit? Then
it's a regular type unit. If so, it shouldn't also have a
DW_IDX_compile_unit.
- Otherwise, did the entry reference a comp unit? If so, it's a comp
unit.
- Otherwise, we don't know what unit the entry references, it's an
error.
Since the .debug_name index attaches hint CU attributes to individual
index entries, my initial implementation added the hint CU information
to the cooked_index_entry structure. I am not sure why DWARF 5 chose to
do it this way, as opposed to attaching one hint per foreign TU. Does
this mean that two type units with the same signature could be different
and, for a specific index entry, it would be important to find one
specific instance of the type unit over the others? I have no idea.
However, I know that the current GDB DWARF reader is not able to load
multiple type units with the same signature but different content. Once
it loads one type unit with a given signature, all subsequent references
to that signature will use that loaded type unit. I therefore chose to
have the .debug_names reader record just one hint CU per foreign TU.
This avoids growing the cooked_index_entry structure for nothing, and
having to pass through this information through multiple layers.
The next step is to locate the .dwo file containing the foreign TUs when
we need them. This sometimes makes use of the hint CU, but not always.
I identified these 3 code paths:
1. When the type unit gets expanded directly, for instance if you use
"ptype" and there is a direct match into the type unit. This case
is handled in load_full_type_unit, calling a new function
fill_in_sig_entry_from_per_cu_hint. This one uses the hint recorded
by the .debug_names reader. When a cooked index entry exists and
refers to a foreign TU for which the section is not yet known, we
know that there exists a hint, otherwise we wouldn't have created
the entry in the first place.
2. The second one is when the type unit is referenced by some other
unit. This case is handled in follow_die_sig_1, calling another new
function fill_in_sig_entry_from_dwo_file. In this case, we know
which unit is referring to the TU, so we use that unit's dwo file to
fill in the details. As explained in the comment, this is sometimes
just an optimization, but sometimes also necessary, if the TU
does not have a hint, due to it not containing any indexed name.
3. Similarly, in dwarf2_base_index_functions::expand_all_symtabs, we
might have to handle foreign type units for which we don't have a
hint. I initially implemented something in two passes, to go dig in
the dwo_file structures to find those TUs, but ended up choosing to
just skip them, for the reasons explained in the comment there.
Setting a dwarf2_per_cu's section a posteriori breaks the assumed
ordering of the dwarf2_per_bfd::all_units vector. After setting the
section, re-sort the vector.
Add a target board to exercise this new code. This board builds with:
- type units (-fdebug-types-section)
- split DWARF (-gsplit-dwarf)
- .debug_names index (created by GDB)
I ran the whole testsuite with this board file and it's not perfect, but
the results are comparable to the dwarf5-fission-debug-types board, for
instance.
There is one known failure that I am unable to get to the bottom of. It
seems orthogonal to my change though, more like an indexer or symbol
reader issue. There are maybe more of this kind, but this is one
example:
FAIL: gdb.ada/tick_length_array_enum_idx.exp: ptype variable_table'length (GDB internal error)
/home/smarchi/src/binutils-gdb/gdb/dwarf2/read.c:1839: internal-error: search_one: Assertion `symtab != nullptr' failed.
The problem appears to be that a cooked index lookup for symbol
variable_table says that a given TU should contain a match. But then
trying to expand the TU makes dw2_instantiate_symtab yield a nullptr
compunit_symtab, I think because the symbol reader found nothing
interesting symbol-wise. And then the assert in search_one triggers.
The issue seems sensitive to some aspects of the environment (gnat
version?). I am able to reproduce the issue on Arch Linux (gnat 15)
with:
$ make check TESTS="gdb.ada/tick_length_array_enum_idx.exp" RUNTESTFLAGS="--target_board=dwarf5-fission-debug-types-debug-names"
But it doesn't reproduce on Debian 13 (gnat 14), Ubuntu 24.04 (gnat
13) or Fedora Rawhide (gnat 16).
Change-Id: I0d4ccc1cbbce3a337794341744d24091e8549d7f
---
gdb/dwarf2/read-debug-names.c | 206 ++++++++++++++----
gdb/dwarf2/read.c | 167 +++++++++++++-
gdb/dwarf2/read.h | 37 +++-
gdb/dwarf2/types.h | 2 +
...dwarf5-fission-debug-types-debug-names.exp | 29 +++
5 files changed, 389 insertions(+), 52 deletions(-)
create mode 100644 gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
diff --git a/gdb/dwarf2/read-debug-names.c b/gdb/dwarf2/read-debug-names.c
index 487e2ea87edb..a062c1ad2562 100644
--- a/gdb/dwarf2/read-debug-names.c
+++ b/gdb/dwarf2/read-debug-names.c
@@ -88,9 +88,13 @@ struct mapped_debug_names_reader
uint8_t offset_size = 0;
uint32_t cu_count = 0;
- uint32_t tu_count = 0, bucket_count = 0, name_count = 0;
+ uint32_t tu_count = 0;
+ uint32_t foreign_tu_count = 0;
+ uint32_t bucket_count = 0;
+ uint32_t name_count = 0;
const gdb_byte *cu_table_reordered = nullptr;
const gdb_byte *tu_table_reordered = nullptr;
+ const gdb_byte *foreign_tu_table_reordered = nullptr;
const uint32_t *bucket_table_reordered = nullptr;
const uint32_t *hash_table_reordered = nullptr;
const gdb_byte *name_table_string_offs_reordered = nullptr;
@@ -122,7 +126,11 @@ struct mapped_debug_names_reader
/* List of local TUs in the same order as found in the index (DWARF 5 section
6.1.1.4.3). */
- std::vector<dwarf2_per_cu *> type_units;
+ std::vector<signatured_type *> type_units;
+
+ /* List of foreign TUs in the same order as found in the index (DWARF 5
+ section 6.1.1.4.4). */
+ std::vector<signatured_type *> foreign_type_units;
/* Even though the scanning of .debug_names and creation of the
cooked index entries is done serially, we create multiple shards
@@ -217,7 +225,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
cooked_index_flag flags = 0;
sect_offset die_offset {};
enum language lang = language_unknown;
- dwarf2_per_cu *per_cu = nullptr;
+ dwarf2_per_cu *comp_unit = nullptr;
+ signatured_type *type_unit = nullptr;
+ signatured_type *foreign_type_unit = nullptr;
+
for (const auto &attr : indexval.attr_vec)
{
ULONGEST ull;
@@ -282,7 +293,6 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
{
case DW_IDX_compile_unit:
{
- /* Don't crash on bad data. */
if (ull >= this->comp_units.size ())
{
complain_about_index_entry
@@ -292,13 +302,17 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->comp_units[ull];
+ comp_unit = this->comp_units[ull];
break;
}
case DW_IDX_type_unit:
{
- /* Don't crash on bad data. */
- if (ull >= this->type_units.size ())
+ if (ull < this->type_units.size ())
+ type_unit = this->type_units[ull];
+ else if (auto foreign_idx = ull - this->type_units.size ();
+ foreign_idx < this->foreign_type_units.size ())
+ foreign_type_unit = this->foreign_type_units[foreign_idx];
+ else
{
complain_about_index_entry
(abfd, name, offset_in_entry_pool,
@@ -307,15 +321,10 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
continue;
}
- per_cu = this->type_units[ull];
break;
}
case DW_IDX_die_offset:
die_offset = sect_offset (ull);
- /* In a per-CU index (as opposed to a per-module index), index
- entries without CU attribute implicitly refer to the single CU. */
- if (per_cu == nullptr)
- per_cu = this->comp_units[0];
break;
case DW_IDX_parent:
parent = ull;
@@ -339,27 +348,117 @@ mapped_debug_names_reader::scan_one_entry (const char *name,
}
}
- /* Skip if we couldn't find a valid CU/TU index. */
- if (per_cu != nullptr)
+ /* From DWARF 5 section 6.1.1.3 ("Per-CU versus Per-Module Indexes"):
+
+ In a per-CU index, the CU list may have only a single entry, and index
+ entries may omit the CU attribute. */
+ if (comp_unit == nullptr
+ && type_unit == nullptr
+ && foreign_type_unit == nullptr)
{
- dwarf_read_debug_printf_v
- (" -> die_offset %s, per_cu offset %s",
- sect_offset_str (die_offset),
- sect_offset_str (per_cu->sect_off ()));
+ if (this->comp_units.size () != 1)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing CU index, but index has %zu CUs "
+ "(expecting 1)"),
+ this->comp_units.size ());
+ return entry;
+ }
- *result
- = indices[next_shard].add (die_offset, (dwarf_tag) indexval.dwarf_tag,
- flags, lang, name, nullptr, per_cu);
+ comp_unit = this->comp_units[0];
+ }
+
+ /* Figure out which unit this entry refers to. */
+ dwarf2_per_cu *actual_unit;
+
+ if (foreign_type_unit != nullptr)
+ {
+ if (type_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to two TUs"));
+ return entry;
+ }
- ++next_shard;
- if (next_shard == indices.size ())
- next_shard = 0;
+ /* Implement this part of DWARF 5 section 6.1.1.2 ("Structure of the Name
+ Index"):
+
+ When an index entry refers to a foreign type unit, it may have
+ attributes for both CU and (foreign) TU. For such entries, the CU
+ attribute gives the consumer a reference to the CU that may be used
+ to locate a split DWARF object file that contains the type unit. */
+ if (comp_unit == nullptr)
+ {
+ /* If a foreign type unit does not say which compile unit to follow
+ to find it, it's pretty much useless. */
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to foreign type unit but "
+ "no compile unit"));
+ return entry;
+ }
+
+ actual_unit = foreign_type_unit;
+
+ /* .debug_names attaches one "hint" CU per index entry, insinuating that
+ for some names / index entries, it is important which .dwo we choose to
+ locate the TU. Even if it's important, the DWARF reader is not
+ currently able to load multiple versions of the same TU. So just
+ record one hint CU for each foreign TU. */
+ if (foreign_type_unit->hint_per_cu == nullptr)
+ {
+ foreign_type_unit->hint_per_cu = comp_unit;
+ dwarf_read_debug_printf_v
+ (" hint CU for foreign type unit with signature %s: "
+ "unit offset %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (comp_unit->sect_off ()));
+ }
+ }
+ else if (type_unit != nullptr)
+ {
+ if (comp_unit != nullptr)
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry refers to both a CU and a TU"));
+ return entry;
+ }
- entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
+ actual_unit = type_unit;
}
+ else if (comp_unit != nullptr)
+ actual_unit = comp_unit;
else
- dwarf_read_debug_printf_v (" -> no valid CU/TU, skipping");
+ {
+ complain_about_index_entry
+ (abfd, name, offset_in_entry_pool,
+ _(".debug_names entry is missing a CU or TU reference"));
+ return entry;
+ }
+
+ gdb_assert (actual_unit != nullptr);
+
+ if (foreign_type_unit != nullptr)
+ dwarf_read_debug_printf_v (" -> signature %s, DIE offset: %s",
+ hex_string (foreign_type_unit->signature),
+ sect_offset_str (die_offset));
+ else
+ dwarf_read_debug_printf_v (" -> unit offset %s, DIE offset %s",
+ sect_offset_str (actual_unit->sect_off ()),
+ sect_offset_str (die_offset));
+
+ *result = indices[next_shard].add (die_offset,
+ (dwarf_tag) indexval.dwarf_tag, flags,
+ lang, name, nullptr, actual_unit);
+ ++next_shard;
+ if (next_shard == indices.size ())
+ next_shard = 0;
+
+ entry_pool_offsets_to_entries.emplace (offset_in_entry_pool, *result);
return entry;
}
@@ -553,7 +652,7 @@ build_and_check_tu_list_from_debug_names (dwarf2_per_objfile *per_objfile,
return false;
}
- map.type_units.emplace_back (per_cu);
+ map.type_units.emplace_back (per_cu->as_signatured_type ());
}
return true;
@@ -665,22 +764,12 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
/* foreign_type_unit_count - The number of TUs in the foreign TU
list. */
- uint32_t foreign_tu_count = read_4_bytes (abfd, addr);
+ map.foreign_tu_count = read_4_bytes (abfd, addr);
addr += 4;
dwarf_read_debug_printf ("cu_count: %u, tu_count: %u, "
"foreign_tu_count: %u",
- map.cu_count, map.tu_count, foreign_tu_count);
-
- if (foreign_tu_count != 0)
- {
- warning (_("Section .debug_names in %ps has unsupported %lu foreign TUs, "
- "ignoring .debug_names."),
- styled_string (file_name_style.style (),
- filename),
- static_cast<unsigned long> (foreign_tu_count));
- return false;
- }
+ map.cu_count, map.tu_count, map.foreign_tu_count);
/* bucket_count - The number of hash buckets in the hash lookup
table. */
@@ -761,6 +850,10 @@ read_debug_names_from_section (dwarf2_per_objfile *per_objfile,
map.tu_table_reordered = addr;
addr += map.tu_count * map.offset_size;
+ /* List of foreign TUs */
+ map.foreign_tu_table_reordered = addr;
+ addr += map.foreign_tu_count * sizeof (std::uint64_t);
+
/* Hash Lookup Table */
map.bucket_table_reordered = reinterpret_cast<const uint32_t *> (addr);
addr += map.bucket_count * 4;
@@ -918,6 +1011,35 @@ build_and_check_cu_lists_from_debug_names (dwarf2_per_bfd *per_bfd,
return build_and_check_cu_list_from_debug_names (per_bfd, dwz_map, dwz->info);
}
+/* Create signatured_type objects for the foreign TU list in MAP. */
+
+static void
+create_foreign_type_units_from_debug_names (dwarf2_per_bfd *per_bfd,
+ mapped_debug_names_reader &map)
+{
+ for (uint32_t i = 0; i < map.foreign_tu_count; ++i)
+ {
+ /* The list of foreign TUs is a list of 64-bit (DW_FORM_ref_sig8) type
+ signatures representing type units placed in .dwo files. All we know
+ about them for now is the signature. */
+ const gdb_byte *ptr
+ = map.foreign_tu_table_reordered + i * sizeof (std::uint64_t);
+ std::uint64_t sig
+ = extract_unsigned_integer (ptr, sizeof (std::uint64_t),
+ map.dwarf5_byte_order);
+
+ dwarf_read_debug_printf_v (" Foreign TU %u (%u): signature %s", i,
+ i + map.tu_count, hex_string (sig));
+
+ signatured_type_up sig_type
+ = per_bfd->allocate_signatured_type (sig);
+
+ map.foreign_type_units.emplace_back (sig_type.get ());
+ per_bfd->signatured_types.emplace (sig_type.get ());
+ per_bfd->all_units.emplace_back (sig_type.release ());
+ }
+}
+
/* See read-debug-names.h. */
bool
@@ -975,6 +1097,12 @@ dwarf2_read_debug_names (dwarf2_per_objfile *per_objfile)
return false;
}
+ create_foreign_type_units_from_debug_names (per_objfile->per_bfd, map);
+
+ /* create_foreign_type_units_from_debug_names may add more entries to the
+ ALL_UNITS vector, so it must be called before finalize_all_units. */
+ finalize_all_units (per_objfile->per_bfd);
+
per_bfd->debug_aranges.read (per_objfile->objfile);
/* There is a single address map for the whole index (coming from
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index c58cc1731f47..39909d103cc3 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -745,6 +745,13 @@ static struct die_info *follow_die_ref (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
+static void fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
+ signatured_type *sig_entry,
+ dwo_unit *dwo_entry);
+
+static void fill_in_sig_entry_from_per_cu_hint
+ (dwarf2_per_objfile &per_objfile, signatured_type &sig_type);
+
static struct die_info *follow_die_sig (struct die_info *,
const struct attribute *,
struct dwarf2_cu **);
@@ -1457,6 +1464,7 @@ dwarf2_per_bfd::allocate_per_cu (dwarf2_section_info *section,
sect_offset sect_off, unsigned int length,
bool is_dwz)
{
+ gdb_assert (section != nullptr);
dwarf2_per_cu_up result (new dwarf2_per_cu (this, section, sect_off,
length, is_dwz));
result->index = all_units.size ();
@@ -1473,6 +1481,7 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
bool is_dwz,
ULONGEST signature)
{
+ gdb_assert (section != nullptr);
auto result
= std::make_unique<signatured_type> (this, section, sect_off, length,
is_dwz, signature);
@@ -1481,6 +1490,20 @@ dwarf2_per_bfd::allocate_signatured_type (dwarf2_section_info *section,
return result;
}
+/* See read.h. */
+
+signatured_type_up
+dwarf2_per_bfd::allocate_signatured_type (ULONGEST signature)
+{
+ auto result
+ = std::make_unique<signatured_type> (this, nullptr,
+ invalid_sect_offset,
+ 0, false, signature);
+ result->index = all_units.size ();
+ this->num_type_units++;
+ return result;
+}
+
/* Subroutine of dw2_get_file_names_reader to simplify it.
Return the file name for the given file_entry.
CU_INFO describes the CU's DW_AT_name and DW_AT_comp_dir.
@@ -1764,6 +1787,20 @@ dwarf2_base_index_functions::expand_all_symtabs (struct objfile *objfile)
for (dwarf2_per_cu *per_cu : all_units_range (per_objfile->per_bfd))
{
+ /* If a .debug_names index contains a foreign TU but no index entry
+ references it, the TU won't have a hint CU. This is a problem, because
+ we won't be able to locate it. Skip them, for the following reasons:
+
+ - If they don't contain anything worthy of a named index entry, they
+ are unlikely to contain anything interesting, symbol-wise.
+ - They are likely to be referred to by some other unit (otherwise,
+ why does it exist?), so will get expanded anyway. */
+ if (signatured_type *sig_type = per_cu->as_signatured_type ();
+ (sig_type != nullptr
+ && sig_type->section () == nullptr
+ && sig_type->hint_per_cu == nullptr))
+ continue;
+
/* We don't want to directly expand a partial CU, because if we
read it with the wrong language, then assertion failures can
be triggered later on. See PR symtab/23010. So, tell
@@ -2266,8 +2303,7 @@ add_type_unit (dwarf2_per_bfd *per_bfd, dwarf2_section_info *section,
return emplace_ret.first;
}
-/* Subroutine of lookup_dwo_signatured_type and lookup_dwp_signatured_type.
- Fill in SIG_ENTRY with DWO_ENTRY. */
+/* Fill in the missing details in SIG_ENTRY from DWO_ENTRY. */
static void
fill_in_sig_entry_from_dwo_entry (dwarf2_per_objfile *per_objfile,
@@ -2723,6 +2759,10 @@ cutu_reader::cutu_reader (dwarf2_per_cu &this_cu,
{
struct objfile *objfile = per_objfile.objfile;
struct dwarf2_section_info *section = this_cu.section ();
+
+ /* Any foreign TU must have been located before getting here. */
+ gdb_assert (section != nullptr);
+
bfd *abfd = section->get_bfd_owner ();
const gdb_byte *begin_info_ptr;
struct dwarf2_section_info *abbrev_section;
@@ -3321,6 +3361,7 @@ cooked_index_worker_debug_info::do_reading ()
dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd;
create_all_units (m_per_objfile);
+ finalize_all_units (m_per_objfile->per_bfd);
process_type_units (m_per_objfile, &m_index_storage);
if (!per_bfd->debug_aranges.empty ())
@@ -3402,15 +3443,9 @@ read_comp_units_from_section (dwarf2_per_objfile *per_objfile,
/* See read.h. */
void
-finalize_all_units (dwarf2_per_bfd *per_bfd)
+dwarf2_per_bfd::sort_all_units ()
{
- /* Sanity check. */
- gdb_assert (per_bfd->all_units.size ()
- == per_bfd->num_comp_units + per_bfd->num_type_units);
-
- /* Ensure that the all_units vector is in the expected order for
- dwarf2_find_containing_unit to be able to perform a binary search. */
- std::sort (per_bfd->all_units.begin (), per_bfd->all_units.end (),
+ std::sort (this->all_units.begin (), this->all_units.end (),
[] (const dwarf2_per_cu_up &a, const dwarf2_per_cu_up &b)
{
return all_units_less_than (*a, { b->section (),
@@ -3420,6 +3455,18 @@ finalize_all_units (dwarf2_per_bfd *per_bfd)
/* See read.h. */
+void
+finalize_all_units (dwarf2_per_bfd *per_bfd)
+{
+ /* Sanity check. */
+ gdb_assert (per_bfd->all_units.size ()
+ == per_bfd->num_comp_units + per_bfd->num_type_units);
+
+ per_bfd->sort_all_units ();
+}
+
+/* See read.h. */
+
void
create_all_units (dwarf2_per_objfile *per_objfile)
{
@@ -3453,7 +3500,6 @@ create_all_units (dwarf2_per_objfile *per_objfile)
per_objfile->per_bfd->signatured_types = std::move (sig_types);
- finalize_all_units (per_objfile->per_bfd);
remove_all_units.disable ();
}
@@ -17113,6 +17159,77 @@ dwarf2_get_die_type (cu_offset die_offset, dwarf2_per_cu *per_cu,
return get_die_type_at_offset (die_offset_sect, per_cu, per_objfile);
}
+/* Fill in the missing details in SIG_TYPE from DWO_FILE.
+
+ Error out if there isn't a type unit with the appropriate signature in
+ DWO_FILE. */
+
+static void
+fill_in_sig_entry_from_dwo_file (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type, dwo_file &dwo_file)
+{
+ gdb_assert (sig_type.section () == nullptr);
+
+ dwo_unit *dwo_tu = dwo_file.find_tu (sig_type.signature);
+ if (dwo_tu == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unable to locate type unit with signature %s in DWO file %s "
+ "[in module %s]"),
+ hex_string (sig_type.signature), dwo_file.dwo_name.c_str (),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_entry (&per_objfile, &sig_type, dwo_tu);
+ sig_type.set_section (dwo_tu->section);
+ sig_type.set_sect_off (dwo_tu->sect_off);
+ sig_type.set_length (dwo_tu->length);
+
+ /* Setting SIG_TYPE's section invalidates the ALL_UNITS vector order,
+ re-sort it. */
+ per_objfile.per_bfd->sort_all_units ();
+}
+
+/* Fill in the missing details in SIG_TYPE from its hint CU.
+
+ Error out if we're unable to locate the .dwo file using the hint CU. */
+
+static void
+fill_in_sig_entry_from_per_cu_hint (dwarf2_per_objfile &per_objfile,
+ signatured_type &sig_type)
+{
+ gdb_assert (sig_type.section () == nullptr);
+ gdb_assert (sig_type.hint_per_cu != nullptr);
+
+ dwarf2_per_cu *hint_per_cu = sig_type.hint_per_cu;
+ dwo_unit *dwo_unit;
+
+ /* If a dwarf2_cu already exists for HINT_PER_CU, no need to build another
+ one. */
+ if (dwarf2_cu *hint_cu = per_objfile.get_cu (hint_per_cu);
+ hint_cu != nullptr)
+ dwo_unit = hint_cu->dwo_unit;
+ else
+ {
+ /* Constructing this cutu_reader will look for the .dwo file, creating
+ the dwo_file if necessary. */
+ abbrev_table_cache abbrev_table_cache;
+ cutu_reader reader (*hint_per_cu, per_objfile, nullptr, true,
+ std::nullopt, abbrev_table_cache);
+
+ /* dwo_unit is owned by the per_bfd, which outlives the reader. */
+ dwo_unit = reader.cu ()->dwo_unit;
+ }
+
+ if (dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Hint compilation unit for foreign type unit with signature %s is "
+ "not in a DWO file [in module %s]"),
+ hex_string (sig_type.signature),
+ objfile_name (per_objfile.objfile));
+
+ fill_in_sig_entry_from_dwo_file (per_objfile, sig_type,
+ *dwo_unit->dwo_file);
+}
+
/* Follow type unit SIG_TYPE referenced by SRC_DIE.
On entry *REF_CU is the CU of SRC_DIE.
On exit *REF_CU is the CU of the result.
@@ -17128,6 +17245,28 @@ follow_die_sig_1 (struct die_info *src_die, struct signatured_type *sig_type,
we can get here for DW_AT_imported_declaration where we need
the DIE not the type. */
+ /* If SIG_TYPE's section is not set, it means it's a .debug_names foreign
+ type unit for which we don't know the containing file or section yet.
+ Use the referencing CU as the hint: we know there must exist a TU with
+ the correct signature in its .dwo file, and that .dwo is already open,
+ might as well use it.
+
+ But this is not only an optimization. If a .debug_names foreign unit
+ does not have any index entry referencing it, then we don't have any
+ "hint CU" for it. The only hint we have is the referencing CU. */
+ if (sig_type->section () == nullptr)
+ {
+ if ((*ref_cu)->dwo_unit == nullptr)
+ error (_(DWARF_ERROR_PREFIX
+ "Unit referencing foreign type unit with signature %s is "
+ "not from a DWO file [in module %s]"),
+ hex_string (sig_type->signature),
+ objfile_name (per_objfile->objfile));
+
+ fill_in_sig_entry_from_dwo_file (*per_objfile, *sig_type,
+ *(*ref_cu)->dwo_unit->dwo_file);
+ }
+
dwarf2_cu *sig_cu = ensure_loaded_type_unit (sig_type, per_objfile);
if (sig_cu == nullptr)
@@ -17308,6 +17447,12 @@ load_full_type_unit (signatured_type *sig_type,
gdb_assert (sig_type->is_debug_types ());
gdb_assert (per_objfile->get_cu (sig_type) == nullptr);
+ /* If the section is not set, this is a .debug_names foreign type unit for
+ which we don't know the containing file nor section yet. For these, we
+ must have a "hint" CU to follow to find the file and section. */
+ if (sig_type->section () == nullptr)
+ fill_in_sig_entry_from_per_cu_hint (*per_objfile, *sig_type);
+
abbrev_table_cache abbrev_table_cache;
cutu_reader reader (*sig_type, *per_objfile, nullptr, false,
std::nullopt, abbrev_table_cache);
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index e4ed84b08ad9..845f50e938d3 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -108,7 +108,6 @@ struct dwarf2_per_cu
m_per_bfd (per_bfd)
{
gdb_assert (per_bfd != nullptr);
- gdb_assert (section != nullptr);
}
private:
@@ -267,9 +266,24 @@ struct dwarf2_per_cu
dwarf2_section_info *section () const
{ return m_section; }
+ /* Set the section of this unit. */
+ void set_section (dwarf2_section_info *section)
+ {
+ gdb_assert (section != nullptr);
+ gdb_assert (m_section == nullptr);
+ m_section = section;
+ }
+
sect_offset sect_off () const
{ return m_sect_off; }
+ /* Set the section offset of this unit. */
+ void set_sect_off (sect_offset sect_off)
+ {
+ gdb_assert (m_sect_off == invalid_sect_offset);
+ m_sect_off = sect_off;
+ }
+
bool is_dwz () const
{ return m_is_dwz; }
@@ -441,6 +455,12 @@ struct signatured_type : public dwarf2_per_cu
/* Containing DWO unit.
This field is valid iff per_cu.reading_dwo_directly. */
struct dwo_unit *dwo_unit = nullptr;
+
+ /* When using a .debug_names index, the section is not initially known for
+ foreign type units (aka skeletonless type units). This is a reference to
+ a CU that can be used to locate the .dwo file and section containing the
+ type unit. */
+ dwarf2_per_cu *hint_per_cu = nullptr;
};
using signatured_type_up = std::unique_ptr<signatured_type>;
@@ -711,6 +731,10 @@ struct dwarf2_per_bfd
return this->all_units[index].get ();
}
+ /* Ensure that the all_units vector is in the expected order for
+ dwarf2_find_containing_unit to be able to perform a binary search. */
+ void sort_all_units ();
+
/* Return the separate '.dwz' debug file. If there is no
.gnu_debugaltlink or .debug_sup section in the file, then the
result depends on REQUIRE: if REQUIRE is true, error out; if
@@ -745,6 +769,12 @@ struct dwarf2_per_bfd
bool is_dwz,
ULONGEST signature);
+ /* A convenience function to allocate a signatured_type. The
+ returned object has its "index" field set properly.
+
+ This one is used when only the signature is known at creation time. */
+ signatured_type_up allocate_signatured_type (ULONGEST signature);
+
/* Map all the DWARF section data needed when scanning
.debug_info. */
void map_info_sections (struct objfile *objfile);
@@ -1468,7 +1498,10 @@ extern const char *read_indirect_string_at_offset
extern void finalize_all_units (dwarf2_per_bfd *per_bfd);
-/* Create a list of all compilation units in OBJFILE. */
+/* Create a list of all compilation units in OBJFILE.
+
+ After it is done creating all units, the caller is responsible for calling
+ finalize_all_units. */
extern void create_all_units (dwarf2_per_objfile *per_objfile);
diff --git a/gdb/dwarf2/types.h b/gdb/dwarf2/types.h
index 169b0fd08678..cb8ce33940ca 100644
--- a/gdb/dwarf2/types.h
+++ b/gdb/dwarf2/types.h
@@ -31,6 +31,8 @@ DEFINE_OFFSET_TYPE (cu_offset, unsigned int);
section. */
DEFINE_OFFSET_TYPE (sect_offset, uint64_t);
+constexpr auto invalid_sect_offset = static_cast<sect_offset> (-1);
+
static inline const char *
sect_offset_str (sect_offset offset)
{
diff --git a/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
new file mode 100644
index 000000000000..be8bebed1a43
--- /dev/null
+++ b/gdb/testsuite/boards/dwarf5-fission-debug-types-debug-names.exp
@@ -0,0 +1,29 @@
+# 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/>.
+
+# A board that compiles with DWARF 5, split DWARF (fission) and type units,
+# and generates a .debug_names index using GDB.
+#
+# Example usage:
+# bash$ make check \
+# RUNTESTFLAGS='--target_board=dwarf5-fission-debug-types-debug-names'
+
+set CC_WITH_TWEAKS_FLAGS "-n"
+load_board_description "cc-with-tweaks"
+
+set_board_info debug_flags \
+ [join { "-gdwarf-5" \
+ "-gsplit-dwarf" \
+ "-fdebug-types-section" }]
--
2.53.0
prev parent reply other threads:[~2026-03-16 23:23 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-16 23:19 [PATCH 0/8] Handle foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 1/8] gdb/dwarf: refuse to produce .gdb_index when skeletonless type units are present simon.marchi
2026-03-17 12:57 ` Eli Zaretskii
2026-03-16 23:19 ` [PATCH 2/8] gdb/dwarf: move dwo_unit and dwo_file to read.h simon.marchi
2026-03-16 23:19 ` [PATCH 3/8] gdb/dwarf: move dwarf2_cu::section to cu.c simon.marchi
2026-03-16 23:19 ` [PATCH 4/8] gdb/dwarf: add dwo_file::find_tus simon.marchi
2026-03-16 23:19 ` [PATCH 5/8] gdb/dwarf: generate foreign type units in .debug_names simon.marchi
2026-03-16 23:19 ` [PATCH 6/8] gdb/dwarf: add debug output in read-debug-names.c simon.marchi
2026-03-16 23:19 ` [PATCH 7/8] gdb/dwarf: add more context to complaints in mapped_debug_names_reader::scan_one_entry simon.marchi
2026-03-16 23:19 ` simon.marchi [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260316232042.368080-9-simon.marchi@polymtl.ca \
--to=simon.marchi@polymtl.ca \
--cc=gdb-patches@sourceware.org \
--cc=simon.marchi@efficios.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox