@@ -60,6 +60,119 @@
#define O_LARGEFILE 0
#endif
+/* A mem_range and the build-id associated with the file mapped into the
+ given range. */
+
+struct mem_range_and_build_id
+{
+ mem_range_and_build_id (mem_range &&r, const bfd_build_id *id)
+ : range (r),
+ build_id (id)
+ { /* Nothing. */ }
+
+ /* A range of memory addresses. */
+ mem_range range;
+
+ /* The build-id of the file mapped into RANGE. */
+ const bfd_build_id *build_id;
+};
+
+/* An instance of this class is created within the core_target and is used
+ to hold all the information that relating to mapped files, their address
+ ranges, and their corresponding build-ids. */
+
+struct mapped_file_info
+{
+ /* See comment on function definition. */
+
+ void add (const char *soname, const char *expected_filename,
+ const char *actual_filename, std::vector<mem_range> &&ranges,
+ const bfd_build_id *build_id);
+
+ /* See comment on function definition. */
+
+ std::optional <core_target_mapped_file_info>
+ lookup (const char *filename, const std::optional<CORE_ADDR> &addr);
+
+private:
+
+ /* Helper for ::lookup. BUILD_ID is a build-id that was found in
+ one of the data structures within this class. Lookup the
+ corresponding filename in m_build_id_to_filename_map and return a pair
+ containing the build-id and filename.
+
+ If no corresponding filename is found in m_build_id_to_filename_map
+ then the returned pair contains BUILD_ID and an empty string.
+
+ If BUILD_ID is nullptr then the returned pair contains nullptr and an
+ empty string. */
+
+ struct core_target_mapped_file_info
+ make_result (const bfd_build_id *build_id)
+ {
+ if (build_id != nullptr)
+ {
+ auto it = m_build_id_to_filename_map.find (build_id);
+ if (it != m_build_id_to_filename_map.end ())
+ return { build_id, it->second };
+ }
+
+ return { build_id, {} };
+ }
+
+ /* A type that maps a string to a build-id. */
+ using string_to_build_id_map
+ = std::unordered_map<std::string, const bfd_build_id *>;
+
+ /* A type that maps a build-id to a string. */
+ using build_id_to_string_map
+ = std::unordered_map<const bfd_build_id *, std::string>;
+
+ /* When loading a core file, the build-ids are extracted based on the
+ file backed mappings. This map associates the name of a file that was
+ mapped into the core file with the corresponding build-id. The
+ build-id pointers in this map will never be nullptr as we only record
+ files if they have a build-id. */
+
+ string_to_build_id_map m_filename_to_build_id_map;
+
+ /* Map a build-id pointer back to the name of the file that was mapped
+ into the inferior's address space. If we lookup a matching build-id
+ using either a soname or an address then this map allows us to also
+ provide a full path to a file with a matching build-id. */
+
+ build_id_to_string_map m_build_id_to_filename_map;
+
+ /* If the file that was mapped into the core file was a shared library
+ then it might have a DT_SONAME tag in its .dynamic section, this tag
+ contains the name of a shared object. When opening a shared library,
+ if it's basename appears in this map then we can use the corresponding
+ build-id.
+
+ In the rare case that two different files have the same DT_SONAME
+ value then the build-id pointer in this map will be nullptr, this
+ indicates that it's not possible to find a build-id based on the given
+ DT_SONAME value. */
+
+ string_to_build_id_map m_soname_to_build_id_map;
+
+ /* This vector maps memory ranges onto an associated build-id. The
+ ranges are those of the files mapped into the core file.
+
+ Entries in this vector must not overlap, and are sorted be increasing
+ memory address. Within each entry the build-id pointer will not be
+ nullptr.
+
+ While building this vector the entries are not sorted, they are
+ sorted once after the table has finished being built. */
+
+ std::vector<mem_range_and_build_id> m_address_to_build_id_list;
+
+ /* False if address_to_build_id_list is unsorted, otherwise true. */
+
+ bool m_address_to_build_id_list_sorted = false;
+};
+
/* The core file target. */
static const target_info core_target_info = {
@@ -136,6 +249,13 @@ class core_target final : public process_stratum_target
/* See definition. */
void info_proc_mappings (struct gdbarch *gdbarch);
+ std::optional <core_target_mapped_file_info>
+ lookup_mapped_file_info (const char *filename,
+ const std::optional<CORE_ADDR> &addr)
+ {
+ return m_mapped_file_info.lookup (filename, addr);
+ }
+
private: /* per-core data */
/* Get rid of the core inferior. */
@@ -158,7 +278,13 @@ class core_target final : public process_stratum_target
still be useful. */
std::vector<mem_range> m_core_unavailable_mappings;
- /* Build m_core_file_mappings. Called from the constructor. */
+ /* Data structure that holds information mapping filenames and address
+ ranges to the corresponding build-ids as well as the reverse build-id
+ to filename mapping. */
+ mapped_file_info m_mapped_file_info;
+
+ /* Build m_core_file_mappings and m_mapped_file_info. Called from the
+ constructor. */
void build_file_mappings ();
/* FIXME: kettenis/20031023: Eventually this field should
@@ -354,6 +480,10 @@ core_target::build_file_mappings ()
}
}
+ std::vector<mem_range> ranges;
+ for (const mapped_file::region ®ion : file_data.regions)
+ ranges.emplace_back (region.start, region.end - region.start);
+
if (expanded_fname == nullptr
|| abfd == nullptr
|| !bfd_check_format (abfd.get (), bfd_object))
@@ -430,16 +560,26 @@ core_target::build_file_mappings ()
}
}
- /* If this is a bfd of a shared library, record its soname and
- build-id. */
- if (file_data.build_id != nullptr && abfd != nullptr)
+ /* If this is a bfd with a build-id then record the filename,
+ optional soname (DT_SONAME .dynamic attribute), and the range of
+ addresses at which this bfd is mapped. This information can be
+ used to perform build-id checking when loading the shared
+ libraries. */
+ if (file_data.build_id != nullptr)
{
- gdb::unique_xmalloc_ptr<char> soname
- = gdb_bfd_read_elf_soname (bfd_get_filename (abfd.get ()));
+ normalize_mem_ranges (&ranges);
+
+ const char *actual_filename = nullptr;
+ gdb::unique_xmalloc_ptr<char> soname;
+ if (abfd != nullptr)
+ {
+ actual_filename = bfd_get_filename (abfd.get ());
+ soname = gdb_bfd_read_elf_soname (actual_filename);
+ }
- if (soname != nullptr)
- set_cbfd_soname_build_id (current_program_space->cbfd,
- soname.get (), file_data.build_id);
+ m_mapped_file_info.add (soname.get (), filename.c_str (),
+ actual_filename, std::move (ranges),
+ file_data.build_id);
}
}
@@ -1634,6 +1774,182 @@ maintenance_print_core_file_backed_mappings (const char *args, int from_tty)
targ->info_proc_mappings (targ->core_gdbarch ());
}
+/* Add more details discovered while processing the core-file's mapped file
+ information, we're building maps between filenames and the corresponding
+ build-ids, between address ranges and the corresponding build-ids, and
+ also a reverse map between build-id and the corresponding filename.
+
+ SONAME is the DT_SONAME attribute extracted from the .dynamic section of
+ a shared library that was mapped into the core file. This can be
+ nullptr if the mapped files was not a shared library, or didn't have a
+ DT_SONAME attribute.
+
+ EXPECTED_FILENAME is the name of the file that was mapped into the
+ inferior as extracted from the core file, this should never be nullptr.
+
+ ACTUAL_FILENAME is the name of the actual file GDB found to provide the
+ mapped file information, this can be nullptr if GDB failed to find a
+ suitable file. This might be different to EXPECTED_FILENAME, e.g. GDB
+ might have downloaded the file from debuginfod and so ACTUAL_FILENAME
+ will be a file in the debuginfod client cache.
+
+ RANGES is the list of memory ranges at which this file was mapped into
+ the inferior.
+
+ BUILD_ID is the build-id for this mapped file, this will never be
+ nullptr. Not every mapped file will have a build-id, but there's no
+ point calling this function if we failed to find a build-id; this
+ structure only exists so we can lookup files based on their build-id. */
+
+void
+mapped_file_info::add (const char *soname,
+ const char *expected_filename,
+ const char *actual_filename,
+ std::vector<mem_range> &&ranges,
+ const bfd_build_id *build_id)
+{
+ gdb_assert (build_id != nullptr);
+ gdb_assert (expected_filename != nullptr);
+
+ if (soname != nullptr)
+ {
+ /* If we already have an entry with this SONAME then this indicates
+ that the inferior has two files mapped into memory with different
+ file names (and most likely different build-ids), but with the
+ same DT_SONAME attribute. In this case we can't use the
+ DT_SONAME to figure out the expected build-id of a shared
+ library, so poison the entry for this SONAME by setting the entry
+ to nullptr. */
+ auto it = m_soname_to_build_id_map.find (soname);
+ if (it != m_soname_to_build_id_map.end ()
+ && it->second != nullptr
+ && !build_id_equal (it->second, build_id))
+ m_soname_to_build_id_map[soname] = nullptr;
+ else
+ m_soname_to_build_id_map[soname] = build_id;
+ }
+
+ /* When the core file is initially opened and the mapped files are
+ 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 [it, inserted]
+ = m_filename_to_build_id_map.emplace (expected_filename, build_id);
+ gdb_assert (inserted);
+
+ /* Setup the reverse build-id to file name map. */
+ if (actual_filename != nullptr)
+ m_build_id_to_filename_map.emplace (build_id, actual_filename);
+
+ /* Setup the list of memory range to build-id objects. */
+ for (mem_range &r : ranges)
+ m_address_to_build_id_list.emplace_back (std::move (r), build_id);
+
+ /* At this point the m_address_to_build_id_list is unsorted (we just
+ added some entries to the end of the list). All entries should be
+ added before any look-ups are performed, and the list is only sorted
+ when the first look-up is performed. */
+ gdb_assert (!m_address_to_build_id_list_sorted);
+}
+
+/* FILENAME is the name of a file GDB is trying to load, and ADDR is
+ (optionally) an address within the file in the inferior's address space.
+
+ Search through the information gathered from the core-file's mapped file
+ information looking for a file named FILENAME, or for a file that covers
+ ADDR. If a match is found then return the build-id for the file along
+ with the location where GDB found the mapped file.
+
+ The location of the mapped file might be the empty string if GDB was
+ unable to find the mapped file.
+
+ If no build-id can be found for FILENAME then GDB will return a pair
+ containing nullptr (for the build-id) and an empty string for the file
+ name. */
+
+std::optional <core_target_mapped_file_info>
+mapped_file_info::lookup (const char *filename,
+ const std::optional<CORE_ADDR> &addr)
+{
+ if (filename != nullptr)
+ {
+ /* If there's a matching entry in m_filename_to_build_id_map then the
+ associated build-id will not be nullptr, and can be used to
+ validate that FILENAME is correct. */
+ auto it = m_filename_to_build_id_map.find (filename);
+ if (it != m_filename_to_build_id_map.end ())
+ return make_result (it->second);
+ }
+
+ if (addr.has_value ())
+ {
+ /* On the first lookup, sort the address_to_build_id_list. */
+ if (!m_address_to_build_id_list_sorted)
+ {
+ std::sort (m_address_to_build_id_list.begin (),
+ m_address_to_build_id_list.end (),
+ [] (const mem_range_and_build_id &a,
+ const mem_range_and_build_id &b) {
+ return a.range < b.range;
+ });
+ m_address_to_build_id_list_sorted = true;
+ }
+
+ /* Look for the first entry whose range's start address is not less
+ than, or equal too, the address ADDR. If we find such an entry,
+ then the previous entry's range might contain ADDR. If it does
+ then that previous entry's build-id can be used. */
+ auto it = std::lower_bound
+ (m_address_to_build_id_list.begin (),
+ m_address_to_build_id_list.end (),
+ *addr,
+ [] (const mem_range_and_build_id &a,
+ const CORE_ADDR &b) {
+ return a.range.start <= b;
+ });
+
+ if (it != m_address_to_build_id_list.begin ())
+ {
+ --it;
+
+ if (it->range.contains (*addr))
+ return make_result (it->build_id);
+ }
+ }
+
+ if (filename != nullptr)
+ {
+ /* If the basename of FILENAME appears in m_soname_to_build_id_map
+ then when the mapped files were processed, we saw a file with a
+ DT_SONAME attribute corresponding to FILENAME, use that build-id
+ to validate FILENAME.
+
+ However, the build-id in this map might be nullptr if we saw
+ multiple mapped files with the same DT_SONAME attribute (though
+ this should be pretty rare). */
+ auto it
+ = m_soname_to_build_id_map.find (lbasename (filename));
+ if (it != m_soname_to_build_id_map.end ()
+ && it->second != nullptr)
+ return make_result (it->second);
+ }
+
+ return {};
+}
+
+/* See gdbcore.h. */
+
+std::optional <core_target_mapped_file_info>
+core_target_find_mapped_file (const char *filename,
+ std::optional<CORE_ADDR> addr)
+{
+ core_target *targ = get_current_core_target ();
+ if (targ == nullptr || current_program_space->cbfd.get () == nullptr)
+ return {};
+
+ return targ->lookup_mapped_file_info (filename, addr);
+}
+
void _initialize_corelow ();
void
_initialize_corelow ()
@@ -196,4 +196,70 @@ class thread_section_name
std::string m_storage;
};
+/* Type returned from core_target_find_mapped_file. Holds information
+ about a mapped file that was processed when a core file was initially
+ loaded. */
+struct core_target_mapped_file_info
+{
+ /* Constructor. BUILD_ID is not nullptr, and is the build-id for the
+ mapped file. FILENAME is the location of the file that GDB loaded to
+ provide the mapped file. This might be different from the name of the
+ mapped file mentioned in the core file, e.g. if GDB downloads a file
+ from debuginfod then FILENAME would point into the debuginfod client
+ cache. The FILENAME can be the empty string if GDB was unable to find
+ a file to provide the mapped file. */
+
+ core_target_mapped_file_info (const bfd_build_id *build_id,
+ const std::string filename)
+ : m_build_id (build_id),
+ m_filename (filename)
+ {
+ gdb_assert (m_build_id != nullptr);
+ }
+
+ /* The build-id for this mapped file. */
+
+ const bfd_build_id *
+ build_id () const
+ {
+ return m_build_id;
+ }
+
+ /* The file GDB used to provide this mapped file. */
+
+ const std::string &
+ filename () const
+ {
+ return m_filename;
+ }
+
+private:
+ const bfd_build_id *m_build_id = nullptr;
+ const std::string m_filename;
+};
+
+/* If the current inferior has a core_target for its process target, then
+ lookup information about a mapped file that was discovered when the
+ core file was loaded.
+
+ The FILENAME is the file we're looking for. The ADDR, if provided, is a
+ mapped address within the inferior which is known to be part of the file
+ we are looking for.
+
+ As an example, when loading shared libraries this function can be
+ called, in that case FILENAME will be the name of the shared library
+ that GDB is trying to load and ADDR will be an inferior address which is
+ part of the shared library we are looking for.
+
+ This function looks for a mapped file which matches FILENAME and/or
+ which covers ADDR and returns information about that file.
+
+ The returned information includes the name of the mapped file if known
+ and the build-id for the mapped file if known.
+
+ */
+std::optional<core_target_mapped_file_info>
+core_target_find_mapped_file (const char *filename,
+ std::optional<CORE_ADDR> addr);
+
#endif /* !defined (GDBCORE_H) */
@@ -689,6 +689,11 @@ const solib_ops solib_aix_so_ops =
solib_aix_open_symbol_file_object,
solib_aix_in_dynsym_resolve_code,
solib_aix_bfd_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ default_find_solib_addr,
};
void _initialize_solib_aix ();
@@ -665,4 +665,9 @@ const solib_ops darwin_so_ops =
open_symbol_file_object,
darwin_in_dynsym_resolve_code,
darwin_bfd_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ default_find_solib_addr,
};
@@ -914,6 +914,11 @@ const solib_ops dsbt_so_ops =
open_symbol_file_object,
dsbt_in_dynsym_resolve_code,
solib_bfd_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ default_find_solib_addr,
};
void _initialize_dsbt_solib ();
@@ -1086,4 +1086,9 @@ const solib_ops frv_so_ops =
open_symbol_file_object,
frv_in_dynsym_resolve_code,
solib_bfd_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ default_find_solib_addr,
};
@@ -3355,6 +3355,15 @@ svr4_iterate_over_objfiles_in_search_order
}
}
+/* See solib_ops::find_solib_addr in solist.h. */
+
+static std::optional<CORE_ADDR>
+svr4_find_solib_addr (solib &so)
+{
+ auto *li = gdb::checked_static_cast<lm_info_svr4 *> (so.lm_info.get ());
+ return li->l_addr_inferior;
+}
+
const struct solib_ops svr4_so_ops =
{
svr4_relocate_section_addresses,
@@ -3369,6 +3378,7 @@ const struct solib_ops svr4_so_ops =
svr4_keep_data_in_core,
svr4_update_solib_event_breakpoints,
svr4_handle_solib_event,
+ svr4_find_solib_addr,
};
void _initialize_svr4_solib ();
@@ -412,4 +412,9 @@ const solib_ops solib_target_so_ops =
solib_target_open_symbol_file_object,
solib_target_in_dynsym_resolve_code,
solib_bfd_open,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ default_find_solib_addr,
};
@@ -469,58 +469,6 @@ solib_bfd_open (const char *pathname)
return abfd;
}
-/* Mapping of a core file's shared library sonames to their respective
- build-ids. Added to the registries of core file bfds. */
-
-typedef std::unordered_map<std::string, std::string> soname_build_id_map;
-
-/* Key used to associate a soname_build_id_map to a core file bfd. */
-
-static const struct registry<bfd>::key<soname_build_id_map>
- cbfd_soname_build_id_data_key;
-
-/* See solib.h. */
-
-void
-set_cbfd_soname_build_id (gdb_bfd_ref_ptr abfd, const char *soname,
- const bfd_build_id *build_id)
-{
- gdb_assert (abfd.get () != nullptr);
- gdb_assert (soname != nullptr);
- gdb_assert (build_id != nullptr);
-
- soname_build_id_map *mapptr
- = cbfd_soname_build_id_data_key.get (abfd.get ());
-
- if (mapptr == nullptr)
- mapptr = cbfd_soname_build_id_data_key.emplace (abfd.get ());
-
- (*mapptr)[soname] = build_id_to_string (build_id);
-}
-
-/* If SONAME had a build-id associated with it in ABFD's registry by a
- previous call to set_cbfd_soname_build_id then return the build-id
- as a NULL-terminated hex string. */
-
-static gdb::unique_xmalloc_ptr<char>
-get_cbfd_soname_build_id (gdb_bfd_ref_ptr abfd, const char *soname)
-{
- if (abfd.get () == nullptr || soname == nullptr)
- return {};
-
- soname_build_id_map *mapptr
- = cbfd_soname_build_id_data_key.get (abfd.get ());
-
- if (mapptr == nullptr)
- return {};
-
- auto it = mapptr->find (lbasename (soname));
- if (it == mapptr->end ())
- return {};
-
- return make_unique_xstrdup (it->second.c_str ());
-}
-
/* Given a pointer to one of the shared objects in our list of mapped
objects, use the recorded name to open a bfd descriptor for the
object, build a section table, relocate all the section addresses
@@ -540,36 +488,60 @@ solib_map_sections (solib &so)
gdb::unique_xmalloc_ptr<char> filename (tilde_expand (so.so_name.c_str ()));
gdb_bfd_ref_ptr abfd (ops->bfd_open (filename.get ()));
- gdb::unique_xmalloc_ptr<char> build_id_hexstr
- = get_cbfd_soname_build_id (current_program_space->cbfd,
- so.so_name.c_str ());
+
+ /* If we have a core target then the core target might have some helpful
+ information (i.e. build-ids) about the shared libraries we are trying
+ to load. Grab those hints now and use the below to validate or find
+ the shared libraries.
+
+ If we don't have a core target then this will return an empty struct
+ with no hint information, we then lookup the shared library based on
+ its filename. */
+ std::optional<CORE_ADDR> solib_addr = ops->find_solib_addr (so);
+ std::optional <const core_target_mapped_file_info> mapped_file_info
+ = core_target_find_mapped_file (so.so_name.c_str (), solib_addr);
/* If we already know the build-id of this solib from a core file, verify
it matches ABFD's build-id. If there is a mismatch or the solib wasn't
found, attempt to query debuginfod for the correct solib. */
- if (build_id_hexstr.get () != nullptr)
+ if (mapped_file_info.has_value ())
{
- bool mismatch = false;
-
- if (abfd != nullptr && abfd->build_id != nullptr)
- {
- std::string build_id = build_id_to_string (abfd->build_id);
+ bool mismatch = (abfd != nullptr
+ && build_id_bfd_get (abfd.get ()) != nullptr
+ && !build_id_equal (mapped_file_info->build_id (),
+ build_id_bfd_get (abfd.get ())));
- if (build_id != build_id_hexstr.get ())
- mismatch = true;
- }
if (abfd == nullptr || mismatch)
{
- scoped_fd fd = debuginfod_exec_query (
- (const unsigned char *) build_id_hexstr.get (), 0,
- so.so_name.c_str (), &filename);
-
- if (fd.get () >= 0)
- abfd = ops->bfd_open (filename.get ());
- else if (mismatch)
- warning (_ ("Build-id of %ps does not match core file."),
- styled_string (file_name_style.style (),
- filename.get ()));
+ /* If GDB found a suitable file during the file mapping
+ processing stage then lets use that. We don't check the
+ build-id after opening this file, either this file was found
+ by build-id, in which case it's going to match, or this file
+ doesn't have a build-id, so checking tells us nothing.
+ However, if it was good enough during the mapped file
+ processing, we assume it's good enough now. */
+ if (!mapped_file_info->filename ().empty ())
+ abfd = ops->bfd_open (mapped_file_info->filename ().c_str ());
+ else
+ abfd = nullptr;
+
+ if (abfd == nullptr)
+ {
+ scoped_fd fd = debuginfod_exec_query
+ (mapped_file_info->build_id ()->data,
+ mapped_file_info->build_id ()->size,
+ so.so_name.c_str (), &filename);
+
+ if (fd.get () >= 0)
+ abfd = ops->bfd_open (filename.get ());
+ else if (mismatch)
+ {
+ warning (_ ("Build-id of %ps does not match core file."),
+ styled_string (file_name_style.style (),
+ filename.get ()));
+ abfd = nullptr;
+ }
+ }
}
}
@@ -1708,6 +1680,14 @@ remove_user_added_objfile (struct objfile *objfile)
}
}
+/* See solist.h. */
+
+std::optional<CORE_ADDR>
+default_find_solib_addr (solib &so)
+{
+ return {};
+}
+
void _initialize_solib ();
void
@@ -136,11 +136,4 @@ extern void update_solib_breakpoints (void);
extern void handle_solib_event (void);
-/* Associate SONAME with BUILD_ID in ABFD's registry so that it can be
- retrieved with get_cbfd_soname_build_id. */
-
-extern void set_cbfd_soname_build_id (gdb_bfd_ref_ptr abfd,
- const char *soname,
- const bfd_build_id *build_id);
-
#endif /* SOLIB_H */
@@ -159,6 +159,23 @@ struct solib_ops
NULL, in which case no specific preprocessing is necessary
for this target. */
void (*handle_event) (void);
+
+ /* Return an address within the inferior's address space which is known
+ to be part of SO. If there is no such address, or GDB doesn't know
+ how to figure out such an address then an empty optional is
+ returned.
+
+ The returned address can be used when loading the shared libraries
+ for a core file. GDB knows the build-ids for (some) files mapped
+ into the inferior's address space, and knows the address ranges which
+ those mapped files cover. If GDB can figure out a representative
+ address for the library then this can be used to match a library to a
+ mapped file, and thus to a build-id. GDB can then use this
+ information to help locate the shared library objfile, if the objfile
+ is not in the expected place (as defined by the shared libraries file
+ name). */
+
+ std::optional<CORE_ADDR> (*find_solib_addr) (solib &so);
};
/* A unique pointer to a so_list. */
@@ -178,4 +195,9 @@ extern gdb_bfd_ref_ptr solib_bfd_fopen (const char *pathname, int fd);
/* Find solib binary file and open it. */
extern gdb_bfd_ref_ptr solib_bfd_open (const char *in_pathname);
+/* A default implementation of the solib_ops::find_solib_addr callback.
+ This just returns an empty std::optional<CORE_ADDR> indicating GDB is
+ unable to find an address within the library SO. */
+extern std::optional<CORE_ADDR> default_find_solib_addr (solib &so);
+
#endif
@@ -43,7 +43,11 @@ set right_binfile2_lib \
set binfile1_lib [standard_output_file ${libname1}.so]
set binfile2_lib [standard_output_file ${libname2}.so]
-set lib_flags [list debug ldflags=-Wl,-Bsymbolic]
+# When this test was written, GDB's ability to track down shared
+# libraries for a core file based on the build-id much poorer. As GDB
+# has improved we now need to disable build-ids in order for this test
+# to function as expected.
+set lib_flags [list debug no-build-id ldflags=-Wl,-Bsymbolic]
set wrong_lib_flags "$lib_flags additional_flags=-DARRAY_SIZE=1"
set right_lib_flags "$lib_flags additional_flags=-DARRAY_SIZE=8192 additional_flags=-DRIGHT"
new file mode 100644
@@ -0,0 +1,39 @@
+/* Copyright 2024 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/>. */
+
+#include <stdlib.h>
+
+/* It is important that these two variables have names of the same length
+ so that the debug information in the two library versions is laid out
+ the same. If they differ then the .dynamic section might move, which
+ will trigger a different check within GDB than the one we actually want
+ to check. */
+
+#if LIB_VERSION == 1
+volatile int *library_1_var = (volatile int *) 0x12345678;
+#elif LIB_VERSION == 2
+volatile int *library_2_var = (volatile int *) 0x11223344;
+#else
+# error Unknown library version
+#endif
+
+int
+foo (void)
+{
+ /* This should trigger a core dump. */
+ abort ();
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,41 @@
+/* Copyright 2024 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/>. */
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stddef.h>
+
+/* This is in the shared library. */
+extern int foo (void);
+
+/* This is updated by the .exp file. */
+char *libname = "libfoo_2.so";
+
+int
+main (void)
+{
+ void *handle;
+ int res, tmp;
+
+ handle = dlopen (libname, RTLD_LAZY);
+ assert (handle != NULL);
+
+ res = foo ();
+
+ tmp = dlclose (handle);
+ assert (tmp == 0);
+
+ return res;
+}
new file mode 100644
@@ -0,0 +1,260 @@
+# Copyright 2024 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/>. */
+
+# This test exercises GDB's ability to validate build-ids when loading
+# shared libraries for a core file.
+#
+# The test creates two "versions" of a shared library, sets up a
+# symlink to point to one version of the library, and creates a core file.
+#
+# We then try re-loading the core file and executable and check that
+# GDB is able to correctly load the shared library. To confuse things
+# we retarget the library symlink at the other version of the library.
+#
+# After that we repeat the test, but this time deleting the symlink
+# completely.
+#
+# Then we remove the version of the library completely, at this point
+# we do expect GDB to give a warning about being unable to load the library.
+#
+# And finally, we setup debuginfod and have it serve the missing
+# library file, GDB should correctly download the library file.
+#
+# Despite this test living in the gdb.debuginfod/ directory, only the last
+# part of this test actually uses debuginfod, everything up to that point is
+# pretty generic.
+
+load_lib debuginfod-support.exp
+
+require allow_shlib_tests
+require {istarget "*-linux*"}
+require {!is_remote host}
+require {!using_fission}
+
+standard_testfile -1.c -2.c
+
+# Build two similar, but slightly different versions of the shared
+# library. Both libraries have DT_SONAME set to the generic
+# libfoo.so, we'll create a symlink with that name later.
+set library_1_filename [standard_output_file "libfoo_1.so"]
+set library_2_filename [standard_output_file "libfoo_2.so"]
+
+# The generic name for the library.
+set library_filename [standard_output_file "libfoo.so"]
+
+# When compiling a shared library the -Wl,-soname,NAME option is
+# automatically added based on the final name of the library. We want
+# to compile libfoo_1.so, but set the soname to libfoo.so. To achieve
+# this we first compile into libfoo.so, and then rename the library to
+# libfoo_1.so.
+if {[build_executable "build libfoo_1.so" $library_filename \
+ $srcfile \
+ { debug shlib build-id \
+ additional_flags=-DLIB_VERSION=1 }] == -1} {
+ return
+}
+remote_exec build "mv ${library_filename} ${library_1_filename}"
+
+# See the comment above, but this time we rename to libfoo_2.so.
+if {[build_executable "build libfoo_2.so" $library_filename \
+ $srcfile \
+ { debug shlib build-id \
+ additional_flags=-DLIB_VERSION=2 }] == -1} {
+ return
+}
+remote_exec build "mv ${library_filename} ${library_2_filename}"
+
+# Create libfoo.so symlink to the libfoo_1.so library. If this
+# symlink creation fails then we assume we can't create symlinks on
+# this host. If this succeeds then later symlink creation is required
+# to succeed, and will trigger an FAIL if it doesn't.
+set status \
+ [remote_exec build \
+ "ln -sf ${library_1_filename} ${library_filename}"]
+if {[lindex $status 0] != 0} {
+ unsupported "host does not support symbolic links"
+ return
+}
+
+# Build the executable. This links against libfoo.so, which is
+# poining at libfoo_1.so. Just to confuse things even more, this
+# executable uses dlopen to load libfoo_2.so. Weird!
+if { [build_executable "build executable" ${binfile} ${srcfile2} \
+ [list debug shlib=${library_filename} shlib_load]] == -1 } {
+ return
+}
+
+# If the board file is automatically splitting the debug information
+# into a separate file (e.g. the cc-with-gnu-debuglink.exp board) then
+# this test isn't going to work.
+clean_restart
+gdb_file_cmd $binfile
+if {$gdb_file_cmd_debug_info ne "debug"} {
+ unsupported "failed to find debug information"
+ return
+}
+if {[regexp "${testfile}.debug" $gdb_file_cmd_msg]} {
+ unsupported "debug information has been split to a separate file"
+ return
+}
+
+# Run BINFILE which will generate a corefile.
+set corefile [core_find $binfile]
+if {$corefile eq ""} {
+ untested "could not generate core file"
+ return
+}
+
+# Helper proc to load global BINFILE and then load global COREFILE.
+#
+# If EXPECT_WARNING is true then we require a warning about being
+# unable to load the shared library symbols, otherwise, EXPECT_WARNING
+# is false and we require no warning.
+#
+# If EXPECT_DOWNLOAD is true then we require a line indicating that
+# the shared library is being downloaded from debuginfod, otherwise
+# the shared library should not be downloaded.
+proc load_exec_and_core_file { expect_warning expect_download testname } {
+ with_test_prefix $testname {
+ clean_restart $::binfile
+
+ set saw_warning false
+ set saw_download false
+ set saw_generated false
+ set saw_terminated false
+
+ gdb_test_multiple "core-file $::corefile" "load core file" {
+ -re "^Core was generated by \[^\r\n\]+\r\n" {
+ set saw_generated true
+ exp_continue
+ }
+ -re "^Program terminated with signal \[^\r\n\]+\r\n" {
+ set saw_terminated true
+ exp_continue
+ }
+ -re "^warning: Can't open file \[^\r\n\]+ during file-backed mapping note processing\r\n" {
+ # Ignore warnings from the file backed mapping phase.
+ exp_continue
+ }
+ -re "^warning: Could not load shared library symbols for \[^\r\n\]+/libfoo\\.so\\.\r\n" {
+ set saw_warning true
+ exp_continue
+ }
+ -re "^Downloading executable for \[^\r\n\]+/libfoo_1\\.so\\.\\.\\.\r\n" {
+ set saw_download true
+ exp_continue
+ }
+ -re "^$::gdb_prompt $" {
+ gdb_assert { $saw_generated && $saw_terminated \
+ && $saw_warning == $expect_warning \
+ && $saw_download == $expect_download } \
+ $gdb_test_name
+ }
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+ }
+
+ # If we don't expect a warning then debug symbols from the
+ # shared library should be available. Confirm we can read a
+ # variable from the shared library. If we do expect a warning
+ # then the shared library debug symbols have not loaded, and
+ # the library variable should not be available.
+ if { !$expect_warning } {
+ gdb_test "print/x library_1_var" " = 0x12345678" \
+ "check library_1_var can be read"
+ } else {
+ gdb_test "print/x library_1_var" \
+ "^No symbol \"library_1_var\" in current context\\." \
+ "check library_1_var cannot be read"
+ }
+ }
+}
+
+# Initial test, just load the executable and core file. At this point
+# everything should load fine as everything is where we expect to find
+# it.
+load_exec_and_core_file false false \
+ "load core file, all libraries as expected"
+
+# Update libfoo.so symlink to point at the second library then reload
+# the core file. GDB should spot that the symlink points to the wrong
+# file, but should be able to figure out the correct file to load as
+# the right file will be in the mapped file list.
+set status [remote_exec build \
+ "ln -sf ${library_2_filename} ${library_filename}"]
+gdb_assert { [lindex $status 0] == 0 } \
+ "update library symlink to point to the wrong file"
+
+load_exec_and_core_file false false \
+ "load core file, symlink points to wrong file"
+
+# Remove libfoo.so symlink and reload the core file. As in the
+# previous test GDB should be able to figure out the correct file to
+# load as the correct file will still appear in the mapped file list.
+set status [remote_exec build "rm -f ${library_filename}"]
+gdb_assert { [lindex $status 0] == 0 } "remove library symlink"
+
+load_exec_and_core_file false false \
+ "load core file, symlink removed"
+
+# Remove LIBRARY_1_FILENAME. We'll now see a warning that the mapped
+# file can't be loaded (we ignore that warning), and we'll see a
+# warning that the shared library can't be loaded.
+set library_1_backup_filename ${library_1_filename}.backup
+set status \
+ [remote_exec build \
+ "mv ${library_1_filename} ${library_1_backup_filename}"]
+gdb_assert { [lindex $status 0] == 0 } \
+ "remove libfoo_1.so"
+
+load_exec_and_core_file true false \
+ "load core file, libfoo_1.so removed"
+
+# Setup a debuginfod server which can serve the original shared
+# library file.
+if {![allow_debuginfod_tests]} {
+ untested "skippig debuginfod parts of this test"
+ return
+}
+
+set server_dir [standard_output_file "debuginfod.server"]
+file mkdir $server_dir
+file rename -force $library_1_backup_filename $server_dir
+
+prepare_for_debuginfod cache db
+
+set url [start_debuginfod $db $server_dir]
+if { $url eq "" } {
+ unresolved "failed to start debuginfod server"
+ return
+}
+
+with_debuginfod_env $cache {
+ setenv DEBUGINFOD_URLS $url
+
+ save_vars { GDBFLAGS } {
+ append GDBFLAGS " -ex \"set debuginfod enabled on\""
+
+ # Reload the executable and core file. GDB should download
+ # the file libfoo_1.so using debuginfod during the mapped file
+ # phase, but should then reuse that download during the shared
+ # library phase.
+ load_exec_and_core_file false true \
+ "load core file, use debuginfod"
+ }
+}
+
+stop_debuginfod