Re: Does gdb debuginfod download libc etc.?

Andrew Burgess via Gdb
2026-03-10T14:22:49Z

Paul Smith <psmith@gnu.org> writes:

> On Mon, 2026-03-09 at 16:32 -0400, Paul Smith via Gdb wrote:
>> Ah, this is very useful.  Unfortunately it leads me to this
>> discovery;
>> the only output I get from this is:
>>
>> (gdb) core-file /data/psh13/tmp/core
>> [core-load] build_file_mappings: enter
>> [core-load] build_file_mappings: exit
>
> More debugging shows that linux_read_core_file_mappings() is returning
> early due to this:
>
> /* It's not required that the NT_FILE note exists, so return silently
> if it's not found. Beyond this point though, we'll complain
> if problems are found. */
> asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
> if (section == nullptr)
> return;
>
> (Most of) my core files are generated by the Google coredumper library,
> not by the Linux kernel, and I guess they don't have this note section.
>
> It appears that this note section isn't actually required in order for
> us to access the build ID values (after all, elfutils does it), but
> this function gives up if the section doesn't exist.
>
> I can look into how to force my cores to have this section, but I'd
> really like to teach GDB how to handle core files without it, since I
> have a lot of them and will likely continue to get them for some time
> even if I do manage to make this change to new releases.

Attached below is a **VERY** rough patch which might get things going
for you. This patch applies on gdb-17-branch and includes the previous
changes I sent, so you should probably revert them and then try this
patch to avoid conflicts.

Just to be clear what this is: this is a proof of concept. A very,
very, rough first cut. It has had minimal testing (just core file
related tests from the testsuite), and I've given no real though to
structure, performance, or improving the naming of functions or
variables as things have changed.

With that said, what's actually happening here? GDB uses the same
technique as elfutils (thanks for the info Mark) for finding the
build-ids; for every LOAD segment we treat this as if it were the start
of an ELF, and try to find the build-id. For those LOAD segments that
actually represent the start of an ELF, this will work.

As you spotted, GDB only does this if the NT_FILE note exists, that's
because we link the filename to the build-id. And historically, the
only way we could lookup the build-id for a shared library was via the
filename, so storing information about mappings without filenames was
pointless.

However, I recently reworked a lot of the code in this area, and (I
think) improved things. One case that interested me was shared
libraries with alternative names via symlinks. The filename stored in
the NT_FILE is the absolute filename, while the shared library name will
usually be the symlink name (if that's how the library was loaded).
This meant that GDB couldn't just match the shared library name to the
NT_FILE filename.

Now GDB used to try and work around this by also matching via the
libraries SONAME attribute.

But when I was working on this I ran into libraries that didn't have the
SONAME attribute, and so GDB would fail to figure out the build-id for
the shared library.

So in addition to keeping the previous lookup, I also added an address
to build-id map. Now, when libraries try to find the required build-id,
not only do we try the filename lookup, but we can also take an address
which we know is within the library, and look to see if we have a
build-id for that address, this filled the gap for libraries without a
SONAME attribute.

But, what remained is that all of the lookup data structures, the
filename to build-id, the soname to build-id, and the address to
build-id maps, are only created for mappings which are covered by
NT_FILE.

And that brings us to this patch. In the linux-tdep code, after
processing the NT_FILE (or if it's missing), I walk the segment table
looking for LOAD segments. If there was no NT_FILE entry for the same
address then I create an anonymous mapping. This mapping is passed back
to corelow.c.

In corelow.c the anonymous mapping is used to add additional entries
into the address to build-id mapping table.

Now, when we try to load a shared library, we try the filename based
lookups as before, but don't find anything. Then we try the address to
build-id lookup and now we find a match.

Anyway. Feel free to give this a go and let me know if it does what you
need. If this resolves the problems you're having then I'll clean this
code up and submit it for inclusion.

Thanks,
Andrew

---

diff --git i/gdb/corelow.c w/gdb/corelow.c
index 29eafe8bdfd..d9c19315adf 100644
--- i/gdb/corelow.c
+++ w/gdb/corelow.c
@@ -58,6 +58,29 @@
#define O_LARGEFILE 0
#endif

+/* When true emit 'core-load' debug messages. */
+
+static bool debug_core_load = false;
+
+/* Handle 'show debug core-load' command. */
+
+static void
+show_core_load_debug (struct ui_file *file, int from_tty,
+ struct cmd_list_element *c, const char *value)
+{
+ gdb_printf (file, _("Core-load debugging is %s.\n"), value);
+}
+
+/* Print a "core-load" debug statement. */
+
+#define core_load_debug_printf(fmt, ...) \
+ debug_prefixed_printf_cond (debug_core_load, "core-load", fmt, ##__VA_ARGS__)
+
+/* Print "core-load" enter/exit debug statements. */
+
+#define CORE_LOAD_SCOPED_DEBUG_ENTER_EXIT \
+ scoped_debug_enter_exit (debug_core_load, "core-load")
+
/* Forward declarations. */

static void core_target_open (const char *arg, int from_tty);
@@ -365,6 +388,8 @@ core_target::core_target ()
void
core_target::build_file_mappings ()
{
+ CORE_LOAD_SCOPED_DEBUG_ENTER_EXIT;
+
/* Type holding information about a single file mapped into the inferior
at the point when the core file was created. Associates a build-id
with the list of regions the file is mapped into. */
@@ -410,6 +435,8 @@ core_target::build_file_mappings ()
/* All files mapped into the core file. The key is the filename. */
gdb::unordered_map<std::string, mapped_file> mapped_files;

+ std::vector<mapped_file> mapped_anon_regions;
+
/* See linux_read_core_file_mappings() in linux-tdep.c for an example
read_core_file_mappings method. */
gdbarch_read_core_file_mappings (m_core_gdbarch,
@@ -426,25 +453,39 @@ core_target::build_file_mappings ()
[&] (int num, ULONGEST start, ULONGEST end, ULONGEST file_ofs,
const char *filename, const bfd_build_id *build_id)
{
- /* Architecture-specific read_core_mapping methods are expected to
- weed out non-file-backed mappings. */
- gdb_assert (filename != nullptr);

- /* Add this mapped region to the data for FILENAME. */
- mapped_file &file_data = mapped_files[filename];
- file_data.regions.emplace_back (start, end, file_ofs);
- if (build_id != nullptr && !file_data.ignore_build_id_p)
+ core_load_debug_printf ("start = 0x%s, end = 0x%s, filename = %s, build-id = %s",
+ phex_nz (start), phex_nz (end),
+ (filename == nullptr ? "NONE" : filename),
+ (build_id == nullptr
+ ? "NONE"
+ : build_id_to_string (build_id).c_str ()));
+
+ if (filename != nullptr)
{
- if (file_data.build_id == nullptr)
- file_data.build_id = build_id;
- else if (!build_id_equal (build_id, file_data.build_id))
+ /* Add this mapped region to the data for FILENAME. */
+ mapped_file &file_data = mapped_files[filename];
+ file_data.regions.emplace_back (start, end, file_ofs);
+ if (build_id != nullptr && !file_data.ignore_build_id_p)
{
- warning (_("Multiple build-ids found for %ps"),
- styled_string (file_name_style.style (), filename));
- file_data.build_id = nullptr;
- file_data.ignore_build_id_p = true;
+ if (file_data.build_id == nullptr)
+ file_data.build_id = build_id;
+ else if (!build_id_equal (build_id, file_data.build_id))
+ {
+ warning (_("Multiple build-ids found for %ps"),
+ styled_string (file_name_style.style (), filename));
+ file_data.build_id = nullptr;
+ file_data.ignore_build_id_p = true;
+ }
}
}
+ else if (build_id != nullptr)
+ {
+ mapped_file data = { .build_id = build_id };
+ gdb_assert (file_ofs == 0);
+ data.regions.emplace_back (start, end, file_ofs);
+ mapped_anon_regions.push_back (std::move (data));
+ }
});

/* Get the build-id of the core file. */
@@ -453,6 +494,8 @@ core_target::build_file_mappings ()

for (const auto &[filename, file_data] : mapped_files)
{
+ core_load_debug_printf ("checking mapped file: %s", filename.c_str ());
+
/* If this mapped file has the same build-id as was discovered for
the core-file itself, then we assume this is the main
executable. Record the filename as we can use this later. */
@@ -468,6 +511,11 @@ core_target::build_file_mappings ()
gdb::unique_xmalloc_ptr<char> expanded_fname
= exec_file_find (filename.c_str (), nullptr);

+ core_load_debug_printf ("expanded filename: %s",
+ (expanded_fname.get () == nullptr
+ ? "NONE"
+ : expanded_fname.get ()));
+
bool build_id_mismatch = false;
if (expanded_fname != nullptr && file_data.build_id != nullptr)
{
@@ -491,6 +539,11 @@ core_target::build_file_mappings ()
= build_id_bfd_get (tmp_bfd.get ());
if (!build_id_equal (found, file_data.build_id))
build_id_mismatch = true;
+
+ core_load_debug_printf ("for expanded filename, build-id is %s%s",
+ build_id_to_string (found).c_str (),
+ (build_id_mismatch
+ ? "\t[mismatched]" : ""));
}
}

@@ -522,6 +575,8 @@ core_target::build_file_mappings ()
struct bfd *b = bfd_openr (expanded_fname.get (), "binary");
gdb_assert (b != nullptr);
abfd = gdb_bfd_ref_ptr::new_reference (b);
+ core_load_debug_printf
+ ("found a suitable objfile based on build-id");
}
}

@@ -533,6 +588,8 @@ core_target::build_file_mappings ()
|| abfd == nullptr
|| !bfd_check_format (abfd.get (), bfd_object))
{
+ core_load_debug_printf ("failed to find a suitable objfile");
+
/* If ABFD was opened, but the wrong format, close it now. */
abfd = nullptr;

@@ -652,12 +709,40 @@ core_target::build_file_mappings ()
soname = gdb_bfd_read_elf_soname (actual_filename);
}

+ core_load_debug_printf ("recording file for soname lookup");
+ core_load_debug_printf (" soname = %s",
+ (soname.get () == nullptr
+ ? "NONE" : soname.get ()));
+ core_load_debug_printf (" actual filename = %s",
+ (actual_filename == nullptr
+ ? "NONE" : actual_filename));
+
m_mapped_file_info.add (soname.get (), filename.c_str (),
actual_filename, std::move (ranges),
file_data.build_id);
}
}

+ for (auto &r : mapped_anon_regions)
+ {
+ std::vector<mem_range> ranges;
+ for (const mapped_file::region &region : r.regions)
+ ranges.emplace_back (region.start, region.end - region.start);
+
+ normalize_mem_ranges (&ranges);
+
+ gdb_assert (r.build_id != nullptr);
+
+ core_load_debug_printf ("recording anon region for build-id %s",
+ build_id_to_string (r.build_id).c_str ());
+
+ m_mapped_file_info.add (nullptr, nullptr, nullptr, std::move (ranges),
+ r.build_id);
+ }
+
+ core_load_debug_printf ("expected executable name: %s",
+ m_expected_exec_filename.c_str ());
+
normalize_mem_ranges (&m_core_unavailable_mappings);
}

@@ -2026,7 +2111,6 @@ mapped_file_info::add (const char *soname,
const bfd_build_id *build_id)
{
gdb_assert (build_id != nullptr);
- gdb_assert (expected_filename != nullptr);

if (soname != nullptr)
{
@@ -2050,9 +2134,13 @@ mapped_file_info::add (const char *soname,
parsed, we group the build-id information based on the file name. As
a consequence, we should see each EXPECTED_FILENAME value exactly
once. This means that each insertion should always succeed. */
- const auto inserted
- = m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
- gdb_assert (inserted);
+ if (expected_filename != nullptr)
+ {
+ const auto inserted
+ = m_filename_to_build_id_map.emplace (expected_filename,
+ build_id).second;
+ gdb_assert (inserted);
+ }

/* Setup the reverse build-id to file name map. */
if (actual_filename != nullptr)
@@ -2164,7 +2252,16 @@ core_target_find_mapped_file (const char *filename,
if (targ == nullptr || current_program_space->cbfd.get () == nullptr)
return {};

- return targ->lookup_mapped_file_info (filename, addr);
+ std::optional <core_target_mapped_file_info> result
+ = targ->lookup_mapped_file_info (filename, addr);
+
+ core_load_debug_printf ("filename = %s, addr = %s%s",
+ (filename == nullptr ? "NONE" : filename),
+ (addr.has_value ()
+ ? core_addr_to_string_nz (addr.value ()) : "NONE"),
+ (result.has_value () ? "\t[found]" : ""));
+
+ return result;
}

INIT_GDB_FILE (corelow)
@@ -2175,4 +2272,13 @@ INIT_GDB_FILE (corelow)
maintenance_print_core_file_backed_mappings,
_("Print core file's file-backed mappings."),
&maintenanceprintlist);
+
+ /* Debug this files internals. */
+ add_setshow_boolean_cmd ("core-load", class_maintenance, &debug_core_load, _("\
+Set core-load debugging."), _("\
+Show core-load debugging."), _("\
+When on, core-load specific internal debugging is enabled."),
+ NULL,
+ show_core_load_debug,
+ &setdebuglist, &showdebuglist);
}
diff --git i/gdb/linux-tdep.c w/gdb/linux-tdep.c
index f07f9879614..27443f7fb4b 100644
--- i/gdb/linux-tdep.c
+++ w/gdb/linux-tdep.c
@@ -1136,70 +1136,7 @@ linux_read_core_file_mappings
/* Ensure that ULONGEST is big enough for reading 64-bit core files. */
static_assert (sizeof (ULONGEST) >= 8);

- /* It's not required that the NT_FILE note exists, so return silently
- if it's not found. Beyond this point though, we'll complain
- if problems are found. */
- asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
- if (section == nullptr)
- return;
-
- unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
- unsigned int addr_size = addr_size_bits / 8;
- size_t note_size = bfd_section_size (section);
-
- if (note_size < 2 * addr_size)
- {
- warning (_("malformed core note - too short for header"));
- return;
- }
-
- gdb::byte_vector contents (note_size);
- if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
- note_size))
- {
- warning (_("could not get core note contents"));
- return;
- }
-
- gdb_byte *descdata = contents.data ();
- char *descend = (char *) descdata + note_size;
-
- if (descdata[note_size - 1] != '\0')
- {
- warning (_("malformed note - does not end with \\0"));
- return;
- }
-
- ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
- descdata += addr_size;
-
- ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
- descdata += addr_size;
-
- if (note_size < 2 * addr_size + count * 3 * addr_size)
- {
- warning (_("malformed note - too short for supplied file count"));
- return;
- }
-
- char *filenames = (char *) descdata + count * 3 * addr_size;
-
- /* Make sure that the correct number of filenames exist. Complain
- if there aren't enough or are too many. */
- char *f = filenames;
- for (int i = 0; i < count; i++)
- {
- if (f >= descend)
- {
- warning (_("malformed note - filename area is too small"));
- return;
- }
- f += strnlen (f, descend - f) + 1;
- }
- /* Complain, but don't return early if the filename area is too big. */
- if (f != descend)
- warning (_("malformed note - filename area is too big"));
-
+ /* Get build-id from every loadable section that can provide one. */
const bfd_build_id *orig_build_id = cbfd->build_id;
gdb::unordered_map<ULONGEST, const bfd_build_id *> vma_map;

@@ -1216,26 +1153,145 @@ linux_read_core_file_mappings
}

cbfd->build_id = orig_build_id;
- pre_loop_cb (count);

- for (int i = 0; i < count; i++)
+ /* Vector to collect proc mappings. */
+ struct proc_mapping
+ {
+ int num;
+ ULONGEST start;
+ ULONGEST end;
+ ULONGEST file_ofs;
+ const char *filename;
+ const bfd_build_id *build_id;
+ };
+ std::vector<struct proc_mapping> proc_mappings;
+
+ /* The start address of each mapping. */
+ gdb::unordered_set<ULONGEST> mapping_start_addr_set;
+
+ /* It's not required that the NT_FILE note exists, so return silently
+ if it's not found. Beyond this point though, we'll complain
+ if problems are found. */
+ asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
+ if (section != nullptr)
{
- ULONGEST start = bfd_get (addr_size_bits, cbfd, descdata);
- descdata += addr_size;
- ULONGEST end = bfd_get (addr_size_bits, cbfd, descdata);
- descdata += addr_size;
- ULONGEST file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
- descdata += addr_size;
- char * filename = filenames;
- filenames += strlen ((char *) filenames) + 1;
- const bfd_build_id *build_id = nullptr;
- auto vma_map_it = vma_map.find (start);
+ unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
+ unsigned int addr_size = addr_size_bits / 8;
+ size_t note_size = bfd_section_size (section);

- if (vma_map_it != vma_map.end ())
- build_id = vma_map_it->second;
+ if (note_size < 2 * addr_size)
+ {
+ warning (_("malformed core note - too short for header"));
+ return;
+ }

- loop_cb (i, start, end, file_ofs, filename, build_id);
+ gdb::byte_vector contents (note_size);
+ if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
+ note_size))
+ {
+ warning (_("could not get core note contents"));
+ return;
+ }
+
+ gdb_byte *descdata = contents.data ();
+ char *descend = (char *) descdata + note_size;
+
+ if (descdata[note_size - 1] != '\0')
+ {
+ warning (_("malformed note - does not end with \\0"));
+ return;
+ }
+
+ ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
+ descdata += addr_size;
+
+ ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
+ descdata += addr_size;
+
+ if (note_size < 2 * addr_size + count * 3 * addr_size)
+ {
+ warning (_("malformed note - too short for supplied file count"));
+ return;
+ }
+
+ char *filenames = (char *) descdata + count * 3 * addr_size;
+
+ /* Make sure that the correct number of filenames exist. Complain
+ if there aren't enough or are too many. */
+ char *f = filenames;
+ for (int i = 0; i < count; i++)
+ {
+ if (f >= descend)
+ {
+ warning (_("malformed note - filename area is too small"));
+ return;
+ }
+ f += strnlen (f, descend - f) + 1;
+ }
+ /* Complain, but don't return early if the filename area is too big. */
+ if (f != descend)
+ warning (_("malformed note - filename area is too big"));
+
+ /* Collect proc mappings. */
+ for (int i = 0; i < count; i++)
+ {
+ struct proc_mapping m = { .num = i };
+ m.start = bfd_get (addr_size_bits, cbfd, descdata);
+ descdata += addr_size;
+ m.end = bfd_get (addr_size_bits, cbfd, descdata);
+ descdata += addr_size;
+ m.file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
+ descdata += addr_size;
+ m.filename = filenames;
+ filenames += strlen ((char *) filenames) + 1;
+
+ m.build_id = nullptr;
+ auto vma_map_it = vma_map.find (m.start);
+ if (vma_map_it != vma_map.end ())
+ m.build_id = vma_map_it->second;
+
+ proc_mappings.push_back (m);
+ mapping_start_addr_set.insert (m.start);
+ }
}
+
+ for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
+ {
+ /* The NUM field is never used, this should probably just be
+ removed. */
+ struct proc_mapping m = { .num = -1 };
+
+ bfd_vma start = bfd_section_vma (sec);
+
+ if (mapping_start_addr_set.contains (start))
+ continue;
+
+ m.start = start;
+ m.end = start + bfd_section_size (sec);
+ m.file_ofs = 0;
+ m.filename = nullptr;
+
+ m.build_id = nullptr;
+ auto vma_map_it = vma_map.find (m.start);
+ if (vma_map_it != vma_map.end ())
+ m.build_id = vma_map_it->second;
+
+ proc_mappings.push_back (m);
+ mapping_start_addr_set.insert (m.start);
+ }
+
+ /* Sort proc mappings. */
+ std::sort (proc_mappings.begin (), proc_mappings.end (),
+ [] (const proc_mapping &a, const proc_mapping &b)
+ {
+ return a.start < b.start;
+ });
+
+ pre_loop_cb (proc_mappings.size ());
+
+ /* Call loop_cb with sorted proc mappings. */
+ for (const auto &m : proc_mappings)
+ loop_cb (m.num, m.start, m.end, m.file_ofs, m.filename, m.build_id);
}

/* Implement "info proc mappings" for corefile CBFD. */