mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-15 04:31:49 +08:00
bc13da1980
On x86_64-linux, with gcc 7.5.0 and CFLAGS/CXXFLAGS="-O0 -g -Wall" I ran into a build breaker: ... gdb/corelow.c: In member function ‘void mapped_file_info::add(const char*, const char*, const char*, std::vector<mem_range>&&, const bfd_build_id*)’: gdb/corelow.c:1822:27: error: unused variable ‘it’ [-Werror=unused-variable] const auto [it, inserted] ^ ... Fix this by dropping the variable it. Tested on x86_64-linux. Reviewed-By: Lancelot Six<lancelot.six@amd.com>
1950 lines
62 KiB
C
1950 lines
62 KiB
C
/* Core dump and executable file functions below target vector, for GDB.
|
||
|
||
Copyright (C) 1986-2024 Free Software Foundation, Inc.
|
||
|
||
This file is part of GDB.
|
||
|
||
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 "arch-utils.h"
|
||
#include <signal.h>
|
||
#include <fcntl.h>
|
||
#include "exceptions.h"
|
||
#include "frame.h"
|
||
#include "inferior.h"
|
||
#include "infrun.h"
|
||
#include "symtab.h"
|
||
#include "command.h"
|
||
#include "bfd.h"
|
||
#include "target.h"
|
||
#include "process-stratum-target.h"
|
||
#include "gdbcore.h"
|
||
#include "gdbthread.h"
|
||
#include "regcache.h"
|
||
#include "regset.h"
|
||
#include "symfile.h"
|
||
#include "exec.h"
|
||
#include "readline/tilde.h"
|
||
#include "solib.h"
|
||
#include "solist.h"
|
||
#include "filenames.h"
|
||
#include "progspace.h"
|
||
#include "objfiles.h"
|
||
#include "gdb_bfd.h"
|
||
#include "completer.h"
|
||
#include "gdbsupport/filestuff.h"
|
||
#include "build-id.h"
|
||
#include "gdbsupport/pathstuff.h"
|
||
#include "gdbsupport/scoped_fd.h"
|
||
#include "gdbsupport/x86-xstate.h"
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#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
|
||
|
||
/* 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 = {
|
||
"core",
|
||
N_("Local core dump file"),
|
||
N_("Use a core file as a target.\n\
|
||
Specify the filename of the core file.")
|
||
};
|
||
|
||
class core_target final : public process_stratum_target
|
||
{
|
||
public:
|
||
core_target ();
|
||
|
||
const target_info &info () const override
|
||
{ return core_target_info; }
|
||
|
||
void close () override;
|
||
void detach (inferior *, int) override;
|
||
void fetch_registers (struct regcache *, int) override;
|
||
|
||
enum target_xfer_status xfer_partial (enum target_object object,
|
||
const char *annex,
|
||
gdb_byte *readbuf,
|
||
const gdb_byte *writebuf,
|
||
ULONGEST offset, ULONGEST len,
|
||
ULONGEST *xfered_len) override;
|
||
void files_info () override;
|
||
|
||
bool thread_alive (ptid_t ptid) override;
|
||
const struct target_desc *read_description () override;
|
||
|
||
std::string pid_to_str (ptid_t) override;
|
||
|
||
const char *thread_name (struct thread_info *) override;
|
||
|
||
bool has_all_memory () override { return true; }
|
||
bool has_memory () override;
|
||
bool has_stack () override;
|
||
bool has_registers () override;
|
||
bool has_execution (inferior *inf) override { return false; }
|
||
|
||
bool info_proc (const char *, enum info_proc_what) override;
|
||
|
||
bool supports_memory_tagging () override;
|
||
|
||
/* Core file implementation of fetch_memtags. Fetch the memory tags from
|
||
core file notes. */
|
||
bool fetch_memtags (CORE_ADDR address, size_t len,
|
||
gdb::byte_vector &tags, int type) override;
|
||
|
||
/* If the architecture supports it, check if ADDRESS is within a memory range
|
||
mapped with tags. For example, MTE tags for AArch64. */
|
||
bool is_address_tagged (gdbarch *gdbarch, CORE_ADDR address) override;
|
||
|
||
x86_xsave_layout fetch_x86_xsave_layout () override;
|
||
|
||
/* A few helpers. */
|
||
|
||
/* Getter, see variable definition. */
|
||
struct gdbarch *core_gdbarch ()
|
||
{
|
||
return m_core_gdbarch;
|
||
}
|
||
|
||
/* See definition. */
|
||
void get_core_register_section (struct regcache *regcache,
|
||
const struct regset *regset,
|
||
const char *name,
|
||
int section_min_size,
|
||
const char *human_name,
|
||
bool required);
|
||
|
||
/* 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. */
|
||
void clear_core ();
|
||
|
||
/* The core's section table. Note that these target sections are
|
||
*not* mapped in the current address spaces' set of target
|
||
sections --- those should come only from pure executable or
|
||
shared library bfds. The core bfd sections are an implementation
|
||
detail of the core target, just like ptrace is for unix child
|
||
targets. */
|
||
std::vector<target_section> m_core_section_table;
|
||
|
||
/* File-backed address space mappings: some core files include
|
||
information about memory mapped files. */
|
||
std::vector<target_section> m_core_file_mappings;
|
||
|
||
/* Unavailable mappings. These correspond to pathnames which either
|
||
weren't found or could not be opened. Knowing these addresses can
|
||
still be useful. */
|
||
std::vector<mem_range> m_core_unavailable_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;
|
||
|
||
/* 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;
|
||
};
|
||
|
||
core_target::core_target ()
|
||
{
|
||
/* Find a first arch based on the BFD. We need the initial gdbarch so
|
||
we can setup the hooks to find a target description. */
|
||
m_core_gdbarch = gdbarch_from_bfd (current_program_space->core_bfd ());
|
||
|
||
/* If the arch is able to read a target description from the core, it
|
||
could yield a more specific gdbarch. */
|
||
const struct target_desc *tdesc = read_description ();
|
||
|
||
if (tdesc != nullptr)
|
||
{
|
||
struct gdbarch_info info;
|
||
info.abfd = current_program_space->core_bfd ();
|
||
info.target_desc = tdesc;
|
||
m_core_gdbarch = gdbarch_find_by_info (info);
|
||
}
|
||
|
||
if (!m_core_gdbarch
|
||
|| !gdbarch_iterate_over_regset_sections_p (m_core_gdbarch))
|
||
error (_("\"%s\": Core file format not supported"),
|
||
bfd_get_filename (current_program_space->core_bfd ()));
|
||
|
||
/* Find the data section */
|
||
m_core_section_table = build_section_table (current_program_space->core_bfd ());
|
||
|
||
build_file_mappings ();
|
||
}
|
||
|
||
/* Construct the table for file-backed mappings if they exist.
|
||
|
||
For each unique path in the note, we'll open a BFD with a bfd
|
||
target of "binary". This is an unstructured bfd target upon which
|
||
we'll impose a structure from the mappings in the architecture-specific
|
||
mappings note. A BFD section is allocated and initialized for each
|
||
file-backed mapping.
|
||
|
||
We take care to not share already open bfds with other parts of
|
||
GDB; in particular, we don't want to add new sections to existing
|
||
BFDs. We do, however, ensure that the BFDs that we allocate here
|
||
will go away (be deallocated) when the core target is detached. */
|
||
|
||
void
|
||
core_target::build_file_mappings ()
|
||
{
|
||
/* 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;
|
||
};
|
||
|
||
std::unordered_map<std::string, struct bfd *> bfd_map;
|
||
std::unordered_set<std::string> unavailable_paths;
|
||
|
||
/* All files mapped into the core file. The key is the filename. */
|
||
std::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. */
|
||
gdbarch_read_core_file_mappings (m_core_gdbarch,
|
||
current_program_space->core_bfd (),
|
||
|
||
/* After determining the number of mappings, read_core_file_mappings
|
||
will invoke this lambda. */
|
||
[&] (ULONGEST)
|
||
{
|
||
},
|
||
|
||
/* read_core_file_mappings will invoke this lambda for each mapping
|
||
that it finds. */
|
||
[&] (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)
|
||
{
|
||
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;
|
||
}
|
||
}
|
||
});
|
||
|
||
for (const auto &iter : mapped_files)
|
||
{
|
||
const std::string &filename = iter.first;
|
||
const mapped_file &file_data = iter.second;
|
||
|
||
/* 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 (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 ®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))
|
||
{
|
||
/* If ABFD was opened, but the wrong format, close it now. */
|
||
abfd = nullptr;
|
||
|
||
/* Record all regions for this file as unavailable. */
|
||
for (const mapped_file::region ®ion : file_data.regions)
|
||
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 (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"),
|
||
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 ®ion : 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 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);
|
||
|
||
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);
|
||
}
|
||
|
||
/* An arbitrary identifier for the core inferior. */
|
||
#define CORELOW_PID 1
|
||
|
||
void
|
||
core_target::clear_core ()
|
||
{
|
||
if (current_program_space->core_bfd () != nullptr)
|
||
{
|
||
switch_to_no_thread (); /* Avoid confusion from thread
|
||
stuff. */
|
||
exit_inferior (current_inferior ());
|
||
|
||
/* Clear out solib state while the bfd is still open. See
|
||
comments in clear_solib in solib.c. */
|
||
clear_solib (current_program_space);
|
||
|
||
current_program_space->cbfd.reset (nullptr);
|
||
}
|
||
}
|
||
|
||
/* Close the core target. */
|
||
|
||
void
|
||
core_target::close ()
|
||
{
|
||
clear_core ();
|
||
|
||
/* Core targets are heap-allocated (see core_target_open), so here
|
||
we delete ourselves. */
|
||
delete this;
|
||
}
|
||
|
||
/* Look for sections whose names start with `.reg/' so that we can
|
||
extract the list of threads in a core file. */
|
||
|
||
/* If ASECT is a section whose name begins with '.reg/' then extract the
|
||
lwpid after the '/' and create a new thread in INF.
|
||
|
||
If REG_SECT is not nullptr, and the both ASECT and REG_SECT point at the
|
||
same position in the parent bfd object then switch to the newly created
|
||
thread, otherwise, the selected thread is left unchanged. */
|
||
|
||
static void
|
||
add_to_thread_list (asection *asect, asection *reg_sect, inferior *inf)
|
||
{
|
||
if (!startswith (bfd_section_name (asect), ".reg/"))
|
||
return;
|
||
|
||
int lwpid = atoi (bfd_section_name (asect) + 5);
|
||
ptid_t ptid (inf->pid, lwpid);
|
||
thread_info *thr = add_thread (inf->process_target (), ptid);
|
||
|
||
/* Warning, Will Robinson, looking at BFD private data! */
|
||
|
||
if (reg_sect != NULL
|
||
&& asect->filepos == reg_sect->filepos) /* Did we find .reg? */
|
||
switch_to_thread (thr); /* Yes, make it current. */
|
||
}
|
||
|
||
/* Issue a message saying we have no core to debug, if FROM_TTY. */
|
||
|
||
static void
|
||
maybe_say_no_core_file_now (int from_tty)
|
||
{
|
||
if (from_tty)
|
||
gdb_printf (_("No core file now.\n"));
|
||
}
|
||
|
||
/* Backward compatibility with old way of specifying core files. */
|
||
|
||
void
|
||
core_file_command (const char *filename, int from_tty)
|
||
{
|
||
dont_repeat (); /* Either way, seems bogus. */
|
||
|
||
if (filename == NULL)
|
||
{
|
||
if (current_program_space->core_bfd () != nullptr)
|
||
{
|
||
target_detach (current_inferior (), from_tty);
|
||
gdb_assert (current_program_space->core_bfd () == nullptr);
|
||
}
|
||
else
|
||
maybe_say_no_core_file_now (from_tty);
|
||
}
|
||
else
|
||
core_target_open (filename, from_tty);
|
||
}
|
||
|
||
/* A vmcore file is a core file created by the Linux kernel at the point of
|
||
a crash. Each thread in the core file represents a real CPU core, and
|
||
the lwpid for each thread is the pid of the process that was running on
|
||
that core at the moment of the crash.
|
||
|
||
However, not every CPU core will have been running a process, some cores
|
||
will be idle. For these idle cores the CPU writes an lwpid of 0. And
|
||
of course, multiple cores might be idle, so there could be multiple
|
||
threads with an lwpid of 0.
|
||
|
||
The problem is GDB doesn't really like threads with an lwpid of 0; GDB
|
||
presents such a thread as a process rather than a thread. And GDB
|
||
certainly doesn't like multiple threads having the same lwpid, each time
|
||
a new thread is seen with the same lwpid the earlier thread (with the
|
||
same lwpid) will be deleted.
|
||
|
||
This function addresses both of these problems by assigning a fake lwpid
|
||
to any thread with an lwpid of 0.
|
||
|
||
GDB finds the lwpid information by looking at the bfd section names
|
||
which include the lwpid, e.g. .reg/NN where NN is the lwpid. This
|
||
function looks though all the section names looking for sections named
|
||
.reg/NN. If any sections are found where NN == 0, then we assign a new
|
||
unique value of NN. Then, in a second pass, any sections ending /0 are
|
||
assigned their new number.
|
||
|
||
Remember, a core file may contain multiple register sections for
|
||
different register sets, but the sets are always grouped by thread, so
|
||
we can figure out which registers should be assigned the same new
|
||
lwpid. For example, consider a core file containing:
|
||
|
||
.reg/0, .reg2/0, .reg/0, .reg2/0
|
||
|
||
This represents two threads, each thread contains a .reg and .reg2
|
||
register set. The .reg represents the start of each thread. After
|
||
renaming the sections will now look like this:
|
||
|
||
.reg/1, .reg2/1, .reg/2, .reg2/2
|
||
|
||
After calling this function the rest of the core file handling code can
|
||
treat this core file just like any other core file. */
|
||
|
||
static void
|
||
rename_vmcore_idle_reg_sections (bfd *abfd, inferior *inf)
|
||
{
|
||
/* Map from the bfd section to its lwpid (the /NN number). */
|
||
std::vector<std::pair<asection *, int>> sections_and_lwpids;
|
||
|
||
/* 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;
|
||
|
||
/* A count of how many sections called .reg/0 we have found. */
|
||
unsigned zero_lwpid_count = 0;
|
||
|
||
/* Look for all the .reg sections. Record the section object and the
|
||
lwpid which is extracted from the section name. Spot if any have an
|
||
lwpid of zero. */
|
||
for (asection *sect : gdb_bfd_sections (current_program_space->core_bfd ()))
|
||
{
|
||
if (startswith (bfd_section_name (sect), ".reg/"))
|
||
{
|
||
int lwpid = atoi (bfd_section_name (sect) + 5);
|
||
sections_and_lwpids.emplace_back (sect, lwpid);
|
||
all_lwpids.insert (lwpid);
|
||
if (lwpid == 0)
|
||
zero_lwpid_count++;
|
||
}
|
||
}
|
||
|
||
/* If every ".reg/NN" section has a non-zero lwpid then we don't need to
|
||
do any renaming. */
|
||
if (zero_lwpid_count == 0)
|
||
return;
|
||
|
||
/* Assign a new number to any .reg sections with an lwpid of 0. */
|
||
int new_lwpid = 1;
|
||
for (auto §_and_lwpid : sections_and_lwpids)
|
||
if (sect_and_lwpid.second == 0)
|
||
{
|
||
while (all_lwpids.find (new_lwpid) != all_lwpids.end ())
|
||
new_lwpid++;
|
||
sect_and_lwpid.second = new_lwpid;
|
||
new_lwpid++;
|
||
}
|
||
|
||
/* Now update the names of any sections with an lwpid of 0. This is
|
||
more than just the .reg sections we originally found. */
|
||
std::string replacement_lwpid_str;
|
||
auto iter = sections_and_lwpids.begin ();
|
||
int replacement_lwpid = 0;
|
||
for (asection *sect : gdb_bfd_sections (current_program_space->core_bfd ()))
|
||
{
|
||
if (iter != sections_and_lwpids.end () && sect == iter->first)
|
||
{
|
||
gdb_assert (startswith (bfd_section_name (sect), ".reg/"));
|
||
|
||
int lwpid = atoi (bfd_section_name (sect) + 5);
|
||
if (lwpid == iter->second)
|
||
{
|
||
/* This section was not given a new number. */
|
||
gdb_assert (lwpid != 0);
|
||
replacement_lwpid = 0;
|
||
}
|
||
else
|
||
{
|
||
replacement_lwpid = iter->second;
|
||
ptid_t ptid (inf->pid, replacement_lwpid);
|
||
if (!replacement_lwpid_str.empty ())
|
||
replacement_lwpid_str += ", ";
|
||
replacement_lwpid_str += target_pid_to_str (ptid);
|
||
}
|
||
|
||
iter++;
|
||
}
|
||
|
||
if (replacement_lwpid != 0)
|
||
{
|
||
const char *name = bfd_section_name (sect);
|
||
size_t len = strlen (name);
|
||
|
||
if (strncmp (name + len - 2, "/0", 2) == 0)
|
||
{
|
||
/* This section needs a new name. */
|
||
std::string name_str
|
||
= string_printf ("%.*s/%d",
|
||
static_cast<int> (len - 2),
|
||
name, replacement_lwpid);
|
||
char *name_buf
|
||
= static_cast<char *> (bfd_alloc (abfd, name_str.size () + 1));
|
||
if (name_buf == nullptr)
|
||
error (_("failed to allocate space for section name '%s'"),
|
||
name_str.c_str ());
|
||
memcpy (name_buf, name_str.c_str(), name_str.size () + 1);
|
||
bfd_rename_section (sect, name_buf);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (zero_lwpid_count == 1)
|
||
warning (_("found thread with pid 0, assigned replacement Target Id: %s"),
|
||
replacement_lwpid_str.c_str ());
|
||
else
|
||
warning (_("found threads with pid 0, assigned replacement Target Ids: %s"),
|
||
replacement_lwpid_str.c_str ());
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
const bfd_build_id *build_id = build_id_bfd_get (abfd);
|
||
if (build_id == nullptr)
|
||
return;
|
||
|
||
gdb_bfd_ref_ptr execbfd
|
||
= find_objfile_by_build_id (build_id, abfd->filename);
|
||
|
||
if (execbfd != nullptr)
|
||
{
|
||
exec_file_attach (bfd_get_filename (execbfd.get ()), from_tty);
|
||
symbol_file_add_main (bfd_get_filename (execbfd.get ()),
|
||
symfile_add_flag (from_tty ? SYMFILE_VERBOSE : 0));
|
||
}
|
||
}
|
||
|
||
/* See gdbcore.h. */
|
||
|
||
void
|
||
core_target_open (const char *arg, int from_tty)
|
||
{
|
||
const char *p;
|
||
int siggy;
|
||
int scratch_chan;
|
||
int flags;
|
||
|
||
target_preopen (from_tty);
|
||
|
||
std::string filename = extract_single_filename_arg (arg);
|
||
|
||
if (filename.empty ())
|
||
{
|
||
if (current_program_space->core_bfd ())
|
||
error (_("No core file specified. (Use `detach' "
|
||
"to stop debugging a core file.)"));
|
||
else
|
||
error (_("No core file specified."));
|
||
}
|
||
|
||
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.c_str (), flags, 0).release ();
|
||
if (scratch_chan < 0)
|
||
perror_with_name (filename.c_str ());
|
||
|
||
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.c_str ());
|
||
|
||
if (!bfd_check_format (temp_bfd.get (), bfd_core))
|
||
{
|
||
/* Do it after the err msg */
|
||
/* FIXME: should be checking for errors from bfd_close (for one
|
||
thing, on error it does not free all the storage associated
|
||
with the bfd). */
|
||
error (_("\"%s\" is not a core dump: %s"),
|
||
filename.c_str (), bfd_errmsg (bfd_get_error ()));
|
||
}
|
||
|
||
current_program_space->cbfd = std::move (temp_bfd);
|
||
|
||
core_target *target = new core_target ();
|
||
|
||
/* Own the target until it is successfully pushed. */
|
||
target_ops_up target_holder (target);
|
||
|
||
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 ();
|
||
|
||
/* Need to flush the register cache (and the frame cache) from a
|
||
previous debug session. If inferior_ptid ends up the same as the
|
||
last debug session --- e.g., b foo; run; gcore core1; step; gcore
|
||
core2; core core1; core core2 --- then there's potential for
|
||
get_current_regcache to return the cached regcache of the
|
||
previous session, and the frame cache being stale. */
|
||
registers_changed ();
|
||
|
||
/* Find (or fake) the pid for the process in this core file, and
|
||
initialise the current inferior with that pid. */
|
||
bool fake_pid_p = false;
|
||
int pid = bfd_core_file_pid (current_program_space->core_bfd ());
|
||
if (pid == 0)
|
||
{
|
||
fake_pid_p = true;
|
||
pid = CORELOW_PID;
|
||
}
|
||
|
||
inferior *inf = current_inferior ();
|
||
gdb_assert (inf->pid == 0);
|
||
inferior_appeared (inf, pid);
|
||
inf->fake_pid_p = fake_pid_p;
|
||
|
||
/* Rename any .reg/0 sections, giving them each a fake lwpid. */
|
||
rename_vmcore_idle_reg_sections (current_program_space->core_bfd (), inf);
|
||
|
||
/* Build up thread list from BFD sections, and possibly set the
|
||
current thread to the .reg/NN section matching the .reg
|
||
section. */
|
||
asection *reg_sect
|
||
= bfd_get_section_by_name (current_program_space->core_bfd (), ".reg");
|
||
for (asection *sect : gdb_bfd_sections (current_program_space->core_bfd ()))
|
||
add_to_thread_list (sect, reg_sect, inf);
|
||
|
||
if (inferior_ptid == null_ptid)
|
||
{
|
||
/* Either we found no .reg/NN section, and hence we have a
|
||
non-threaded core (single-threaded, from gdb's perspective),
|
||
or for some reason add_to_thread_list couldn't determine
|
||
which was the "main" thread. The latter case shouldn't
|
||
usually happen, but we're dealing with input here, which can
|
||
always be broken in different ways. */
|
||
thread_info *thread = first_thread_of_inferior (inf);
|
||
|
||
if (thread == NULL)
|
||
thread = add_thread_silent (target, ptid_t (CORELOW_PID));
|
||
|
||
switch_to_thread (thread);
|
||
}
|
||
|
||
if (current_program_space->exec_bfd () == nullptr)
|
||
locate_exec_from_corefile_build_id (current_program_space->core_bfd (),
|
||
from_tty);
|
||
|
||
post_create_inferior (from_tty);
|
||
|
||
/* Now go through the target stack looking for threads since there
|
||
may be a thread_stratum target loaded on top of target core by
|
||
now. The layer above should claim threads found in the BFD
|
||
sections. */
|
||
try
|
||
{
|
||
target_update_thread_list ();
|
||
}
|
||
|
||
catch (const gdb_exception_error &except)
|
||
{
|
||
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);
|
||
|
||
/* Clearing any previous state of convenience variables. */
|
||
clear_exit_convenience_vars ();
|
||
|
||
siggy = bfd_core_file_failing_signal (current_program_space->core_bfd ());
|
||
if (siggy > 0)
|
||
{
|
||
gdbarch *core_gdbarch = target->core_gdbarch ();
|
||
|
||
/* If we don't have a CORE_GDBARCH to work with, assume a native
|
||
core (map gdb_signal from host signals). If we do have
|
||
CORE_GDBARCH to work with, but no gdb_signal_from_target
|
||
implementation for that gdbarch, as a fallback measure,
|
||
assume the host signal mapping. It'll be correct for native
|
||
cores, but most likely incorrect for cross-cores. */
|
||
enum gdb_signal sig = (core_gdbarch != NULL
|
||
&& gdbarch_gdb_signal_from_target_p (core_gdbarch)
|
||
? gdbarch_gdb_signal_from_target (core_gdbarch,
|
||
siggy)
|
||
: gdb_signal_from_host (siggy));
|
||
|
||
gdb_printf (_("Program terminated with signal %s, %s"),
|
||
gdb_signal_to_name (sig), gdb_signal_to_string (sig));
|
||
if (gdbarch_report_signal_info_p (core_gdbarch))
|
||
gdbarch_report_signal_info (core_gdbarch, current_uiout, sig);
|
||
gdb_printf (_(".\n"));
|
||
|
||
/* Set the value of the internal variable $_exitsignal,
|
||
which holds the signal uncaught by the inferior. */
|
||
set_internalvar_integer (lookup_internalvar ("_exitsignal"),
|
||
siggy);
|
||
}
|
||
|
||
/* Fetch all registers from core file. */
|
||
target_fetch_registers (get_thread_regcache (inferior_thread ()), -1);
|
||
|
||
/* Now, set up the frame cache, and print the top of stack. */
|
||
reinit_frame_cache ();
|
||
print_stack_frame (get_selected_frame (NULL), 1, SRC_AND_LOC, 1);
|
||
|
||
/* Current thread should be NUM 1 but the user does not know that.
|
||
If a program is single threaded gdb in general does not mention
|
||
anything about threads. That is why the test is >= 2. */
|
||
if (thread_count (target) >= 2)
|
||
{
|
||
try
|
||
{
|
||
thread_command (NULL, from_tty);
|
||
}
|
||
catch (const gdb_exception_error &except)
|
||
{
|
||
exception_print (gdb_stderr, except);
|
||
}
|
||
}
|
||
}
|
||
|
||
void
|
||
core_target::detach (inferior *inf, int from_tty)
|
||
{
|
||
/* Get rid of the core. Don't rely on core_target::close doing it,
|
||
because target_detach may be called with core_target's refcount > 1,
|
||
meaning core_target::close may not be called yet by the
|
||
unpush_target call below. */
|
||
clear_core ();
|
||
|
||
/* Note that 'this' may be dangling after this call. unpush_target
|
||
closes the target if the refcount reaches 0, and our close
|
||
implementation deletes 'this'. */
|
||
inf->unpush_target (this);
|
||
|
||
/* Clear the register cache and the frame cache. */
|
||
registers_changed ();
|
||
reinit_frame_cache ();
|
||
maybe_say_no_core_file_now (from_tty);
|
||
}
|
||
|
||
/* Try to retrieve registers from a section in core_bfd, and supply
|
||
them to REGSET.
|
||
|
||
If ptid's lwp member is zero, do the single-threaded
|
||
thing: look for a section named NAME. If ptid's lwp
|
||
member is non-zero, do the multi-threaded thing: look for a section
|
||
named "NAME/LWP", where LWP is the shortest ASCII decimal
|
||
representation of ptid's lwp member.
|
||
|
||
HUMAN_NAME is a human-readable name for the kind of registers the
|
||
NAME section contains, for use in error messages.
|
||
|
||
If REQUIRED is true, print an error if the core file doesn't have a
|
||
section by the appropriate name. Otherwise, just do nothing. */
|
||
|
||
void
|
||
core_target::get_core_register_section (struct regcache *regcache,
|
||
const struct regset *regset,
|
||
const char *name,
|
||
int section_min_size,
|
||
const char *human_name,
|
||
bool required)
|
||
{
|
||
gdb_assert (regset != nullptr);
|
||
|
||
struct bfd_section *section;
|
||
bfd_size_type size;
|
||
bool variable_size_section = (regset->flags & REGSET_VARIABLE_SIZE);
|
||
|
||
thread_section_name section_name (name, regcache->ptid ());
|
||
|
||
section = bfd_get_section_by_name (current_program_space->core_bfd (),
|
||
section_name.c_str ());
|
||
if (! section)
|
||
{
|
||
if (required)
|
||
warning (_("Couldn't find %s registers in core file."),
|
||
human_name);
|
||
return;
|
||
}
|
||
|
||
size = bfd_section_size (section);
|
||
if (size < section_min_size)
|
||
{
|
||
warning (_("Section `%s' in core file too small."),
|
||
section_name.c_str ());
|
||
return;
|
||
}
|
||
if (size != section_min_size && !variable_size_section)
|
||
{
|
||
warning (_("Unexpected size of section `%s' in core file."),
|
||
section_name.c_str ());
|
||
}
|
||
|
||
gdb::byte_vector contents (size);
|
||
if (!bfd_get_section_contents (current_program_space->core_bfd (), section,
|
||
contents.data (), (file_ptr) 0, size))
|
||
{
|
||
warning (_("Couldn't read %s registers from `%s' section in core file."),
|
||
human_name, section_name.c_str ());
|
||
return;
|
||
}
|
||
|
||
regset->supply_regset (regset, regcache, -1, contents.data (), size);
|
||
}
|
||
|
||
/* Data passed to gdbarch_iterate_over_regset_sections's callback. */
|
||
struct get_core_registers_cb_data
|
||
{
|
||
core_target *target;
|
||
struct regcache *regcache;
|
||
};
|
||
|
||
/* Callback for get_core_registers that handles a single core file
|
||
register note section. */
|
||
|
||
static void
|
||
get_core_registers_cb (const char *sect_name, int supply_size, int collect_size,
|
||
const struct regset *regset,
|
||
const char *human_name, void *cb_data)
|
||
{
|
||
gdb_assert (regset != nullptr);
|
||
|
||
auto *data = (get_core_registers_cb_data *) cb_data;
|
||
bool required = false;
|
||
bool variable_size_section = (regset->flags & REGSET_VARIABLE_SIZE);
|
||
|
||
if (!variable_size_section)
|
||
gdb_assert (supply_size == collect_size);
|
||
|
||
if (strcmp (sect_name, ".reg") == 0)
|
||
{
|
||
required = true;
|
||
if (human_name == NULL)
|
||
human_name = "general-purpose";
|
||
}
|
||
else if (strcmp (sect_name, ".reg2") == 0)
|
||
{
|
||
if (human_name == NULL)
|
||
human_name = "floating-point";
|
||
}
|
||
|
||
data->target->get_core_register_section (data->regcache, regset, sect_name,
|
||
supply_size, human_name, required);
|
||
}
|
||
|
||
/* Get the registers out of a core file. This is the machine-
|
||
independent part. Fetch_core_registers is the machine-dependent
|
||
part, typically implemented in the xm-file for each
|
||
architecture. */
|
||
|
||
/* We just get all the registers, so we don't use regno. */
|
||
|
||
void
|
||
core_target::fetch_registers (struct regcache *regcache, int regno)
|
||
{
|
||
if (!(m_core_gdbarch != nullptr
|
||
&& gdbarch_iterate_over_regset_sections_p (m_core_gdbarch)))
|
||
{
|
||
gdb_printf (gdb_stderr,
|
||
"Can't fetch registers from this type of core file\n");
|
||
return;
|
||
}
|
||
|
||
struct gdbarch *gdbarch = regcache->arch ();
|
||
get_core_registers_cb_data data = { this, regcache };
|
||
gdbarch_iterate_over_regset_sections (gdbarch,
|
||
get_core_registers_cb,
|
||
(void *) &data, NULL);
|
||
|
||
/* Mark all registers not found in the core as unavailable. */
|
||
for (int i = 0; i < gdbarch_num_regs (regcache->arch ()); i++)
|
||
if (regcache->get_register_status (i) == REG_UNKNOWN)
|
||
regcache->raw_supply (i, NULL);
|
||
}
|
||
|
||
void
|
||
core_target::files_info ()
|
||
{
|
||
print_section_info (&m_core_section_table, current_program_space->core_bfd ());
|
||
}
|
||
|
||
|
||
enum target_xfer_status
|
||
core_target::xfer_partial (enum target_object object, const char *annex,
|
||
gdb_byte *readbuf, const gdb_byte *writebuf,
|
||
ULONGEST offset, ULONGEST len, ULONGEST *xfered_len)
|
||
{
|
||
switch (object)
|
||
{
|
||
case TARGET_OBJECT_MEMORY:
|
||
{
|
||
enum target_xfer_status xfer_status;
|
||
|
||
/* Try accessing memory contents from core file data,
|
||
restricting consideration to those sections for which
|
||
the BFD section flag SEC_HAS_CONTENTS is set. */
|
||
auto has_contents_cb = [] (const struct target_section *s)
|
||
{
|
||
return ((s->the_bfd_section->flags & SEC_HAS_CONTENTS) != 0);
|
||
};
|
||
xfer_status = section_table_xfer_memory_partial
|
||
(readbuf, writebuf,
|
||
offset, len, xfered_len,
|
||
m_core_section_table,
|
||
has_contents_cb);
|
||
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 (!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;
|
||
}
|
||
|
||
/* 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)
|
||
{
|
||
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;
|
||
}
|
||
|
||
/* Finally, attempt to access data in core file sections with
|
||
no contents. These will typically read as all zero. */
|
||
auto no_contents_cb = [&] (const struct target_section *s)
|
||
{
|
||
return !has_contents_cb (s);
|
||
};
|
||
xfer_status = section_table_xfer_memory_partial
|
||
(readbuf, writebuf,
|
||
offset, len, xfered_len,
|
||
m_core_section_table,
|
||
no_contents_cb);
|
||
|
||
return xfer_status;
|
||
}
|
||
case TARGET_OBJECT_AUXV:
|
||
if (readbuf)
|
||
{
|
||
/* When the aux vector is stored in core file, BFD
|
||
represents this with a fake section called ".auxv". */
|
||
|
||
struct bfd_section *section;
|
||
bfd_size_type size;
|
||
|
||
section = bfd_get_section_by_name (current_program_space->core_bfd (),
|
||
".auxv");
|
||
if (section == NULL)
|
||
return TARGET_XFER_E_IO;
|
||
|
||
size = bfd_section_size (section);
|
||
if (offset >= size)
|
||
return TARGET_XFER_EOF;
|
||
size -= offset;
|
||
if (size > len)
|
||
size = len;
|
||
|
||
if (size == 0)
|
||
return TARGET_XFER_EOF;
|
||
if (!bfd_get_section_contents (current_program_space->core_bfd (),
|
||
section, readbuf, (file_ptr) offset,
|
||
size))
|
||
{
|
||
warning (_("Couldn't read NT_AUXV note in core file."));
|
||
return TARGET_XFER_E_IO;
|
||
}
|
||
|
||
*xfered_len = (ULONGEST) size;
|
||
return TARGET_XFER_OK;
|
||
}
|
||
return TARGET_XFER_E_IO;
|
||
|
||
case TARGET_OBJECT_WCOOKIE:
|
||
if (readbuf)
|
||
{
|
||
/* When the StackGhost cookie is stored in core file, BFD
|
||
represents this with a fake section called
|
||
".wcookie". */
|
||
|
||
struct bfd_section *section;
|
||
bfd_size_type size;
|
||
|
||
section = bfd_get_section_by_name (current_program_space->core_bfd (),
|
||
".wcookie");
|
||
if (section == NULL)
|
||
return TARGET_XFER_E_IO;
|
||
|
||
size = bfd_section_size (section);
|
||
if (offset >= size)
|
||
return TARGET_XFER_EOF;
|
||
size -= offset;
|
||
if (size > len)
|
||
size = len;
|
||
|
||
if (size == 0)
|
||
return TARGET_XFER_EOF;
|
||
if (!bfd_get_section_contents (current_program_space->core_bfd (),
|
||
section, readbuf, (file_ptr) offset,
|
||
size))
|
||
{
|
||
warning (_("Couldn't read StackGhost cookie in core file."));
|
||
return TARGET_XFER_E_IO;
|
||
}
|
||
|
||
*xfered_len = (ULONGEST) size;
|
||
return TARGET_XFER_OK;
|
||
|
||
}
|
||
return TARGET_XFER_E_IO;
|
||
|
||
case TARGET_OBJECT_LIBRARIES:
|
||
if (m_core_gdbarch != nullptr
|
||
&& gdbarch_core_xfer_shared_libraries_p (m_core_gdbarch))
|
||
{
|
||
if (writebuf)
|
||
return TARGET_XFER_E_IO;
|
||
else
|
||
{
|
||
*xfered_len = gdbarch_core_xfer_shared_libraries (m_core_gdbarch,
|
||
readbuf,
|
||
offset, len);
|
||
|
||
if (*xfered_len == 0)
|
||
return TARGET_XFER_EOF;
|
||
else
|
||
return TARGET_XFER_OK;
|
||
}
|
||
}
|
||
return TARGET_XFER_E_IO;
|
||
|
||
case TARGET_OBJECT_LIBRARIES_AIX:
|
||
if (m_core_gdbarch != nullptr
|
||
&& gdbarch_core_xfer_shared_libraries_aix_p (m_core_gdbarch))
|
||
{
|
||
if (writebuf)
|
||
return TARGET_XFER_E_IO;
|
||
else
|
||
{
|
||
*xfered_len
|
||
= gdbarch_core_xfer_shared_libraries_aix (m_core_gdbarch,
|
||
readbuf, offset,
|
||
len);
|
||
|
||
if (*xfered_len == 0)
|
||
return TARGET_XFER_EOF;
|
||
else
|
||
return TARGET_XFER_OK;
|
||
}
|
||
}
|
||
return TARGET_XFER_E_IO;
|
||
|
||
case TARGET_OBJECT_SIGNAL_INFO:
|
||
if (readbuf)
|
||
{
|
||
if (m_core_gdbarch != nullptr
|
||
&& gdbarch_core_xfer_siginfo_p (m_core_gdbarch))
|
||
{
|
||
LONGEST l = gdbarch_core_xfer_siginfo (m_core_gdbarch, readbuf,
|
||
offset, len);
|
||
|
||
if (l >= 0)
|
||
{
|
||
*xfered_len = l;
|
||
if (l == 0)
|
||
return TARGET_XFER_EOF;
|
||
else
|
||
return TARGET_XFER_OK;
|
||
}
|
||
}
|
||
}
|
||
return TARGET_XFER_E_IO;
|
||
|
||
default:
|
||
return this->beneath ()->xfer_partial (object, annex, readbuf,
|
||
writebuf, offset, len,
|
||
xfered_len);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* Okay, let's be honest: threads gleaned from a core file aren't
|
||
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.
|
||
*/
|
||
bool
|
||
core_target::thread_alive (ptid_t ptid)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
/* Ask the current architecture what it knows about this core file.
|
||
That will be used, in turn, to pick a better architecture. This
|
||
wrapper could be avoided if targets got a chance to specialize
|
||
core_target. */
|
||
|
||
const struct target_desc *
|
||
core_target::read_description ()
|
||
{
|
||
/* First check whether the target wants us to use the corefile target
|
||
description notes. */
|
||
if (gdbarch_use_target_description_from_corefile_notes
|
||
(m_core_gdbarch, current_program_space->core_bfd ()))
|
||
{
|
||
/* If the core file contains a target description note then go ahead and
|
||
use that. */
|
||
bfd_size_type tdesc_note_size = 0;
|
||
struct bfd_section *tdesc_note_section
|
||
= bfd_get_section_by_name (current_program_space->core_bfd (), ".gdb-tdesc");
|
||
if (tdesc_note_section != nullptr)
|
||
tdesc_note_size = bfd_section_size (tdesc_note_section);
|
||
if (tdesc_note_size > 0)
|
||
{
|
||
gdb::char_vector contents (tdesc_note_size + 1);
|
||
if (bfd_get_section_contents (current_program_space->core_bfd (),
|
||
tdesc_note_section, contents.data (),
|
||
(file_ptr) 0, tdesc_note_size))
|
||
{
|
||
/* Ensure we have a null terminator. */
|
||
contents[tdesc_note_size] = '\0';
|
||
const struct target_desc *result
|
||
= string_read_description_xml (contents.data ());
|
||
if (result != nullptr)
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If the architecture provides a corefile target description hook, use
|
||
it now. Even if the core file contains a target description in a note
|
||
section, it is not useful for targets that can potentially have distinct
|
||
descriptions for each thread. One example is AArch64's SVE/SME
|
||
extensions that allow per-thread vector length changes, resulting in
|
||
registers with different sizes. */
|
||
if (m_core_gdbarch && gdbarch_core_read_description_p (m_core_gdbarch))
|
||
{
|
||
const struct target_desc *result;
|
||
|
||
result = gdbarch_core_read_description
|
||
(m_core_gdbarch, this, current_program_space->core_bfd ());
|
||
if (result != nullptr)
|
||
return result;
|
||
}
|
||
|
||
return this->beneath ()->read_description ();
|
||
}
|
||
|
||
std::string
|
||
core_target::pid_to_str (ptid_t ptid)
|
||
{
|
||
struct inferior *inf;
|
||
int pid;
|
||
|
||
/* The preferred way is to have a gdbarch/OS specific
|
||
implementation. */
|
||
if (m_core_gdbarch != nullptr
|
||
&& gdbarch_core_pid_to_str_p (m_core_gdbarch))
|
||
return gdbarch_core_pid_to_str (m_core_gdbarch, ptid);
|
||
|
||
/* Otherwise, if we don't have one, we'll just fallback to
|
||
"process", with normal_pid_to_str. */
|
||
|
||
/* Try the LWPID field first. */
|
||
pid = ptid.lwp ();
|
||
if (pid != 0)
|
||
return normal_pid_to_str (ptid_t (pid));
|
||
|
||
/* Otherwise, this isn't a "threaded" core -- use the PID field, but
|
||
only if it isn't a fake PID. */
|
||
inf = find_inferior_ptid (this, ptid);
|
||
if (inf != NULL && !inf->fake_pid_p)
|
||
return normal_pid_to_str (ptid);
|
||
|
||
/* No luck. We simply don't have a valid PID to print. */
|
||
return "<main task>";
|
||
}
|
||
|
||
const char *
|
||
core_target::thread_name (struct thread_info *thr)
|
||
{
|
||
if (m_core_gdbarch != nullptr
|
||
&& gdbarch_core_thread_name_p (m_core_gdbarch))
|
||
return gdbarch_core_thread_name (m_core_gdbarch, thr);
|
||
return NULL;
|
||
}
|
||
|
||
bool
|
||
core_target::has_memory ()
|
||
{
|
||
return current_program_space->core_bfd () != nullptr;
|
||
}
|
||
|
||
bool
|
||
core_target::has_stack ()
|
||
{
|
||
return current_program_space->core_bfd () != nullptr;
|
||
}
|
||
|
||
bool
|
||
core_target::has_registers ()
|
||
{
|
||
return current_program_space->core_bfd () != nullptr;
|
||
}
|
||
|
||
/* Implement the to_info_proc method. */
|
||
|
||
bool
|
||
core_target::info_proc (const char *args, enum info_proc_what request)
|
||
{
|
||
struct gdbarch *gdbarch = get_current_arch ();
|
||
|
||
/* Since this is the core file target, call the 'core_info_proc'
|
||
method on gdbarch, not 'info_proc'. */
|
||
if (gdbarch_core_info_proc_p (gdbarch))
|
||
gdbarch_core_info_proc (gdbarch, args, request);
|
||
|
||
return true;
|
||
}
|
||
|
||
/* Implementation of the "supports_memory_tagging" target_ops method. */
|
||
|
||
bool
|
||
core_target::supports_memory_tagging ()
|
||
{
|
||
/* Look for memory tag sections. If they exist, that means this core file
|
||
supports memory tagging. */
|
||
|
||
return (bfd_get_section_by_name (current_program_space->core_bfd (), "memtag")
|
||
!= nullptr);
|
||
}
|
||
|
||
/* Implementation of the "fetch_memtags" target_ops method. */
|
||
|
||
bool
|
||
core_target::fetch_memtags (CORE_ADDR address, size_t len,
|
||
gdb::byte_vector &tags, int type)
|
||
{
|
||
gdbarch *gdbarch = current_inferior ()->arch ();
|
||
|
||
/* Make sure we have a way to decode the memory tag notes. */
|
||
if (!gdbarch_decode_memtag_section_p (gdbarch))
|
||
error (_("gdbarch_decode_memtag_section not implemented for this "
|
||
"architecture."));
|
||
|
||
memtag_section_info info;
|
||
info.memtag_section = nullptr;
|
||
|
||
while (get_next_core_memtag_section (current_program_space->core_bfd (),
|
||
info.memtag_section, address, info))
|
||
{
|
||
size_t adjusted_length
|
||
= (address + len < info.end_address) ? len : (info.end_address - address);
|
||
|
||
/* Decode the memory tag note and return the tags. */
|
||
gdb::byte_vector tags_read
|
||
= gdbarch_decode_memtag_section (gdbarch, info.memtag_section, type,
|
||
address, adjusted_length);
|
||
|
||
/* Transfer over the tags that have been read. */
|
||
tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
|
||
|
||
/* ADDRESS + LEN may cross the boundaries of a particular memory tag
|
||
segment. Check if we need to fetch tags from a different section. */
|
||
if (!tags_read.empty () && (address + len) < info.end_address)
|
||
return true;
|
||
|
||
/* There are more tags to fetch. Update ADDRESS and LEN. */
|
||
len -= (info.end_address - address);
|
||
address = info.end_address;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
bool
|
||
core_target::is_address_tagged (gdbarch *gdbarch, CORE_ADDR address)
|
||
{
|
||
return gdbarch_tagged_address_p (gdbarch, address);
|
||
}
|
||
|
||
/* Implementation of the "fetch_x86_xsave_layout" target_ops method. */
|
||
|
||
x86_xsave_layout
|
||
core_target::fetch_x86_xsave_layout ()
|
||
{
|
||
if (m_core_gdbarch != nullptr &&
|
||
gdbarch_core_read_x86_xsave_layout_p (m_core_gdbarch))
|
||
{
|
||
x86_xsave_layout layout;
|
||
if (!gdbarch_core_read_x86_xsave_layout (m_core_gdbarch, layout))
|
||
return {};
|
||
|
||
return layout;
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
/* Get a pointer to the current core target. If not connected to a
|
||
core target, return NULL. */
|
||
|
||
static core_target *
|
||
get_current_core_target ()
|
||
{
|
||
target_ops *proc_target = current_inferior ()->process_target ();
|
||
return dynamic_cast<core_target *> (proc_target);
|
||
}
|
||
|
||
/* Display file backed mappings from core file. */
|
||
|
||
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");
|
||
}
|
||
}
|
||
|
||
for (const target_section &tsp : m_core_file_mappings)
|
||
{
|
||
ULONGEST start = tsp.addr;
|
||
ULONGEST end = tsp.endaddr;
|
||
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);
|
||
}
|
||
}
|
||
|
||
/* Implement "maintenance print core-file-backed-mappings" command.
|
||
|
||
If mappings are loaded, the results should be similar to the
|
||
mappings shown by "info proc mappings". This command is mainly a
|
||
debugging tool for GDB developers to make sure that the expected
|
||
mappings are present after loading a core file. For Linux, the
|
||
output provided by this command will be very similar (if not
|
||
identical) to that provided by "info proc mappings". This is not
|
||
necessarily the case for other OSes which might provide
|
||
more/different information in the "info proc mappings" output. */
|
||
|
||
static void
|
||
maintenance_print_core_file_backed_mappings (const char *args, int from_tty)
|
||
{
|
||
core_target *targ = get_current_core_target ();
|
||
if (targ != nullptr)
|
||
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 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);
|
||
}
|
||
|
||
void _initialize_corelow ();
|
||
void
|
||
_initialize_corelow ()
|
||
{
|
||
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."),
|
||
&maintenanceprintlist);
|
||
}
|