|  | /* Core dump and executable file functions below target vector, for GDB. | 
|  |  | 
|  | Copyright (C) 1986-2022 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 "defs.h" | 
|  | #include "arch-utils.h" | 
|  | #include <signal.h> | 
|  | #include <fcntl.h> | 
|  | #include "frame.h"		/* required by inferior.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 <unordered_map> | 
|  | #include <unordered_set> | 
|  | #include "gdbcmd.h" | 
|  | #include "xml-tdesc.h" | 
|  |  | 
|  | #ifndef O_LARGEFILE | 
|  | #define O_LARGEFILE 0 | 
|  | #endif | 
|  |  | 
|  | /* 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; | 
|  |  | 
|  | /* 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); | 
|  |  | 
|  | private: /* per-core data */ | 
|  |  | 
|  | /* 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.  */ | 
|  | target_section_table m_core_section_table; | 
|  |  | 
|  | /* File-backed address space mappings: some core files include | 
|  | information about memory mapped files.  */ | 
|  | target_section_table 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; | 
|  |  | 
|  | /* Build m_core_file_mappings.  Called from the constructor.  */ | 
|  | void build_file_mappings (); | 
|  |  | 
|  | /* 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); | 
|  |  | 
|  | /* 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 (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 = 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 (core_bfd)); | 
|  |  | 
|  | /* Find the data section */ | 
|  | m_core_section_table = build_section_table (core_bfd); | 
|  |  | 
|  | build_file_mappings (); | 
|  | } | 
|  |  | 
|  | /* Construct the target_section_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 () | 
|  | { | 
|  | std::unordered_map<std::string, struct bfd *> bfd_map; | 
|  | std::unordered_set<std::string> unavailable_paths; | 
|  |  | 
|  | /* 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, 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); | 
|  |  | 
|  | 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) | 
|  | { | 
|  | m_core_unavailable_mappings.emplace_back (start, end - start); | 
|  | /* Print just one warning per path.  */ | 
|  | if (unavailable_paths.insert (filename).second) | 
|  | warning (_("Can't open file %s during file-backed mapping " | 
|  | "note processing"), | 
|  | filename); | 
|  | return; | 
|  | } | 
|  |  | 
|  | bfd = bfd_map[filename] = bfd_openr (expanded_fname.get (), | 
|  | "binary"); | 
|  |  | 
|  | if (bfd == nullptr || !bfd_check_format (bfd, bfd_object)) | 
|  | { | 
|  | m_core_unavailable_mappings.emplace_back (start, end - start); | 
|  | /* If we get here, there's a good chance that it's due to | 
|  | an internal error.  We issue a warning instead of an | 
|  | internal error because of the possibility that the | 
|  | file was removed in between checking for its | 
|  | existence during the expansion in exec_file_find() | 
|  | and the calls to bfd_openr() / bfd_check_format(). | 
|  | Output both the path from the core file note along | 
|  | with its expansion to make debugging this problem | 
|  | easier.  */ | 
|  | warning (_("Can't open file %s which was expanded to %s " | 
|  | "during file-backed mapping note processing"), | 
|  | filename, expanded_fname.get ()); | 
|  | 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 (core_bfd, bfd); | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | }); | 
|  |  | 
|  | normalize_mem_ranges (&m_core_unavailable_mappings); | 
|  | } | 
|  |  | 
|  | /* An arbitrary identifier for the core inferior.  */ | 
|  | #define CORELOW_PID 1 | 
|  |  | 
|  | /* Close the core target.  */ | 
|  |  | 
|  | void | 
|  | core_target::close () | 
|  | { | 
|  | if (core_bfd) | 
|  | { | 
|  | switch_to_no_thread ();    /* Avoid confusion from thread | 
|  | stuff.  */ | 
|  | exit_inferior_silent (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->cbfd.reset (nullptr); | 
|  | } | 
|  |  | 
|  | /* 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.  */ | 
|  |  | 
|  | static void | 
|  | add_to_thread_list (asection *asect, asection *reg_sect) | 
|  | { | 
|  | int core_tid; | 
|  | int pid, lwpid; | 
|  | bool fake_pid_p = false; | 
|  | struct inferior *inf; | 
|  |  | 
|  | if (!startswith (bfd_section_name (asect), ".reg/")) | 
|  | return; | 
|  |  | 
|  | core_tid = atoi (bfd_section_name (asect) + 5); | 
|  |  | 
|  | pid = bfd_core_file_pid (core_bfd); | 
|  | if (pid == 0) | 
|  | { | 
|  | fake_pid_p = true; | 
|  | pid = CORELOW_PID; | 
|  | } | 
|  |  | 
|  | lwpid = core_tid; | 
|  |  | 
|  | inf = current_inferior (); | 
|  | if (inf->pid == 0) | 
|  | { | 
|  | inferior_appeared (inf, pid); | 
|  | inf->fake_pid_p = fake_pid_p; | 
|  | } | 
|  |  | 
|  | ptid_t ptid (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) | 
|  | printf_filtered (_("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 (core_bfd != NULL) | 
|  | { | 
|  | target_detach (current_inferior (), from_tty); | 
|  | gdb_assert (core_bfd == NULL); | 
|  | } | 
|  | else | 
|  | maybe_say_no_core_file_now (from_tty); | 
|  | } | 
|  | else | 
|  | core_target_open (filename, from_tty); | 
|  | } | 
|  |  | 
|  | /* 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 | 
|  | = build_id_to_exec_bfd (build_id->size, build_id->data); | 
|  |  | 
|  | 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); | 
|  | if (!arg) | 
|  | { | 
|  | if (core_bfd) | 
|  | error (_("No core file specified.  (Use `detach' " | 
|  | "to stop debugging a core file.)")); | 
|  | else | 
|  | 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 = gdb_abspath (filename.get ()); | 
|  |  | 
|  | 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 (); | 
|  | if (scratch_chan < 0) | 
|  | perror_with_name (filename.get ()); | 
|  |  | 
|  | gdb_bfd_ref_ptr temp_bfd (gdb_bfd_fopen (filename.get (), gnutarget, | 
|  | write_files ? FOPEN_RUB : FOPEN_RB, | 
|  | scratch_chan)); | 
|  | if (temp_bfd == NULL) | 
|  | perror_with_name (filename.get ()); | 
|  |  | 
|  | 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.get (), 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 (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 (); | 
|  |  | 
|  | /* 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 (core_bfd, ".reg"); | 
|  | for (asection *sect : gdb_bfd_sections (core_bfd)) | 
|  | add_to_thread_list (sect, reg_sect); | 
|  |  | 
|  | 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 (current_inferior ()); | 
|  |  | 
|  | if (thread == NULL) | 
|  | { | 
|  | inferior_appeared (current_inferior (), CORELOW_PID); | 
|  | 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 (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 (core_bfd); | 
|  | if (p) | 
|  | printf_filtered (_("Core was generated by `%s'.\n"), p); | 
|  |  | 
|  | /* Clearing any previous state of convenience variables.  */ | 
|  | clear_exit_convenience_vars (); | 
|  |  | 
|  | siggy = bfd_core_file_failing_signal (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)); | 
|  |  | 
|  | printf_filtered (_("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); | 
|  | printf_filtered (_(".\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_current_regcache (), -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) | 
|  | { | 
|  | /* Note that 'this' is dangling after this call.  unpush_target | 
|  | closes the target, 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 (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 (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))) | 
|  | { | 
|  | fprintf_filtered (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, 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 (address_in_mem_range (memaddr, &mr)) | 
|  | { | 
|  | if (!address_in_mem_range (memend, &mr)) | 
|  | 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, | 
|  | 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 not, check the stratum beneath us, which should | 
|  | be the file stratum. | 
|  |  | 
|  | We also check unavailable mappings due to Docker/AUFS driver | 
|  | issues.  */ | 
|  | if (!m_core_file_mappings.empty () | 
|  | || !m_core_unavailable_mappings.empty ()) | 
|  | { | 
|  | xfer_status = xfer_memory_via_mappings (readbuf, writebuf, offset, | 
|  | len, xfered_len); | 
|  | } | 
|  | 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.  */ | 
|  | 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 (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 (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 (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 (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; | 
|  | } | 
|  | } | 
|  | /* FALL THROUGH */ | 
|  |  | 
|  | 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; | 
|  | } | 
|  | } | 
|  | /* FALL THROUGH */ | 
|  |  | 
|  | 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 () | 
|  | { | 
|  | /* If the core file contains a target description note then we will use | 
|  | that in preference to anything else.  */ | 
|  | bfd_size_type tdesc_note_size = 0; | 
|  | struct bfd_section *tdesc_note_section | 
|  | = bfd_get_section_by_name (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 (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 (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, core_bfd); | 
|  | if (result != NULL) | 
|  | 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 (core_bfd != NULL); | 
|  | } | 
|  |  | 
|  | bool | 
|  | core_target::has_stack () | 
|  | { | 
|  | return (core_bfd != NULL); | 
|  | } | 
|  |  | 
|  | bool | 
|  | core_target::has_registers () | 
|  | { | 
|  | return (core_bfd != NULL); | 
|  | } | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | /* 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 ()) | 
|  | { | 
|  | printf_filtered (_("Mapped address spaces:\n\n")); | 
|  | if (gdbarch_addr_bit (gdbarch) == 32) | 
|  | { | 
|  | printf_filtered ("\t%10s %10s %10s %10s %s\n", | 
|  | "Start Addr", | 
|  | "  End Addr", | 
|  | "      Size", "    Offset", "objfile"); | 
|  | } | 
|  | else | 
|  | { | 
|  | printf_filtered ("  %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) | 
|  | printf_filtered ("\t%10s %10s %10s %10s %s\n", | 
|  | paddress (gdbarch, start), | 
|  | paddress (gdbarch, end), | 
|  | hex_string (end - start), | 
|  | hex_string (file_ofs), | 
|  | filename); | 
|  | else | 
|  | printf_filtered ("  %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 ()); | 
|  | } | 
|  |  | 
|  | void _initialize_corelow (); | 
|  | void | 
|  | _initialize_corelow () | 
|  | { | 
|  | add_target (core_target_info, core_target_open, filename_completer); | 
|  | add_cmd ("core-file-backed-mappings", class_maintenance, | 
|  | maintenance_print_core_file_backed_mappings, | 
|  | _("Print core file's file-backed mappings."), | 
|  | &maintenanceprintlist); | 
|  | } |