Re: Does gdb debuginfod download libc etc.?
Andrew Burgess via Gdb <[email protected]> Tue, 10 Mar 2026 14:20:17 +0000
| Newsgroups | gmane.comp.gdb.devel |
|---|---|
| Message-ID | <[email protected]> |
Paul Smith <[email protected]> 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 ®ion : 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. */