aboutsummaryrefslogtreecommitdiff
path: root/gdb/corelow.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/corelow.c')
-rw-r--r--gdb/corelow.c1104
1 files changed, 872 insertions, 232 deletions
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 2b7a355..24b949b 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -1,6 +1,6 @@
/* Core dump and executable file functions below target vector, for GDB.
- Copyright (C) 1986-2024 Free Software Foundation, Inc.
+ Copyright (C) 1986-2025 Free Software Foundation, Inc.
This file is part of GDB.
@@ -37,7 +37,6 @@
#include "exec.h"
#include "readline/tilde.h"
#include "solib.h"
-#include "solist.h"
#include "filenames.h"
#include "progspace.h"
#include "objfiles.h"
@@ -48,17 +47,134 @@
#include "gdbsupport/pathstuff.h"
#include "gdbsupport/scoped_fd.h"
#include "gdbsupport/x86-xstate.h"
-#include "debuginfod-support.h"
-#include <unordered_map>
-#include <unordered_set>
+#include "gdbsupport/unordered_map.h"
+#include "gdbsupport/unordered_set.h"
#include "cli/cli-cmds.h"
#include "xml-tdesc.h"
#include "memtag.h"
+#include "cli/cli-style.h"
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif
+/* Forward declarations. */
+
+static void core_target_open (const char *arg, int from_tty);
+
+/* 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
+ = gdb::unordered_map<std::string, const bfd_build_id *>;
+
+ /* A type that maps a build-id to a string. */
+ using build_id_to_string_map
+ = gdb::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 = {
@@ -135,6 +251,34 @@ public:
/* 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);
+ }
+
+ /* Return a string containing the expected executable filename obtained
+ from the mapped file information within the core file. The filename
+ returned will be for the mapped file whose ELF headers are mapped at
+ the lowest address (i.e. which GDB encounters first).
+
+ If no suitable filename can be found then the returned string will be
+ empty.
+
+ If there are no build-ids embedded into the core file then the
+ returned string will be empty.
+
+ If a non-empty string is returned then there is no guarantee that the
+ named file exists on disk, or if it does exist on disk, then the
+ on-disk file might have a different build-id to the desired
+ build-id. */
+ const std::string &
+ expected_exec_filename () const
+ {
+ return m_expected_exec_filename;
+ }
+
private: /* per-core data */
/* Get rid of the core inferior. */
@@ -157,19 +301,23 @@ private: /* per-core data */
still be useful. */
std::vector<mem_range> m_core_unavailable_mappings;
- /* Build m_core_file_mappings. Called from the constructor. */
- void build_file_mappings ();
+ /* 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;
- /* Helper method for xfer_partial. */
- enum target_xfer_status xfer_memory_via_mappings (gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset,
- ULONGEST len,
- ULONGEST *xfered_len);
+ /* 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
disappear. */
struct gdbarch *m_core_gdbarch = NULL;
+
+ /* If not empty then this contains the name of the executable discovered
+ when processing the memory-mapped file information. This will only
+ be set if we find a mapped with a suitable build-id. */
+ std::string m_expected_exec_filename;
};
core_target::core_target ()
@@ -217,8 +365,50 @@ core_target::core_target ()
void
core_target::build_file_mappings ()
{
- std::unordered_map<std::string, struct bfd *> bfd_map;
- std::unordered_set<std::string> unavailable_paths;
+ /* 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. */
+ struct mapped_file
+ {
+ /* Type for a region of a file that was mapped into the inferior when
+ the core file was generated. */
+ struct region
+ {
+ /* Constructor. See member variables for argument descriptions. */
+ region (CORE_ADDR start_, CORE_ADDR end_, CORE_ADDR file_ofs_)
+ : start (start_),
+ end (end_),
+ file_ofs (file_ofs_)
+ { /* Nothing. */ }
+
+ /* The inferior address for the start of the mapped region. */
+ CORE_ADDR start;
+
+ /* The inferior address immediately after the mapped region. */
+ CORE_ADDR end;
+
+ /* The offset within the mapped file for this content. */
+ CORE_ADDR file_ofs;
+ };
+
+ /* If not nullptr, then this is the build-id associated with this
+ file. */
+ const bfd_build_id *build_id = nullptr;
+
+ /* If true then we have seen multiple different build-ids associated
+ with the same filename. The build_id field will have been set back
+ to nullptr, and we should not set build_id in future. */
+ bool ignore_build_id_p = false;
+
+ /* All the mapped regions of this file. */
+ std::vector<region> regions;
+ };
+
+ gdb::unordered_map<std::string, struct bfd *> bfd_map;
+ gdb::unordered_set<std::string> unavailable_paths;
+
+ /* All files mapped into the core file. The key is the filename. */
+ gdb::unordered_map<std::string, mapped_file> mapped_files;
/* See linux_read_core_file_mappings() in linux-tdep.c for an example
read_core_file_mappings method. */
@@ -240,87 +430,236 @@ core_target::build_file_mappings ()
weed out non-file-backed mappings. */
gdb_assert (filename != nullptr);
- if (unavailable_paths.find (filename) != unavailable_paths.end ())
+ /* 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)
{
- /* We have already seen some mapping for FILENAME but failed to
- find/open the file. There is no point in trying the same
- thing again so just record that the range [start, end) is
- unavailable. */
- m_core_unavailable_mappings.emplace_back (start, end - start);
- return;
- }
-
- struct bfd *bfd = bfd_map[filename];
- if (bfd == nullptr)
- {
- /* Use exec_file_find() to do sysroot expansion. It'll
- also strip the potential sysroot "target:" prefix. If
- there is no sysroot, an equivalent (possibly more
- canonical) pathname will be provided. */
- gdb::unique_xmalloc_ptr<char> expanded_fname
- = exec_file_find (filename, NULL);
-
- if (expanded_fname == nullptr && build_id != nullptr)
- debuginfod_exec_query (build_id->data, build_id->size,
- filename, &expanded_fname);
-
- if (expanded_fname == nullptr)
+ if (file_data.build_id == nullptr)
+ file_data.build_id = build_id;
+ else if (!build_id_equal (build_id, file_data.build_id))
{
- m_core_unavailable_mappings.emplace_back (start, end - start);
- unavailable_paths.insert (filename);
- warning (_("Can't open file %s during file-backed mapping "
- "note processing"),
- filename);
- return;
+ 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;
}
+ }
+ });
- bfd = bfd_openr (expanded_fname.get (), "binary");
+ /* Get the build-id of the core file. */
+ const bfd_build_id *core_build_id
+ = build_id_bfd_get (current_program_space->core_bfd ());
- if (bfd == nullptr || !bfd_check_format (bfd, bfd_object))
- {
- m_core_unavailable_mappings.emplace_back (start, end - start);
- unavailable_paths.insert (filename);
- warning (_("Can't open file %s which was expanded to %s "
+ for (const auto &iter : mapped_files)
+ {
+ const std::string &filename = iter.first;
+ const mapped_file &file_data = iter.second;
+
+ /* 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. */
+ if (file_data.build_id != nullptr
+ && m_expected_exec_filename.empty ()
+ && build_id_equal (file_data.build_id, core_build_id))
+ m_expected_exec_filename = filename;
+
+ /* Use exec_file_find() to do sysroot expansion. It'll
+ also strip the potential sysroot "target:" prefix. If
+ there is no sysroot, an equivalent (possibly more
+ canonical) pathname will be provided. */
+ gdb::unique_xmalloc_ptr<char> expanded_fname
+ = exec_file_find (filename.c_str (), nullptr);
+
+ bool build_id_mismatch = false;
+ if (expanded_fname != nullptr && file_data.build_id != nullptr)
+ {
+ /* We temporarily open the bfd as a structured target, this
+ allows us to read the build-id from the bfd if there is one.
+ For this task it's OK if we reuse an already open bfd object,
+ so we make this call through GDB's bfd cache. Once we've
+ checked the build-id (if there is one) we'll drop this
+ reference and re-open the bfd using the "binary" target. */
+ gdb_bfd_ref_ptr tmp_bfd
+ = gdb_bfd_open (expanded_fname.get (), gnutarget);
+
+ if (tmp_bfd != nullptr
+ && bfd_check_format (tmp_bfd.get (), bfd_object)
+ && build_id_bfd_get (tmp_bfd.get ()) != nullptr)
+ {
+ /* The newly opened TMP_BFD has a build-id, and this mapped
+ file has a build-id extracted from the core-file. Check
+ the build-id's match, and if not, reject TMP_BFD. */
+ const struct bfd_build_id *found
+ = build_id_bfd_get (tmp_bfd.get ());
+ if (!build_id_equal (found, file_data.build_id))
+ build_id_mismatch = true;
+ }
+ }
+
+ gdb_bfd_ref_ptr abfd;
+ if (expanded_fname != nullptr && !build_id_mismatch)
+ {
+ struct bfd *b = bfd_openr (expanded_fname.get (), "binary");
+ abfd = gdb_bfd_ref_ptr::new_reference (b);
+ }
+
+ if ((expanded_fname == nullptr
+ || abfd == nullptr
+ || !bfd_check_format (abfd.get (), bfd_object))
+ && file_data.build_id != nullptr)
+ {
+ abfd = find_objfile_by_build_id (current_program_space,
+ file_data.build_id,
+ filename.c_str ());
+
+ if (abfd != nullptr)
+ {
+ /* The find_objfile_by_build_id will have opened ABFD using
+ the GNUTARGET global bfd type, however, we need the bfd
+ opened as the binary type (see the function's header
+ comment), so now we reopen ABFD with the desired binary
+ type. */
+ expanded_fname
+ = make_unique_xstrdup (bfd_get_filename (abfd.get ()));
+ struct bfd *b = bfd_openr (expanded_fname.get (), "binary");
+ gdb_assert (b != nullptr);
+ abfd = gdb_bfd_ref_ptr::new_reference (b);
+ }
+ }
+
+ std::vector<mem_range> ranges;
+ for (const mapped_file::region &region : 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))
+ {
+ /* If ABFD was opened, but the wrong format, close it now. */
+ abfd = nullptr;
+
+ /* When true, this indicates that the mapped contents of this
+ file are available within the core file. When false, some of
+ the mapped contents are not available. If the contents are
+ entirely available within the core file, then we don't need to
+ warn the user if GDB cannot find the file. */
+ bool content_is_in_core_file_p = true;
+
+ /* Record all regions for this file as unavailable. */
+ for (const mapped_file::region &region : file_data.regions)
+ {
+ /* Check to see if the region is available within the core
+ file. */
+ bool found_region_in_core_file = false;
+ for (const target_section &ts : m_core_section_table)
+ {
+ if (ts.addr <= region.start && ts.endaddr >= region.end
+ && (ts.the_bfd_section->flags & SEC_HAS_CONTENTS) != 0)
+ {
+ found_region_in_core_file = true;
+ break;
+ }
+ }
+
+ /* This region is not available within the core file.
+ Without the file available to read from it is not possible
+ for GDB to read this mapping within the inferior. Warn
+ the user about this case. */
+ if (!found_region_in_core_file)
+ content_is_in_core_file_p = false;
+
+ /* Record the unavailable region. */
+ m_core_unavailable_mappings.emplace_back (region.start,
+ region.end
+ - region.start);
+ }
+
+ /* And give the user an appropriate warning. */
+ if (build_id_mismatch)
+ {
+ if (expanded_fname == nullptr
+ || filename == expanded_fname.get ())
+ warning (_("File %ps doesn't match build-id from core-file "
+ "during file-backed mapping processing"),
+ styled_string (file_name_style.style (),
+ filename.c_str ()));
+ else
+ warning (_("File %ps which was expanded to %ps, doesn't match "
+ "build-id from core-file during file-backed "
+ "mapping processing"),
+ styled_string (file_name_style.style (),
+ filename.c_str ()),
+ styled_string (file_name_style.style (),
+ expanded_fname.get ()));
+ }
+ else if (!content_is_in_core_file_p)
+ {
+ if (expanded_fname == nullptr
+ || filename == expanded_fname.get ())
+ warning (_("Can't open file %ps during file-backed mapping "
+ "note processing"),
+ styled_string (file_name_style.style (),
+ filename.c_str ()));
+ else
+ warning (_("Can't open file %ps which was expanded to %ps "
"during file-backed mapping note processing"),
- filename, expanded_fname.get ());
+ styled_string (file_name_style.style (),
+ filename.c_str ()),
+ styled_string (file_name_style.style (),
+ expanded_fname.get ()));
+ }
+ }
+ else
+ {
+ /* Ensure that the bfd will be closed when core_bfd is closed.
+ This can be checked before/after a core file detach via "maint
+ info bfds". */
+ gdb_bfd_record_inclusion (current_program_space->core_bfd (),
+ abfd.get ());
+
+ /* Create sections for each mapped region. */
+ for (const mapped_file::region &region : file_data.regions)
+ {
+ /* Make new BFD section. All sections have the same name,
+ which is permitted by bfd_make_section_anyway(). */
+ asection *sec = bfd_make_section_anyway (abfd.get (), "load");
+ if (sec == nullptr)
+ error (_("Can't make section"));
+ sec->filepos = region.file_ofs;
+ bfd_set_section_flags (sec, SEC_READONLY | SEC_HAS_CONTENTS);
+ bfd_set_section_size (sec, region.end - region.start);
+ bfd_set_section_vma (sec, region.start);
+ bfd_set_section_lma (sec, region.start);
+ bfd_set_section_alignment (sec, 2);
+
+ /* Set target_section fields. */
+ m_core_file_mappings.emplace_back (region.start, region.end, sec);
+ }
+ }
- if (bfd != nullptr)
- bfd_close (bfd);
- return;
- }
- /* Ensure that the bfd will be closed when core_bfd is closed.
- This can be checked before/after a core file detach via
- "maint info bfds". */
- gdb_bfd_record_inclusion (current_program_space->core_bfd (), bfd);
- bfd_map[filename] = bfd;
- }
+ /* 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)
+ {
+ normalize_mem_ranges (&ranges);
- /* Make new BFD section. All sections have the same name,
- which is permitted by bfd_make_section_anyway(). */
- asection *sec = bfd_make_section_anyway (bfd, "load");
- if (sec == nullptr)
- error (_("Can't make section"));
- sec->filepos = file_ofs;
- bfd_set_section_flags (sec, SEC_READONLY | SEC_HAS_CONTENTS);
- bfd_set_section_size (sec, end - start);
- bfd_set_section_vma (sec, start);
- bfd_set_section_lma (sec, start);
- bfd_set_section_alignment (sec, 2);
-
- /* Set target_section fields. */
- m_core_file_mappings.emplace_back (start, end, sec);
-
- /* If this is a bfd of a shared library, record its soname
- and build id. */
- if (build_id != nullptr)
- {
- gdb::unique_xmalloc_ptr<char> soname
- = gdb_bfd_read_elf_soname (bfd->filename);
- if (soname != nullptr)
- set_cbfd_soname_build_id (current_program_space->cbfd,
- soname.get (), build_id);
- }
- });
+ 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);
+ }
+
+ m_mapped_file_info.add (soname.get (), filename.c_str (),
+ actual_filename, std::move (ranges),
+ file_data.build_id);
+ }
+ }
normalize_mem_ranges (&m_core_unavailable_mappings);
}
@@ -464,7 +803,7 @@ rename_vmcore_idle_reg_sections (bfd *abfd, inferior *inf)
/* The set of all /NN numbers found. Needed so we can easily find unused
numbers in the case that we need to rename some sections. */
- std::unordered_set<int> all_lwpids;
+ gdb::unordered_set<int> all_lwpids;
/* A count of how many sections called .reg/0 we have found. */
unsigned zero_lwpid_count = 0;
@@ -561,38 +900,137 @@ rename_vmcore_idle_reg_sections (bfd *abfd, inferior *inf)
replacement_lwpid_str.c_str ());
}
+/* Use CTX to try and find (and open) the executable file for the core file
+ CBFD. BUILD_ID is the build-id for CBFD which was already extracted by
+ our caller.
+
+ Will return the opened executable or nullptr if the executable couldn't
+ be found. */
+
+static gdb_bfd_ref_ptr
+locate_exec_from_corefile_exec_context (bfd *cbfd,
+ const bfd_build_id *build_id,
+ const core_file_exec_context &ctx)
+{
+ /* CTX must be valid, and a valid context has an execfn() string. */
+ gdb_assert (ctx.valid ());
+ gdb_assert (ctx.execfn () != nullptr);
+
+ /* EXEC_NAME will be the command used to start the inferior. This might
+ not be an absolute path (but could be). */
+ const char *exec_name = ctx.execfn ();
+
+ /* Function to open FILENAME and check if its build-id matches BUILD_ID
+ from this enclosing scope. Returns the open BFD for filename if the
+ FILENAME has a matching build-id, otherwise, returns nullptr. */
+ const auto open_and_check_build_id
+ = [&build_id] (const char *filename) -> gdb_bfd_ref_ptr
+ {
+ /* Try to open a file. If this succeeds then we still need to perform
+ a build-id check. */
+ gdb_bfd_ref_ptr execbfd = gdb_bfd_open (filename, gnutarget);
+
+ /* We managed to open a file, but if it's build-id doesn't match
+ BUILD_ID then we just cannot trust it's the right file. */
+ if (execbfd != nullptr)
+ {
+ const bfd_build_id *other_build_id = build_id_bfd_get (execbfd.get ());
+
+ if (other_build_id == nullptr
+ || !build_id_equal (other_build_id, build_id))
+ execbfd = nullptr;
+ }
+
+ return execbfd;
+ };
+
+ gdb_bfd_ref_ptr execbfd;
+
+ /* If EXEC_NAME is absolute then try to open it now. Otherwise, see if
+ EXEC_NAME is a relative path from the location of the core file. This
+ is just a guess, the executable might not be here, but we still rely
+ on a build-id match in order to accept any executable we find; we
+ don't accept something just because it happens to be in the right
+ location. */
+ if (IS_ABSOLUTE_PATH (exec_name))
+ execbfd = open_and_check_build_id (exec_name);
+ else
+ {
+ std::string p = (gdb_ldirname (bfd_get_filename (cbfd))
+ + '/'
+ + exec_name);
+ execbfd = open_and_check_build_id (p.c_str ());
+ }
+
+ /* If we haven't found the executable yet, then try checking to see if
+ the executable is in the same directory as the core file. Again,
+ there's no reason why this should be the case, but it's worth a try,
+ and the build-id check should ensure we don't use an invalid file if
+ we happen to find one. */
+ if (execbfd == nullptr)
+ {
+ const char *base_name = lbasename (exec_name);
+ std::string p = (gdb_ldirname (bfd_get_filename (cbfd))
+ + '/'
+ + base_name);
+ execbfd = open_and_check_build_id (p.c_str ());
+ }
+
+ /* If the above didn't provide EXECBFD then try the exec_filename from
+ the context. This will be an absolute filename which the gdbarch code
+ figured out from the core file. In some cases the gdbarch code might
+ not be able to figure out a suitable absolute filename though. */
+ if (execbfd == nullptr && ctx.exec_filename () != nullptr)
+ {
+ gdb_assert (IS_ABSOLUTE_PATH (ctx.exec_filename ()));
+
+ /* Try to open a file. If this succeeds then we still need to
+ perform a build-id check. */
+ execbfd = open_and_check_build_id (ctx.exec_filename ());
+ }
+
+ return execbfd;
+}
+
/* Locate (and load) an executable file (and symbols) given the core file
BFD ABFD. */
static void
-locate_exec_from_corefile_build_id (bfd *abfd, int from_tty)
+locate_exec_from_corefile_build_id (bfd *abfd,
+ core_target *target,
+ const core_file_exec_context &ctx,
+ int from_tty)
{
const bfd_build_id *build_id = build_id_bfd_get (abfd);
if (build_id == nullptr)
return;
- gdb_bfd_ref_ptr execbfd
- = build_id_to_exec_bfd (build_id->size, build_id->data);
+ gdb_bfd_ref_ptr execbfd;
+
+ if (ctx.valid ())
+ execbfd = locate_exec_from_corefile_exec_context (abfd, build_id, ctx);
if (execbfd == nullptr)
{
- /* Attempt to query debuginfod for the executable. */
- gdb::unique_xmalloc_ptr<char> execpath;
- scoped_fd fd = debuginfod_exec_query (build_id->data, build_id->size,
- abfd->filename, &execpath);
+ /* The filename used for the find_objfile_by_build_id call. */
+ std::string filename;
- if (fd.get () >= 0)
+ if (!target->expected_exec_filename ().empty ())
+ filename = target->expected_exec_filename ();
+ else
{
- execbfd = gdb_bfd_open (execpath.get (), gnutarget);
-
- if (execbfd == nullptr)
- warning (_("\"%s\" from debuginfod cannot be opened as bfd: %s"),
- execpath.get (),
- gdb_bfd_errmsg (bfd_get_error (), nullptr).c_str ());
- else if (!build_id_verify (execbfd.get (), build_id->size,
- build_id->data))
- execbfd.reset (nullptr);
+ /* We didn't find an executable name from the mapped file
+ information, so as a stand-in build a string based on the
+ build-id. */
+ std::string build_id_hex_str
+ = bin2hex (build_id->data, build_id->size);
+ filename
+ = string_printf ("with build-id %s", build_id_hex_str.c_str ());
}
+
+ execbfd
+ = find_objfile_by_build_id (current_program_space, build_id,
+ filename.c_str ());
}
if (execbfd != nullptr)
@@ -603,18 +1041,20 @@ locate_exec_from_corefile_build_id (bfd *abfd, int from_tty)
}
}
-/* See gdbcore.h. */
+/* Open and set up the core file bfd. */
-void
+static void
core_target_open (const char *arg, int from_tty)
{
- const char *p;
int siggy;
int scratch_chan;
int flags;
target_preopen (from_tty);
- if (!arg)
+
+ std::string filename = extract_single_filename_arg (arg);
+
+ if (filename.empty ())
{
if (current_program_space->core_bfd ())
error (_("No core file specified. (Use `detach' "
@@ -623,25 +1063,23 @@ core_target_open (const char *arg, int from_tty)
error (_("No core file specified."));
}
- gdb::unique_xmalloc_ptr<char> filename (tilde_expand (arg));
- if (strlen (filename.get ()) != 0
- && !IS_ABSOLUTE_PATH (filename.get ()))
- filename = make_unique_xstrdup (gdb_abspath (filename).c_str ());
+ if (!IS_ABSOLUTE_PATH (filename.c_str ()))
+ filename = gdb_abspath (filename);
flags = O_BINARY | O_LARGEFILE;
if (write_files)
flags |= O_RDWR;
else
flags |= O_RDONLY;
- scratch_chan = gdb_open_cloexec (filename.get (), flags, 0).release ();
+ scratch_chan = gdb_open_cloexec (filename.c_str (), flags, 0).release ();
if (scratch_chan < 0)
- perror_with_name (filename.get ());
+ perror_with_name (filename.c_str ());
- gdb_bfd_ref_ptr temp_bfd (gdb_bfd_fopen (filename.get (), gnutarget,
+ gdb_bfd_ref_ptr temp_bfd (gdb_bfd_fopen (filename.c_str (), gnutarget,
write_files ? FOPEN_RUB : FOPEN_RB,
scratch_chan));
if (temp_bfd == NULL)
- perror_with_name (filename.get ());
+ perror_with_name (filename.c_str ());
if (!bfd_check_format (temp_bfd.get (), bfd_core))
{
@@ -650,7 +1088,7 @@ core_target_open (const char *arg, int from_tty)
thing, on error it does not free all the storage associated
with the bfd). */
error (_("\"%s\" is not a core dump: %s"),
- filename.get (), bfd_errmsg (bfd_get_error ()));
+ filename.c_str (), bfd_errmsg (bfd_get_error ()));
}
current_program_space->cbfd = std::move (temp_bfd);
@@ -662,13 +1100,6 @@ core_target_open (const char *arg, int from_tty)
validate_files ();
- /* If we have no exec file, try to set the architecture from the
- core file. We don't do this unconditionally since an exec file
- typically contains more information that helps us determine the
- architecture than a core file. */
- if (!current_program_space->exec_bfd ())
- set_gdbarch_from_file (current_program_space->core_bfd ());
-
current_inferior ()->push_target (std::move (target_holder));
switch_to_no_thread ();
@@ -723,11 +1154,33 @@ core_target_open (const char *arg, int from_tty)
switch_to_thread (thread);
}
+ /* In order to parse the exec context from the core file the current
+ inferior needs to have a suitable gdbarch set. If an exec file is
+ loaded then the gdbarch will have been set based on the exec file, but
+ if not, ensure we have a suitable gdbarch in place now. */
+ if (current_program_space->exec_bfd () == nullptr)
+ current_inferior ()->set_arch (target->core_gdbarch ());
+
+ /* See if the gdbarch can find the executable name and argument list from
+ the core file. */
+ core_file_exec_context ctx
+ = gdbarch_core_parse_exec_context (target->core_gdbarch (),
+ current_program_space->core_bfd ());
+
+ /* If we don't have an executable loaded then see if we can locate one
+ based on the core file. */
if (current_program_space->exec_bfd () == nullptr)
locate_exec_from_corefile_build_id (current_program_space->core_bfd (),
- from_tty);
+ target, ctx, from_tty);
+
+ /* If we have no exec file, try to set the architecture from the
+ core file. We don't do this unconditionally since an exec file
+ typically contains more information that helps us determine the
+ architecture than a core file. */
+ if (current_program_space->exec_bfd () == nullptr)
+ set_gdbarch_from_file (current_program_space->core_bfd ());
- post_create_inferior (from_tty);
+ post_create_inferior (from_tty, true);
/* Now go through the target stack looking for threads since there
may be a thread_stratum target loaded on top of target core by
@@ -743,9 +1196,33 @@ core_target_open (const char *arg, int from_tty)
exception_print (gdb_stderr, except);
}
- p = bfd_core_file_failing_command (current_program_space->core_bfd ());
- if (p)
- gdb_printf (_("Core was generated by `%s'.\n"), p);
+ if (ctx.valid ())
+ {
+ /* Copy the arguments into the inferior. */
+ std::vector<char *> argv;
+ for (const gdb::unique_xmalloc_ptr<char> &a : ctx.args ())
+ argv.push_back (a.get ());
+ gdb::array_view<char * const> view (argv.data (), argv.size ());
+ current_inferior ()->set_args (view, true);
+
+ /* And now copy the environment. */
+ current_inferior ()->environment = ctx.environment ();
+
+ /* Inform the user of executable and arguments. */
+ const std::string &args = current_inferior ()->args ();
+ gdb_printf (_("Core was generated by `%ps%s%s'.\n"),
+ styled_string (file_name_style.style (),
+ ctx.execfn ()),
+ (args.length () > 0 ? " " : ""), args.c_str ());
+ }
+ else
+ {
+ const char *failing_command
+ = bfd_core_file_failing_command (current_program_space->core_bfd ());
+ if (failing_command != nullptr)
+ gdb_printf (_("Core was generated by `%s'.\n"),
+ failing_command);
+ }
/* Clearing any previous state of convenience variables. */
clear_exit_convenience_vars ();
@@ -964,55 +1441,6 @@ core_target::files_info ()
print_section_info (&m_core_section_table, current_program_space->core_bfd ());
}
-/* Helper method for core_target::xfer_partial. */
-
-enum target_xfer_status
-core_target::xfer_memory_via_mappings (gdb_byte *readbuf,
- const gdb_byte *writebuf,
- ULONGEST offset, ULONGEST len,
- ULONGEST *xfered_len)
-{
- enum target_xfer_status xfer_status;
-
- xfer_status = (section_table_xfer_memory_partial
- (readbuf, writebuf,
- offset, len, xfered_len,
- m_core_file_mappings));
-
- if (xfer_status == TARGET_XFER_OK || m_core_unavailable_mappings.empty ())
- return xfer_status;
-
- /* There are instances - e.g. when debugging within a docker
- container using the AUFS storage driver - where the pathnames
- obtained from the note section are incorrect. Despite the path
- being wrong, just knowing the start and end addresses of the
- mappings is still useful; we can attempt an access of the file
- stratum constrained to the address ranges corresponding to the
- unavailable mappings. */
-
- ULONGEST memaddr = offset;
- ULONGEST memend = offset + len;
-
- for (const auto &mr : m_core_unavailable_mappings)
- {
- if (mr.contains (memaddr))
- {
- if (!mr.contains (memend))
- len = mr.start + mr.length - memaddr;
-
- xfer_status = this->beneath ()->xfer_partial (TARGET_OBJECT_MEMORY,
- NULL,
- readbuf,
- writebuf,
- offset,
- len,
- xfered_len);
- break;
- }
- }
-
- return xfer_status;
-}
enum target_xfer_status
core_target::xfer_partial (enum target_object object, const char *annex,
@@ -1040,26 +1468,72 @@ core_target::xfer_partial (enum target_object object, const char *annex,
if (xfer_status == TARGET_XFER_OK)
return TARGET_XFER_OK;
- /* Check file backed mappings. If they're available, use
- core file provided mappings (e.g. from .note.linuxcore.file
- or the like) as this should provide a more accurate
- result. If not, check the stratum beneath us, which should
- be the file stratum.
+ /* Check file backed mappings. If they're available, use core file
+ provided mappings (e.g. from .note.linuxcore.file or the like)
+ as this should provide a more accurate result. */
+ if (!m_core_file_mappings.empty ())
+ {
+ xfer_status = section_table_xfer_memory_partial
+ (readbuf, writebuf, offset, len, xfered_len,
+ m_core_file_mappings);
+ if (xfer_status == TARGET_XFER_OK)
+ return xfer_status;
+ }
- We also check unavailable mappings due to Docker/AUFS driver
- issues. */
- if (!m_core_file_mappings.empty ()
- || !m_core_unavailable_mappings.empty ())
+ /* If the access is within an unavailable file mapping then we try
+ to check in the stratum below (the executable stratum). The
+ thinking here is that if the mapping was read/write then the
+ contents would have been written into the core file and the
+ access would have been satisfied by m_core_section_table.
+
+ But if the access has not yet been resolved then we can assume
+ the access is read-only. If the executable was not found
+ during the mapped file check then we'll have an unavailable
+ mapping entry, however, if the user has provided the executable
+ (maybe in a different location) then we might be able to
+ resolve the access from there.
+
+ If that fails, but the access is within an unavailable region,
+ then the access itself should fail. */
+ for (const auto &mr : m_core_unavailable_mappings)
{
- xfer_status = xfer_memory_via_mappings (readbuf, writebuf, offset,
+ if (mr.contains (offset))
+ {
+ if (!mr.contains (offset + len))
+ len = mr.start + mr.length - offset;
+
+ xfer_status
+ = this->beneath ()->xfer_partial (TARGET_OBJECT_MEMORY,
+ nullptr, readbuf,
+ writebuf, offset,
len, xfered_len);
+ if (xfer_status == TARGET_XFER_OK)
+ return TARGET_XFER_OK;
+
+ return TARGET_XFER_E_IO;
+ }
+ }
+
+ /* The following is acting as a fallback in case we encounter a
+ situation where the core file is lacking and mapped file
+ information. Here we query the exec file stratum to see if it
+ can resolve the access. Doing this when we are missing mapped
+ file information might be the best we can do, but there are
+ certainly cases this will get wrong, e.g. if an inferior created
+ a zero initialised mapping over the top of some data that exists
+ within the executable then this will return the executable data
+ rather than the zero data. Maybe we should just drop this
+ block? */
+ if (m_core_file_mappings.empty ()
+ && m_core_unavailable_mappings.empty ())
+ {
+ xfer_status
+ = this->beneath ()->xfer_partial (object, annex, readbuf,
+ writebuf, offset, len,
+ xfered_len);
+ if (xfer_status == TARGET_XFER_OK)
+ return TARGET_XFER_OK;
}
- else
- xfer_status = this->beneath ()->xfer_partial (object, annex, readbuf,
- writebuf, offset, len,
- xfered_len);
- if (xfer_status == TARGET_XFER_OK)
- return TARGET_XFER_OK;
/* Finally, attempt to access data in core file sections with
no contents. These will typically read as all zero. */
@@ -1224,7 +1698,7 @@ core_target::xfer_partial (enum target_object object, const char *annex,
exactly lively, are they? On the other hand, if we don't claim
that each & every one is alive, then we don't get any of them
to appear in an "info thread" command, which is quite a useful
- behaviour.
+ behavior.
*/
bool
core_target::thread_alive (ptid_t ptid)
@@ -1454,24 +1928,19 @@ get_current_core_target ()
void
core_target::info_proc_mappings (struct gdbarch *gdbarch)
{
- if (!m_core_file_mappings.empty ())
- {
- gdb_printf (_("Mapped address spaces:\n\n"));
- if (gdbarch_addr_bit (gdbarch) == 32)
- {
- gdb_printf ("\t%10s %10s %10s %10s %s\n",
- "Start Addr",
- " End Addr",
- " Size", " Offset", "objfile");
- }
- else
- {
- gdb_printf (" %18s %18s %10s %10s %s\n",
- "Start Addr",
- " End Addr",
- " Size", " Offset", "objfile");
- }
- }
+ if (m_core_file_mappings.empty ())
+ return;
+
+ gdb_printf (_("Mapped address spaces:\n\n"));
+ ui_out_emit_table emitter (current_uiout, 5, -1, "ProcMappings");
+
+ int width = gdbarch_addr_bit (gdbarch) == 32 ? 10 : 18;
+ current_uiout->table_header (width, ui_left, "start", "Start Addr");
+ current_uiout->table_header (width, ui_left, "end", "End Addr");
+ current_uiout->table_header (width, ui_left, "size", "Size");
+ current_uiout->table_header (width, ui_left, "offset", "Offset");
+ current_uiout->table_header (0, ui_left, "objfile", "File");
+ current_uiout->table_body ();
for (const target_section &tsp : m_core_file_mappings)
{
@@ -1480,20 +1949,16 @@ core_target::info_proc_mappings (struct gdbarch *gdbarch)
ULONGEST file_ofs = tsp.the_bfd_section->filepos;
const char *filename = bfd_get_filename (tsp.the_bfd_section->owner);
- if (gdbarch_addr_bit (gdbarch) == 32)
- gdb_printf ("\t%10s %10s %10s %10s %s\n",
- paddress (gdbarch, start),
- paddress (gdbarch, end),
- hex_string (end - start),
- hex_string (file_ofs),
- filename);
- else
- gdb_printf (" %18s %18s %10s %10s %s\n",
- paddress (gdbarch, start),
- paddress (gdbarch, end),
- hex_string (end - start),
- hex_string (file_ofs),
- filename);
+ ui_out_emit_tuple tuple_emitter (current_uiout, nullptr);
+ current_uiout->field_core_addr ("start", gdbarch, start);
+ current_uiout->field_core_addr ("end", gdbarch, end);
+ /* These next two aren't really addresses and so shouldn't be
+ styled as such. */
+ current_uiout->field_string ("size", paddress (gdbarch, end - start));
+ current_uiout->field_string ("offset", paddress (gdbarch, file_ofs));
+ current_uiout->field_string ("objfile", filename,
+ file_name_style.style ());
+ current_uiout->text ("\n");
}
}
@@ -1516,11 +1981,186 @@ maintenance_print_core_file_backed_mappings (const char *args, int from_tty)
targ->info_proc_mappings (targ->core_gdbarch ());
}
-void _initialize_corelow ();
+/* 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
-_initialize_corelow ()
+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 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)
+ 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);
+}
+
+INIT_GDB_FILE (corelow)
{
- add_target (core_target_info, core_target_open, filename_completer);
+ add_target (core_target_info, core_target_open,
+ filename_maybe_quoted_completer);
add_cmd ("core-file-backed-mappings", class_maintenance,
maintenance_print_core_file_backed_mappings,
_("Print core file's file-backed mappings."),