public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] [AArch64] MTE corefile support
@ 2021-05-18 20:20 Luis Machado
  2021-05-19 10:01 ` David Spickett
                   ` (5 more replies)
  0 siblings, 6 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-18 20:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: alan.hayward, jhb, david.spickett, catalin.marinas

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
  uint64_t start_vma;
  uint64_t end_vma;
};

struct tag_dump_mte
{
  uint16_t granule_byte_size;
  uint16_t tag_bit_size;
  uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* aarch64-linux-tdep.c: Include elf/common.h.
	(MAX_TAGS_TO_TRANSFER): New constant.
	(aarch64_linux_create_memtag_notes_from_range): New function.
	(aarch64_linux_decode_memtag_note): Likewise.
	(aarch64_linux_init_abi): Register new core file hooks.
	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
	(struct tag_dump_header): New struct.
	(struct tag_dump_mte): New struct.
	(MEMTAG_NOTE_HEADER_SIZE): New constant.
	* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
	method overrides.
	* gdbarch.c: Regenerate.
	* gdbarch.h: Likewise.
	* gdbarch.sh (create_memtag_notes_from_range): New hook.
	(decode_memtag_note): Likewise.
	* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
	(linux_process_address_in_memtag_page): ... this.
	(linux_core_file_address_in_memtag_page): New function.
	(linux_address_in_memtag_page): Likewise.
	(linux_make_memtag_corefile_notes): Likewise.
	(linux_make_corefile_notes): Handle memory tag notes.
	* NEWS: Mention core file support for memory tagging.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
	for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.arch/aarch64-mte-gcore.c: New file.
	* gdb.arch/aarch64-mte-gcore.exp: New file.
---
 gdb/NEWS                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.h                 |  29 +++
 gdb/corelow.c                                |  88 +++++++++
 gdb/doc/gdb.texinfo                          |   4 +
 gdb/gdbarch.c                                |  64 +++++++
 gdb/gdbarch.h                                |  16 ++
 gdb/gdbarch.sh                               |   6 +
 gdb/linux-tdep.c                             | 141 ++++++++++++++-
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
 11 files changed, 736 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 10
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
 * GDB now supports general memory tagging functionality if the underlying
   architecture supports the proper primitives and hooks.  Currently this is
   enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..663d0e1a215 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -53,6 +53,8 @@
 
 #include "gdbsupport/selftest.h"
 
+#include "elf/common.h"
+
 /* Signal frame handling.
 
       +------------+  ^
@@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+					      CORE_ADDR start_address,
+					      CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  int granules = aarch64_mte_get_tag_granules (start_address,
+					       end_address - start_address,
+					       AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  notes.resize (0);
+	  return notes;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (uint16_t);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
+  buf += sizeof (uint64_t);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
+  buf += sizeof (uint64_t);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
+	  tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the request tags.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+				  gdb::array_view <const gdb_byte> note,
+				  CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  struct tag_dump_header header;
+  const gdb_byte *buf = note.data ();
+
+  header.format
+    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
+  buf += sizeof (uint16_t);
+
+  header.start_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  header.end_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  /* Sanity check  */
+  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  int skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+				    aligned_address - aligned_start_address,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* The amount of memory tag granules we need to fetch.  */
+  int granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
+
+  /* Read the tag granules.  */
+  for (unsigned int i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+	 addresses.  */
+      set_gdbarch_create_memtag_notes_from_range (gdbarch,
+				  aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+				      aarch64_linux_decode_memtag_note);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..a4a26628847 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* Headers for the NT_MEMTAG notes.  */
+
+/* Generic NT_MEMTAG header.  */
+struct tag_dump_header
+{
+  /* Tag format.  */
+  uint16_t format;
+  /* Start address of the tagged range.  */
+  uint64_t start_vma;
+  /* End address of the tagged range.  */
+  uint64_t end_vma;
+};
+
+/* MTE-specific NT_MEMTAG header.  */
+struct tag_dump_mte
+{
+  /* Size of the tag granule in bytes.  */
+  uint16_t granule_byte_size;
+  /* Size of the tag in bits.  */
+  uint16_t tag_bit_size;
+  /* Reserved field for the future.  */
+  uint16_t __unused;
+};
+
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..33d16b7220a 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
   return true;
 }
 
+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
+    return false;
+
+  return true;
+}
+
+/* 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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    warning (_("gdbarch_decode_memtag_note not implemented for this "
+	       "architecture."));
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Remove the top byte for the memory range check.  */
+  address = address_significant (gdbarch, address);
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
+	{
+	  warning (_("malformed core note - too short for header"));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  Thos contains the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+				+ sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  /* Decode the memory tag note and return the tags.  */
+	  tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
+	  return true;
+	}
+
+      /* The requested address lies outside this particular memtag note.  Keep
+	 looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 56f37eb2288..738cb3b56eb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   fprintf_unfiltered (file,
                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                       host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
   fprintf_unfiltered (file,
                       "gdbarch_dump: decr_pc_after_break = %s\n",
                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
 # Find core file memory regions
 M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
 
+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
 # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
 # core file into buffer READBUF with length LEN.  Return the number of bytes read
 # (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..f4ff57dec87 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
+	{
+	  warning (_("malformed core note - too short for header"));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  Those contain the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+				+ sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	return true;
+
+      /* The requested address lies outside this particular memtag note.  Keep
+	 looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+				  gdb::unique_xmalloc_ptr<char> &note_data,
+				  int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  std::vector<struct smaps_data> smaps;
+
+  /* Parse the contents of smaps into a vector.  */
+  smaps = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+	 memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+	continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+	 this particular memory range, including the header.
+
+	 If the notes are too big, we may need to break up the transfer
+	 into smaller chunks.
+
+	 If the architecture returns an empty vector, that means there are
+	 no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes;
+      memory_tag_notes
+	= gdbarch_create_memtag_notes_from_range (gdbarch,
+						  map.start_address,
+						  map.end_address);
+      /* Write notes to the core file.  */
+      for (gdb::byte_vector note : memory_tag_notes)
+	{
+	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
+					       note_size, "CORE",
+					       NT_MEMTAG, note.data (),
+					       note.size ()));
+	}
+    }
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	return NULL;
     }
 
+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
   /* File mappings.  */
   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
 
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..bb529a8b369
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,115 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart
+
+# Load the program file.
+set program_filename [standard_output_file $testfile]
+set program_loaded [gdb_file_cmd $program_filename]
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
@ 2021-05-19 10:01 ` David Spickett
  2021-05-19 11:11   ` Luis Machado
  2021-05-19 12:13 ` Eli Zaretskii
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 47+ messages in thread
From: David Spickett @ 2021-05-19 10:01 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches, alan.hayward, John Baldwin, Catalin Marinas

> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024

Is this 1024 derived from something? Random guess the max note size
minus the size of the note header.

> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the request tags.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +                                 gdb::array_view <const gdb_byte> note,
> +                                 CORE_ADDR address, size_t length)

Would be worth commenting that this assumes that address is within the
range of the note given. (I see later that it's only called if that's
true)

On Tue, 18 May 2021 at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
>
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
>
> Dumping memory tags
> -
>
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
>
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
>
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
>
> struct tag_dump_header
> {
>   uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>   uint64_t start_vma;
>   uint64_t end_vma;
> };
>
> struct tag_dump_mte
> {
>   uint16_t granule_byte_size;
>   uint16_t tag_bit_size;
>   uint16_t __unused;
> };
>
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
>
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
>
> We can extend the format in the future to make room for other memory tag
> layouts.
>
> Reading memory tags
> -
>
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
>
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
>
> I've added a test to exercise writing and reading of memory tags in core
> files.
>
> gdb/ChangeLog:
>
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>
>         * aarch64-linux-tdep.c: Include elf/common.h.
>         (MAX_TAGS_TO_TRANSFER): New constant.
>         (aarch64_linux_create_memtag_notes_from_range): New function.
>         (aarch64_linux_decode_memtag_note): Likewise.
>         (aarch64_linux_init_abi): Register new core file hooks.
>         * arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
>         (struct tag_dump_header): New struct.
>         (struct tag_dump_mte): New struct.
>         (MEMTAG_NOTE_HEADER_SIZE): New constant.
>         * corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
>         method overrides.
>         * gdbarch.c: Regenerate.
>         * gdbarch.h: Likewise.
>         * gdbarch.sh (create_memtag_notes_from_range): New hook.
>         (decode_memtag_note): Likewise.
>         * linux-tdep.c (linux_address_in_memtag_page): Renamed to...
>         (linux_process_address_in_memtag_page): ... this.
>         (linux_core_file_address_in_memtag_page): New function.
>         (linux_address_in_memtag_page): Likewise.
>         (linux_make_memtag_corefile_notes): Likewise.
>         (linux_make_corefile_notes): Handle memory tag notes.
>         * NEWS: Mention core file support for memory tagging.
>
> gdb/doc/ChangeLog:
>
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>
>         * gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>         for memory tagging in core files.
>
> gdb/testsuite/ChangeLog:
>
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>
>         * gdb.arch/aarch64-mte-gcore.c: New file.
>         * gdb.arch/aarch64-mte-gcore.exp: New file.
> ---
>  gdb/NEWS                                     |   4 +
>  gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
>  gdb/arch/aarch64-mte-linux.h                 |  29 +++
>  gdb/corelow.c                                |  88 +++++++++
>  gdb/doc/gdb.texinfo                          |   4 +
>  gdb/gdbarch.c                                |  64 +++++++
>  gdb/gdbarch.h                                |  16 ++
>  gdb/gdbarch.sh                               |   6 +
>  gdb/linux-tdep.c                             | 141 ++++++++++++++-
>  gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
>  gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
>  11 files changed, 736 insertions(+), 3 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>  create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>
>  *** Changes since GDB 10
>
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
>  * GDB now supports general memory tagging functionality if the underlying
>    architecture supports the proper primitives and hooks.  Currently this is
>    enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..663d0e1a215 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,8 @@
>
>  #include "gdbsupport/selftest.h"
>
> +#include "elf/common.h"
> +
>  /* Signal frame handling.
>
>        +------------+  ^
> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>      }
>  }
>
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                             CORE_ADDR start_address,
> +                                             CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  int granules = aarch64_mte_get_tag_granules (start_address,
> +                                              end_address - start_address,
> +                                              AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +       = (granules >= MAX_TAGS_TO_TRANSFER)?
> +         MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +         granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +                                static_cast<int> (memtag_type::allocation)))
> +       {
> +         warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +                    phex_nz (start_address, sizeof (start_address)),
> +                    phex_nz (end_address, sizeof (end_address)));
> +         notes.resize (0);
> +         return notes;
> +       }
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +                         NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
> +  buf += sizeof (uint64_t);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
> +  buf += sizeof (uint64_t);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +                         AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +                         AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
> +         tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the request tags.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +                                 gdb::array_view <const gdb_byte> note,
> +                                 CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  struct tag_dump_header header;
> +  const gdb_byte *buf = note.data ();
> +
> +  header.format
> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
> +  buf += sizeof (uint16_t);
> +
> +  header.start_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  header.end_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  /* Sanity check  */
> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  int skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +                                   aligned_address - aligned_start_address,
> +                                   AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  int granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
> +
> +  /* Read the tag granules.  */
> +  for (unsigned int i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
>  static void
>  aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>  {
> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>
>        set_gdbarch_report_signal_info (gdbarch,
>                                       aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +        addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
> +                                 aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +                                     aarch64_linux_decode_memtag_note);
>      }
>
>    /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..a4a26628847 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>
>  /* We have one tag per 16 bytes of memory.  */
>  #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>  #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>  #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>
> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>     It is always possible to get the logical tag.  */
>  extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>
> +/* Headers for the NT_MEMTAG notes.  */
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* MTE-specific NT_MEMTAG header.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
> +
>  #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..33d16b7220a 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
>
>    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;
> +
>    /* A few helpers.  */
>
>    /* Getter, see variable definition.  */
> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>    return true;
>  }
>
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
> +    return false;
> +
> +  return true;
> +}
> +
> +/* 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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
> +              "architecture."));
> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> +
> +  /* Remove the top byte for the memory range check.  */
> +  address = address_significant (gdbarch, address);
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +        is malformed.  */
> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
> +       {
> +         warning (_("malformed core note - too short for header"));
> +         return false;
> +       }
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (core_bfd, section,
> +                                    note.data (), 0, note_size))
> +       {
> +         warning (_("could not get core note contents."));
> +         return false;
> +       }
> +
> +      /* Read the generic header of the note.  Thos contains the format,
> +        start address and end address.  */
> +      uint64_t start_address
> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
> +      uint64_t end_address
> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
> +                               + sizeof (uint64_t));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +         && address < end_address)
> +       {
> +         /* Decode the memory tag note and return the tags.  */
> +         tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
> +         return true;
> +       }
> +
> +      /* The requested address lies outside this particular memtag note.  Keep
> +        looking and get the next section.  */
> +      section = bfd_get_next_section_by_name (core_bfd, section);
> +    }
> +
> +  return false;
> +}
> +
>  /* Get a pointer to the current core target.  If not connected to a
>     core target, return NULL.  */
>
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 56f37eb2288..738cb3b56eb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>  option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>  documentation in the Linux kernel.
>
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
>  @node i386
>  @subsection x86 Architecture-specific Issues
>
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>    gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>    gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>    gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>    gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>    gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>    gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>    /* Skip verify of iterate_over_regset_sections, has predicate.  */
>    /* Skip verify of make_corefile_notes, has predicate.  */
>    /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>    /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>    /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>    /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>    fprintf_unfiltered (file,
>                        "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                        host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>    fprintf_unfiltered (file,
>                        "gdbarch_dump: decr_pc_after_break = %s\n",
>                        core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>    gdbarch->find_memory_regions = find_memory_regions;
>  }
>
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
>  bool
>  gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>  {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>  extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>  extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
>  /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>     core file into buffer READBUF with length LEN.  Return the number of bytes read
>     (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>  # Find core file memory regions
>  M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
>  # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>  # core file into buffer READBUF with length LEN.  Return the number of bytes read
>  # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..f4ff57dec87 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>    return smaps;
>  }
>
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>  {
>    if (current_inferior ()->fake_pid_p)
>      return false;
> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>    return false;
>  }
>
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +        is malformed.  */
> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
> +       {
> +         warning (_("malformed core note - too short for header"));
> +         return false;
> +       }
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (core_bfd, section,
> +                                    note.data (), 0, note_size))
> +       {
> +         warning (_("could not get core note contents."));
> +         return false;
> +       }
> +
> +      /* Read the generic header of the note.  Those contain the format,
> +        start address and end address.  */
> +      uint64_t start_address
> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
> +      uint64_t end_address
> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
> +                               + sizeof (uint64_t));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +         && address < end_address)
> +       return true;
> +
> +      /* The requested address lies outside this particular memtag note.  Keep
> +        looking and get the next section.  */
> +      section = bfd_get_next_section_by_name (core_bfd, section);
> +    }
> +
> +  return false;
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +                                 gdb::unique_xmalloc_ptr<char> &note_data,
> +                                 int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  std::vector<struct smaps_data> smaps;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  smaps = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +        memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +       continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +        this particular memory range, including the header.
> +
> +        If the notes are too big, we may need to break up the transfer
> +        into smaller chunks.
> +
> +        If the architecture returns an empty vector, that means there are
> +        no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes;
> +      memory_tag_notes
> +       = gdbarch_create_memtag_notes_from_range (gdbarch,
> +                                                 map.start_address,
> +                                                 map.end_address);
> +      /* Write notes to the core file.  */
> +      for (gdb::byte_vector note : memory_tag_notes)
> +       {
> +         note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +                                              note_size, "CORE",
> +                                              NT_MEMTAG, note.data (),
> +                                              note.size ()));
> +       }
> +    }
> +}
> +
>  /* List memory regions in the inferior for a corefile.  */
>
>  static int
> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>         return NULL;
>      }
>
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>    /* File mappings.  */
>    linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE  (1UL << 0)
> +#define PR_MTE_TCF_SHIFT       1
> +#define PR_MTE_TCF_SYNC                (1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT       3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +            PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +            | (0xfffe << PR_MTE_TAG_SHIFT),
> +            0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..bb529a8b369
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,115 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +            $atag_msg \
> +            "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +       "Program received signal SIGSEGV, Segmentation fault" \
> +       "Memory tag violation while accessing address $hex" \
> +       "Allocation tag $hex" \
> +       "Logical tag $hex\." \
> +       "$hex in access_memory \\(.*\\) at .*" \
> +       ".*tagged_ptr\\\[0\\\] = 'a';"] \
> +        "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart
> +
> +# Load the program file.
> +set program_filename [standard_output_file $testfile]
> +set program_loaded [gdb_file_cmd $program_filename]
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +       "Core was generated by.*\." \
> +       "Program terminated with signal SIGSEGV, Segmentation fault" \
> +       "Memory tag violation while accessing address $hex" \
> +       "Allocation tag 0xf" \
> +       "Logical tag 0x0\." \
> +       "#0.*$hex in access_memory \\(.*\\) at .*" \
> +       ".*tagged_ptr\\\[0\\\] = 'a';"] \
> +        "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +        "tag_ctl.*$hex.*${::decimal}" \
> +        "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +            "= 0x$tag" \
> +            "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> --
> 2.25.1
>

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-19 10:01 ` David Spickett
@ 2021-05-19 11:11   ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-19 11:11 UTC (permalink / raw)
  To: David Spickett; +Cc: gdb-patches, alan.hayward, John Baldwin, Catalin Marinas

On 5/19/21 7:01 AM, David Spickett wrote:
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
> 
> Is this 1024 derived from something? Random guess the max note size
> minus the size of the note header.

No. Just a fixed number so we can transfer tags in chunks as opposed to 
transferring a lot of them at once. ptrace won't let us do it in one go 
anyway.

> 
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> +   hook.  Decode a memory tag note and return the request tags.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> +                                 gdb::array_view <const gdb_byte> note,
>> +                                 CORE_ADDR address, size_t length)
> 
> Would be worth commenting that this assumes that address is within the
> range of the note given. (I see later that it's only called if that's
> true)

I'll add a comment.

> 
> On Tue, 18 May 2021 at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>>
>> Teach GDB how to dump memory tags when using the gcore command and how
>> to read them back from a core file generated via gcore or the kernel.
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own NT_MEMTAG note. A section named ".memtag" is created for each of those
>> when reading the core file back.
>>
>> Dumping memory tags
>> -
>>
>> When using the gcore command to dump a core file, GDB will go through the maps
>> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
>> passed to an arch-specific gdbarch hook that generates a vector of blobs of
>> memory tag data that are blindly put into a NT_MEMTAG note.
>>
>> The vector is used because we may have, in the future,  multiple tag types for
>> a particular memory range.
>>
>> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
>> like so:
>>
>> struct tag_dump_header
>> {
>>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>    uint64_t start_vma;
>>    uint64_t end_vma;
>> };
>>
>> struct tag_dump_mte
>> {
>>    uint16_t granule_byte_size;
>>    uint16_t tag_bit_size;
>>    uint16_t __unused;
>> };
>>
>> The only bits meant to be generic are the tag_dump_format, start_vma and
>> end_vma fields.
>>
>> The format-specific data is supposed to be opaque and only useful for the
>> arch-specific code.
>>
>> We can extend the format in the future to make room for other memory tag
>> layouts.
>>
>> Reading memory tags
>> -
>>
>> When reading a core file that contains NT_MEMTAG entries, GDB will use
>> a different approach to check for tagged memory range. Rather than looking
>> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
>> memory range.
>>
>> When reading tags, GDB will now use the core target's implementation of
>> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
>> is fed into an arch-specific hook that will decode the memory tag format and
>> return a vector of tags.
>>
>> I've added a test to exercise writing and reading of memory tags in core
>> files.
>>
>> gdb/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>>          * aarch64-linux-tdep.c: Include elf/common.h.
>>          (MAX_TAGS_TO_TRANSFER): New constant.
>>          (aarch64_linux_create_memtag_notes_from_range): New function.
>>          (aarch64_linux_decode_memtag_note): Likewise.
>>          (aarch64_linux_init_abi): Register new core file hooks.
>>          * arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
>>          (struct tag_dump_header): New struct.
>>          (struct tag_dump_mte): New struct.
>>          (MEMTAG_NOTE_HEADER_SIZE): New constant.
>>          * corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
>>          method overrides.
>>          * gdbarch.c: Regenerate.
>>          * gdbarch.h: Likewise.
>>          * gdbarch.sh (create_memtag_notes_from_range): New hook.
>>          (decode_memtag_note): Likewise.
>>          * linux-tdep.c (linux_address_in_memtag_page): Renamed to...
>>          (linux_process_address_in_memtag_page): ... this.
>>          (linux_core_file_address_in_memtag_page): New function.
>>          (linux_address_in_memtag_page): Likewise.
>>          (linux_make_memtag_corefile_notes): Likewise.
>>          (linux_make_corefile_notes): Handle memory tag notes.
>>          * NEWS: Mention core file support for memory tagging.
>>
>> gdb/doc/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>>          * gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>>          for memory tagging in core files.
>>
>> gdb/testsuite/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>>          * gdb.arch/aarch64-mte-gcore.c: New file.
>>          * gdb.arch/aarch64-mte-gcore.exp: New file.
>> ---
>>   gdb/NEWS                                     |   4 +
>>   gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
>>   gdb/arch/aarch64-mte-linux.h                 |  29 +++
>>   gdb/corelow.c                                |  88 +++++++++
>>   gdb/doc/gdb.texinfo                          |   4 +
>>   gdb/gdbarch.c                                |  64 +++++++
>>   gdb/gdbarch.h                                |  16 ++
>>   gdb/gdbarch.sh                               |   6 +
>>   gdb/linux-tdep.c                             | 141 ++++++++++++++-
>>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
>>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
>>   11 files changed, 736 insertions(+), 3 deletions(-)
>>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index ab678acec8b..58b9f739d4f 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,10 @@
>>
>>   *** Changes since GDB 10
>>
>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>> +  reading memory tag data for AArch64 MTE from core files generated by
>> +  the gcore command or the Linux kernel.
>> +
>>   * GDB now supports general memory tagging functionality if the underlying
>>     architecture supports the proper primitives and hooks.  Currently this is
>>     enabled only for AArch64 MTE.
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index e9761ed2189..663d0e1a215 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -53,6 +53,8 @@
>>
>>   #include "gdbsupport/selftest.h"
>>
>> +#include "elf/common.h"
>> +
>>   /* Signal frame handling.
>>
>>         +------------+  ^
>> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>       }
>>   }
>>
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
>> +   gdbarch hook.  Create core file notes for memory tags.  */
>> +
>> +static std::vector<gdb::byte_vector>
>> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> +                                             CORE_ADDR start_address,
>> +                                             CORE_ADDR end_address)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  int granules = aarch64_mte_get_tag_granules (start_address,
>> +                                              end_address - start_address,
>> +                                              AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
>> +     at the moment).  */
>> +  std::vector<gdb::byte_vector> notes (1);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return notes;
>> +
>> +  /* Adjust the MTE note size to hold the header + tags.  */
>> +  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
>> +
>> +  CORE_ADDR address = start_address;
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +       = (granules >= MAX_TAGS_TO_TRANSFER)?
>> +         MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +         granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +                                static_cast<int> (memtag_type::allocation)))
>> +       {
>> +         warning (_("Failed to read MTE tags from memory range [%s,%s]."),
>> +                    phex_nz (start_address, sizeof (start_address)),
>> +                    phex_nz (end_address, sizeof (end_address)));
>> +         notes.resize (0);
>> +         return notes;
>> +       }
>> +
>> +      /* Transfer over the tags that have been read.  */
>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>> +
>> +      /* Adjust the remaining granules and starting address.  */
>> +      granules -= tags_read.size ();
>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> +    }
>> +
>> +  /* Create the header.  */
>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> +  gdb_byte *buf = notes[0].data ();
>> +
>> +  /* Generic header.  */
>> +  /* Tag dump format */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +                         NT_MEMTAG_TYPE_AARCH_MTE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Start address */
>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* End address */
>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* MTE-specific header.  */
>> +  /* Granule byte size */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +                         AARCH64_MTE_GRANULE_SIZE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Tag bit size */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +                         AARCH64_MTE_TAG_BIT_SIZE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Unused value */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
>> +
>> +  /* Store the tags.  */
>> +  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
>> +         tags.size ());
>> +
>> +  return notes;
>> +}
>> +
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> +   hook.  Decode a memory tag note and return the request tags.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> +                                 gdb::array_view <const gdb_byte> note,
>> +                                 CORE_ADDR address, size_t length)
>> +{
>> +  gdb::byte_vector tags;
>> +
>> +  /* Read the generic header.  */
>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> +  struct tag_dump_header header;
>> +  const gdb_byte *buf = note.data ();
>> +
>> +  header.format
>> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
>> +  buf += sizeof (uint16_t);
>> +
>> +  header.start_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  header.end_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* Sanity check  */
>> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
>> +    {
>> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
>> +      return tags;
>> +    }
>> +
>> +  /* Calculate how many granules we need to skip to get to the granule of
>> +     ADDRESS.  Align both the start address and the requested address
>> +     so it is easier to get the number of granules to skip.  This way we
>> +     don't need to consider cases where ADDRESS falls in the middle of a
>> +     tag granule range.  */
>> +  CORE_ADDR aligned_start_address
>> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
>> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  int skipped_granules
>> +    = aarch64_mte_get_tag_granules (aligned_start_address,
>> +                                   aligned_address - aligned_start_address,
>> +                                   AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* The amount of memory tag granules we need to fetch.  */
>> +  int granules
>> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to decode, just return.  */
>> +  if (granules == 0)
>> +    return tags;
>> +
>> +  /* Point to the block of data that contains the first granule we are
>> +     interested in.  */
>> +  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
>> +
>> +  /* Read the tag granules.  */
>> +  for (unsigned int i = 0; i < granules; i++)
>> +      tags.push_back (tags_data[i]);
>> +
>> +  return tags;
>> +}
>> +
>>   static void
>>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>   {
>> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>
>>         set_gdbarch_report_signal_info (gdbarch,
>>                                        aarch64_linux_report_signal_info);
>> +
>> +      /* Core file helpers.  */
>> +
>> +      /* Core file helper to create memory tag notes for a particular range of
>> +        addresses.  */
>> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
>> +                                 aarch64_linux_create_memtag_notes_from_range);
>> +
>> +      /* Core file helper to decode a memory tag note.  */
>> +      set_gdbarch_decode_memtag_note (gdbarch,
>> +                                     aarch64_linux_decode_memtag_note);
>>       }
>>
>>     /* Initialize the aarch64_linux_record_tdep.  */
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index 2aa97eb861a..a4a26628847 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>
>>   /* We have one tag per 16 bytes of memory.  */
>>   #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>
>> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>      It is always possible to get the logical tag.  */
>>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>
>> +/* Headers for the NT_MEMTAG notes.  */
>> +
>> +/* Generic NT_MEMTAG header.  */
>> +struct tag_dump_header
>> +{
>> +  /* Tag format.  */
>> +  uint16_t format;
>> +  /* Start address of the tagged range.  */
>> +  uint64_t start_vma;
>> +  /* End address of the tagged range.  */
>> +  uint64_t end_vma;
>> +};
>> +
>> +/* MTE-specific NT_MEMTAG header.  */
>> +struct tag_dump_mte
>> +{
>> +  /* Size of the tag granule in bytes.  */
>> +  uint16_t granule_byte_size;
>> +  /* Size of the tag in bits.  */
>> +  uint16_t tag_bit_size;
>> +  /* Reserved field for the future.  */
>> +  uint16_t __unused;
>> +};
>> +
>> +/* Memory tag note header size.  Includes both the generic and the
>> +   arch-specific parts.  */
>> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
>> +
>>   #endif /* ARCH_AARCH64_LINUX_H */
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index 452b4dd4f9a..33d16b7220a 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
>>
>>     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;
>> +
>>     /* A few helpers.  */
>>
>>     /* Getter, see variable definition.  */
>> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>>     return true;
>>   }
>>
>> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
>> +
>> +bool
>> +core_target::supports_memory_tagging ()
>> +{
>> +  /* Look for memory tag notes.  If they exist, that means this core file
>> +     supports memory tagging.  */
>> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
>> +    return false;
>> +
>> +  return true;
>> +}
>> +
>> +/* 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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* Make sure we have a way to decode the memory tag notes.  */
>> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
>> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
>> +              "architecture."));
>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> +  /* Remove the top byte for the memory range check.  */
>> +  address = address_significant (gdbarch, address);
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t note_size = bfd_section_size (section);
>> +
>> +      /* If the note is smaller than the size of the header, this core note
>> +        is malformed.  */
>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>> +       {
>> +         warning (_("malformed core note - too short for header"));
>> +         return false;
>> +       }
>> +
>> +      gdb::byte_vector note (note_size);
>> +
>> +      /* Fetch the contents of this particular memtag note.  */
>> +      if (!bfd_get_section_contents (core_bfd, section,
>> +                                    note.data (), 0, note_size))
>> +       {
>> +         warning (_("could not get core note contents."));
>> +         return false;
>> +       }
>> +
>> +      /* Read the generic header of the note.  Thos contains the format,
>> +        start address and end address.  */
>> +      uint64_t start_address
>> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> +      uint64_t end_address
>> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> +                               + sizeof (uint64_t));
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +         && address < end_address)
>> +       {
>> +         /* Decode the memory tag note and return the tags.  */
>> +         tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
>> +         return true;
>> +       }
>> +
>> +      /* The requested address lies outside this particular memtag note.  Keep
>> +        looking and get the next section.  */
>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>> +    }
>> +
>> +  return false;
>> +}
>> +
>>   /* Get a pointer to the current core target.  If not connected to a
>>      core target, return NULL.  */
>>
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 56f37eb2288..738cb3b56eb 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>>   documentation in the Linux kernel.
>>
>> +@value{GDBN} supports dumping memory tag data to core files through the
>> +@command{gcore} command and reading memory tag data from core files generated
>> +by the @command{gcore} command or the Linux kernel.
>> +
>>   @node i386
>>   @subsection x86 Architecture-specific Issues
>>
>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>> index 208cf4b5aaa..de384da2e9a 100644
>> --- a/gdb/gdbarch.c
>> +++ b/gdb/gdbarch.c
>> @@ -283,6 +283,8 @@ struct gdbarch
>>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>     gdbarch_find_memory_regions_ftype *find_memory_regions;
>> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
>> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>     /* Skip verify of make_corefile_notes, has predicate.  */
>>     /* Skip verify of find_memory_regions, has predicate.  */
>> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
>> +  /* Skip verify of decode_memtag_note, has predicate.  */
>>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>     /* Skip verify of core_pid_to_str, has predicate.  */
>> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>>     fprintf_unfiltered (file,
>>                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>>                         host_address_to_string (gdbarch->core_xfer_siginfo));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
>> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
>> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
>> +                      gdbarch_decode_memtag_note_p (gdbarch));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
>> +                      host_address_to_string (gdbarch->decode_memtag_note));
>>     fprintf_unfiltered (file,
>>                         "gdbarch_dump: decr_pc_after_break = %s\n",
>>                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
>> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>     gdbarch->find_memory_regions = find_memory_regions;
>>   }
>>
>> +bool
>> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->create_memtag_notes_from_range != NULL;
>> +}
>> +
>> +std::vector<gdb::byte_vector>
>> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
>> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
>> +}
>> +
>> +void
>> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
>> +{
>> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
>> +}
>> +
>> +bool
>> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->decode_memtag_note != NULL;
>> +}
>> +
>> +gdb::byte_vector
>> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
>> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
>> +}
>> +
>> +void
>> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
>> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
>> +{
>> +  gdbarch->decode_memtag_note = decode_memtag_note;
>> +}
>> +
>>   bool
>>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>   {
>> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
>> index 7157e5596fd..80e244624de 100644
>> --- a/gdb/gdbarch.h
>> +++ b/gdb/gdbarch.h
>> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>
>> +/* Create memory tag core file notes given a range of addresses. */
>> +
>> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
>> +
>> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
>> +
>> +/* Decode a memory tag note and return the tags that it contains. */
>> +
>> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
>> +
>> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
>> +
>>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>>      (zero indicates failure).
>> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
>> index 43e51341f97..7c5eed0780c 100755
>> --- a/gdb/gdbarch.sh
>> +++ b/gdb/gdbarch.sh
>> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>>   # Find core file memory regions
>>   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>>
>> +# Create memory tag core file notes given a range of addresses.
>> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
>> +
>> +# Decode a memory tag note and return the tags that it contains.
>> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
>> +
>>   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>   # core file into buffer READBUF with length LEN.  Return the number of bytes read
>>   # (zero indicates failure).
>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>> index 927e69bf1e1..f4ff57dec87 100644
>> --- a/gdb/linux-tdep.c
>> +++ b/gdb/linux-tdep.c
>> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>>     return smaps;
>>   }
>>
>> -/* See linux-tdep.h.  */
>> +/* Helper that checks if an address is in a memory tag page for a live
>> +   process.  */
>>
>> -bool
>> -linux_address_in_memtag_page (CORE_ADDR address)
>> +static bool
>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>   {
>>     if (current_inferior ()->fake_pid_p)
>>       return false;
>> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>     return false;
>>   }
>>
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> +   process.  */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (core_bfd == nullptr)
>> +    return false;
>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t note_size = bfd_section_size (section);
>> +
>> +      /* If the note is smaller than the size of the header, this core note
>> +        is malformed.  */
>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>> +       {
>> +         warning (_("malformed core note - too short for header"));
>> +         return false;
>> +       }
>> +
>> +      gdb::byte_vector note (note_size);
>> +
>> +      /* Fetch the contents of this particular memtag note.  */
>> +      if (!bfd_get_section_contents (core_bfd, section,
>> +                                    note.data (), 0, note_size))
>> +       {
>> +         warning (_("could not get core note contents."));
>> +         return false;
>> +       }
>> +
>> +      /* Read the generic header of the note.  Those contain the format,
>> +        start address and end address.  */
>> +      uint64_t start_address
>> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> +      uint64_t end_address
>> +       = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> +                               + sizeof (uint64_t));
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +         && address < end_address)
>> +       return true;
>> +
>> +      /* The requested address lies outside this particular memtag note.  Keep
>> +        looking and get the next section.  */
>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>> +    }
>> +
>> +  return false;
>> +}
>> +
>> +/* See linux-tdep.h.  */
>> +
>> +bool
>> +linux_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (!target_has_execution ())
>> +    return linux_core_file_address_in_memtag_page (address);
>> +
>> +  return linux_process_address_in_memtag_page (address);
>> +}
>> +
>> +/* For each memory map entry that has memory tagging enabled, create a new
>> +   core file note that contains all of its memory tags.  Save the data to
>> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
>> +
>> +static void
>> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
>> +                                 gdb::unique_xmalloc_ptr<char> &note_data,
>> +                                 int *note_size)
>> +{
>> +  if (current_inferior ()->fake_pid_p)
>> +    return;
>> +
>> +  /* If the architecture doesn't have a hook to return memory tag notes,
>> +     there is nothing left to do.  */
>> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
>> +    return;
>> +
>> +  pid_t pid = current_inferior ()->pid;
>> +
>> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
>> +
>> +  gdb::unique_xmalloc_ptr<char> data
>> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
>> +
>> +  if (data == nullptr)
>> +    return;
>> +
>> +  std::vector<struct smaps_data> smaps;
>> +
>> +  /* Parse the contents of smaps into a vector.  */
>> +  smaps = parse_smaps_data (data.get (), smaps_file);
>> +
>> +  for (const smaps_data &map : smaps)
>> +    {
>> +      /* Does this mapping have memory tagging enabled? If so, save the
>> +        memory tags to the core file note.  */
>> +      if (map.vmflags.memory_tagging == 0)
>> +       continue;
>> +
>> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
>> +        this particular memory range, including the header.
>> +
>> +        If the notes are too big, we may need to break up the transfer
>> +        into smaller chunks.
>> +
>> +        If the architecture returns an empty vector, that means there are
>> +        no memory tag notes to write.  */
>> +      std::vector<gdb::byte_vector> memory_tag_notes;
>> +      memory_tag_notes
>> +       = gdbarch_create_memtag_notes_from_range (gdbarch,
>> +                                                 map.start_address,
>> +                                                 map.end_address);
>> +      /* Write notes to the core file.  */
>> +      for (gdb::byte_vector note : memory_tag_notes)
>> +       {
>> +         note_data.reset (elfcore_write_note (obfd, note_data.release (),
>> +                                              note_size, "CORE",
>> +                                              NT_MEMTAG, note.data (),
>> +                                              note.size ()));
>> +       }
>> +    }
>> +}
>> +
>>   /* List memory regions in the inferior for a corefile.  */
>>
>>   static int
>> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>>          return NULL;
>>       }
>>
>> +  /* Dump the memory tags, if any.  */
>> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
>> +
>>     /* File mappings.  */
>>     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>>
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> new file mode 100644
>> index 00000000000..b20ebcff424
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2021 Free Software Foundation, Inc.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
>> +
>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>> +   Extension from the Linux Kernel, found in the sources in
>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>> +
>> +#include <errno.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <sys/auxv.h>
>> +#include <sys/mman.h>
>> +#include <sys/prctl.h>
>> +
>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>> +#define HWCAP2_MTE              (1 << 18)
>> +
>> +/* From arch/arm64/include/uapi/asm/mman.h */
>> +#define PROT_MTE  0x20
>> +
>> +/* From include/uapi/linux/prctl.h */
>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>> +#define PR_TAGGED_ADDR_ENABLE  (1UL << 0)
>> +#define PR_MTE_TCF_SHIFT       1
>> +#define PR_MTE_TCF_SYNC                (1UL << PR_MTE_TCF_SHIFT)
>> +#define PR_MTE_TAG_SHIFT       3
>> +
>> +void
>> +access_memory (unsigned char *tagged_ptr)
>> +{
>> +  tagged_ptr[0] = 'a';
>> +}
>> +
>> +int
>> +main (int argc, char **argv)
>> +{
>> +  unsigned char *tagged_ptr;
>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> +
>> +  /* Bail out if MTE is not supported.  */
>> +  if (!(hwcap2 & HWCAP2_MTE))
>> +    return 1;
>> +
>> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
>> +     allow all non-zero tags in the randomly generated set.  */
>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>> +            PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>> +            | (0xfffe << PR_MTE_TAG_SHIFT),
>> +            0, 0, 0))
>> +    {
>> +      perror ("prctl () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Create a mapping that will have PROT_MTE set.  */
>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>> +                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> +  if (tagged_ptr == MAP_FAILED)
>> +    {
>> +      perror ("mmap () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Enable MTE on the above anonymous mmap.  */
>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>> +    {
>> +      perror ("mprotect () failed");
>> +      return 1;
>> +    }
>> +
>> +  access_memory (tagged_ptr);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..bb529a8b369
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,115 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# This file is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> +    verbose "Skipping ${gdb_test_file_name}."
>> +    return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    untested "could not run to main"
>> +    return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> +    unsupported "memory tagging unsupported"
>> +    return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> +    return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%02x" $i]
>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> +            $atag_msg \
>> +            "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +       "Program received signal SIGSEGV, Segmentation fault" \
>> +       "Memory tag violation while accessing address $hex" \
>> +       "Allocation tag $hex" \
>> +       "Logical tag $hex\." \
>> +       "$hex in access_memory \\(.*\\) at .*" \
>> +       ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +        "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> +    return -1
>> +}
>> +
>> +clean_restart
>> +
>> +# Load the program file.
>> +set program_filename [standard_output_file $testfile]
>> +set program_loaded [gdb_file_cmd $program_filename]
>> +
>> +if { $program_loaded } {
>> +    return -1
>> +}
>> +
>> +# Load the core file and make sure we see the tag violation fault
>> +# information.
>> +gdb_test "core $core_filename" \
>> +    [multi_line \
>> +       "Core was generated by.*\." \
>> +       "Program terminated with signal SIGSEGV, Segmentation fault" \
>> +       "Memory tag violation while accessing address $hex" \
>> +       "Allocation tag 0xf" \
>> +       "Logical tag 0x0\." \
>> +       "#0.*$hex in access_memory \\(.*\\) at .*" \
>> +       ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +        "core file shows tag violation information"
>> +
>> +# Make sure we have the tag_ctl register.
>> +gdb_test "info register tag_ctl" \
>> +        "tag_ctl.*$hex.*${::decimal}" \
>> +        "tag_ctl is available"
>> +
>> +# Check if the tag granules have the expected values.  If they do, that
>> +# means the core file saved the tags properly and GDB has read them
>> +# correctly.
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%x" $i]
>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>> +            "= 0x$tag" \
>> +            "memory tag of &tagged_ptr\[$index\] is correct"
>> +}
>> --
>> 2.25.1
>>

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
  2021-05-19 10:01 ` David Spickett
@ 2021-05-19 12:13 ` Eli Zaretskii
  2021-05-21 15:12 ` Alan Hayward
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 47+ messages in thread
From: Eli Zaretskii @ 2021-05-19 12:13 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches, david.spickett, catalin.marinas

> Date: Tue, 18 May 2021 17:20:47 -0300
> From: Luis Machado via Gdb-patches <gdb-patches@sourceware.org>
> Cc: david.spickett@linaro.org, catalin.marinas@arm.com
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	(struct tag_dump_header): New struct.
> 	(struct tag_dump_mte): New struct.
> 	(MEMTAG_NOTE_HEADER_SIZE): New constant.
> 	* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* NEWS: Mention core file support for memory tagging.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.

Thanks, the documentation parts are OK.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
  2021-05-19 10:01 ` David Spickett
  2021-05-19 12:13 ` Eli Zaretskii
@ 2021-05-21 15:12 ` Alan Hayward
  2021-05-21 15:30   ` Luis Machado
  2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 47+ messages in thread
From: Alan Hayward @ 2021-05-21 15:12 UTC (permalink / raw)
  To: Luis Machado
  Cc: gdb-patches\@sourceware.org, jhb@freebsd.org, david.spickett,
	Catalin Marinas, nd



> On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>  uint64_t start_vma;
>  uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>  uint16_t granule_byte_size;
>  uint16_t tag_bit_size;
>  uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 

This is a wider question than this patch - but is there someplace this format will
be documented? Ideally either Linux or GDB needs something written down in
text format.

A few nits below too, but mostly looks fine.


> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	(struct tag_dump_header): New struct.
> 	(struct tag_dump_mte): New struct.
> 	(MEMTAG_NOTE_HEADER_SIZE): New constant.
> 	* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* NEWS: Mention core file support for memory tagging.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> ---
> gdb/NEWS                                     |   4 +
> gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
> gdb/arch/aarch64-mte-linux.h                 |  29 +++
> gdb/corelow.c                                |  88 +++++++++
> gdb/doc/gdb.texinfo                          |   4 +
> gdb/gdbarch.c                                |  64 +++++++
> gdb/gdbarch.h                                |  16 ++
> gdb/gdbarch.sh                               |   6 +
> gdb/linux-tdep.c                             | 141 ++++++++++++++-
> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
> 11 files changed, 736 insertions(+), 3 deletions(-)
> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
> 
> *** Changes since GDB 10
> 
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> * GDB now supports general memory tagging functionality if the underlying
>   architecture supports the proper primitives and hooks.  Currently this is
>   enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..663d0e1a215 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,8 @@
> 
> #include "gdbsupport/selftest.h"
> 
> +#include "elf/common.h"
> +
> /* Signal frame handling.
> 
>       +------------+  ^
> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>     }
> }
> 
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  int granules = aarch64_mte_get_tag_granules (start_address,
> +					       end_address - start_address,
> +					       AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);
> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +			  NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
> +  buf += sizeof (uint64_t);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
> +  buf += sizeof (uint64_t);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +			  AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
> +			  AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (uint16_t);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the request tags.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  struct tag_dump_header header;
> +  const gdb_byte *buf = note.data ();
> +
> +  header.format
> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
> +  buf += sizeof (uint16_t);
> +
> +  header.start_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  header.end_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  /* Sanity check  */
> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  int skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  int granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
> +
> +  /* Read the tag granules.  */
> +  for (unsigned int i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
> static void
> aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> {
> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> 
>       set_gdbarch_report_signal_info (gdbarch,
> 				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
> +				  aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>     }
> 
>   /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..a4a26628847 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
> 
> /* We have one tag per 16 bytes of memory.  */
> #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
> #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
> #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
> 
> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>    It is always possible to get the logical tag.  */
> extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
> 
> +/* Headers for the NT_MEMTAG notes.  */
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* MTE-specific NT_MEMTAG header.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)

This should compile down to a constant value: ?

#define MEMTAG_NOTE_HEADER_SIZE (sizeof(tag_dump_header)+sizeof(tag_dump_mte))


> +
> #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..33d16b7220a 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
> 
>   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;
> +
>   /* A few helpers.  */
> 
>   /* Getter, see variable definition.  */
> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>   return true;
> }
> 
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
> +    return false;
> +
> +  return true;
> +}
> +
> +/* 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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
> +	       "architecture."));
> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> +
> +  /* Remove the top byte for the memory range check.  */
> +  address = address_significant (gdbarch, address);
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))

I’m not sure why 64+16 ?

> +	{
> +	  warning (_("malformed core note - too short for header"));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (core_bfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  Thos contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
> +      uint64_t end_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
> +				+ sizeof (uint64_t));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  /* Decode the memory tag note and return the tags.  */
> +	  tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
> +	  return true;
> +	}
> +
> +      /* The requested address lies outside this particular memtag note.  Keep
> +	 looking and get the next section.  */
> +      section = bfd_get_next_section_by_name (core_bfd, section);
> +    }
> +
> +  return false;
> +}
> +
> /* Get a pointer to the current core target.  If not connected to a
>    core target, return NULL.  */
> 
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 56f37eb2288..738cb3b56eb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
> option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
> documentation in the Linux kernel.
> 
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> @node i386
> @subsection x86 Architecture-specific Issues
> 
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>   gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>   /* Skip verify of iterate_over_regset_sections, has predicate.  */
>   /* Skip verify of make_corefile_notes, has predicate.  */
>   /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>   /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>   fprintf_unfiltered (file,
>                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                       host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>   fprintf_unfiltered (file,
>                       "gdbarch_dump: decr_pc_after_break = %s\n",
>                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>   gdbarch->find_memory_regions = find_memory_regions;
> }
> 
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
> bool
> gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
> {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
> extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
> extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
> 
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
> /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>    core file into buffer READBUF with length LEN.  Return the number of bytes read
>    (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
> # Find core file memory regions
> M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
> 
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
> # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> # core file into buffer READBUF with length LEN.  Return the number of bytes read
> # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..f4ff57dec87 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>   return smaps;
> }
> 
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
> 
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
> {
>   if (current_inferior ()->fake_pid_p)
>     return false;
> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>   return false;
> }
> 
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
> +	{
> +	  warning (_("malformed core note - too short for header"));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (core_bfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  Those contain the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
> +      uint64_t end_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
> +				+ sizeof (uint64_t));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	return true;
> +
> +      /* The requested address lies outside this particular memtag note.  Keep
> +	 looking and get the next section.  */
> +      section = bfd_get_next_section_by_name (core_bfd, section);
> +    }
> +
> +  return false;
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  std::vector<struct smaps_data> smaps;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  smaps = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes;
> +      memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (gdb::byte_vector note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
> /* List memory regions in the inferior for a corefile.  */
> 
> static int
> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
> 	return NULL;
>     }
> 
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>   /* File mappings.  */
>   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
> 
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..bb529a8b369
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,115 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.

2018?

> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart
> +
> +# Load the program file.
> +set program_filename [standard_output_file $testfile]
> +set program_loaded [gdb_file_cmd $program_filename]
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> -- 
> 2.25.1
> 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-21 15:12 ` Alan Hayward
@ 2021-05-21 15:30   ` Luis Machado
  2021-05-21 17:20     ` John Baldwin
  2021-05-24  8:07     ` Alan Hayward
  0 siblings, 2 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-21 15:30 UTC (permalink / raw)
  To: Alan Hayward
  Cc: gdb-patches\@sourceware.org, jhb@freebsd.org, david.spickett,
	Catalin Marinas, nd

Hi Alan,

On 5/21/21 12:12 PM, Alan Hayward wrote:
> 
> 
>> On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>>
>> Teach GDB how to dump memory tags when using the gcore command and how
>> to read them back from a core file generated via gcore or the kernel.
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own NT_MEMTAG note. A section named ".memtag" is created for each of those
>> when reading the core file back.
>>
>> Dumping memory tags
>> -
>>
>> When using the gcore command to dump a core file, GDB will go through the maps
>> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
>> passed to an arch-specific gdbarch hook that generates a vector of blobs of
>> memory tag data that are blindly put into a NT_MEMTAG note.
>>
>> The vector is used because we may have, in the future,  multiple tag types for
>> a particular memory range.
>>
>> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
>> like so:
>>
>> struct tag_dump_header
>> {
>>   uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>   uint64_t start_vma;
>>   uint64_t end_vma;
>> };
>>
>> struct tag_dump_mte
>> {
>>   uint16_t granule_byte_size;
>>   uint16_t tag_bit_size;
>>   uint16_t __unused;
>> };
>>
>> The only bits meant to be generic are the tag_dump_format, start_vma and
>> end_vma fields.
>>
>> The format-specific data is supposed to be opaque and only useful for the
>> arch-specific code.
>>
>> We can extend the format in the future to make room for other memory tag
>> layouts.
>>
> 
> This is a wider question than this patch - but is there someplace this format will
> be documented? Ideally either Linux or GDB needs something written down in
> text format.
> 
> A few nits below too, but mostly looks fine.

Yes. I'm still thinking whether this should go into the Linux Kernel's 
documentation or somewhere in the GDB manual.

Given GDB reads core files generated by the Linux Kernel and also 
creates cores files itself, I'm thinking it may be best to put it in the 
GDB manual.

I want to wait for possible feedbacks about the format before we 
document how it will look like in the manual.

> 
> 
>> Reading memory tags
>> -
>>
>> When reading a core file that contains NT_MEMTAG entries, GDB will use
>> a different approach to check for tagged memory range. Rather than looking
>> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
>> memory range.
>>
>> When reading tags, GDB will now use the core target's implementation of
>> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
>> is fed into an arch-specific hook that will decode the memory tag format and
>> return a vector of tags.
>>
>> I've added a test to exercise writing and reading of memory tags in core
>> files.
>>
>> gdb/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* aarch64-linux-tdep.c: Include elf/common.h.
>> 	(MAX_TAGS_TO_TRANSFER): New constant.
>> 	(aarch64_linux_create_memtag_notes_from_range): New function.
>> 	(aarch64_linux_decode_memtag_note): Likewise.
>> 	(aarch64_linux_init_abi): Register new core file hooks.
>> 	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
>> 	(struct tag_dump_header): New struct.
>> 	(struct tag_dump_mte): New struct.
>> 	(MEMTAG_NOTE_HEADER_SIZE): New constant.
>> 	* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
>> 	method overrides.
>> 	* gdbarch.c: Regenerate.
>> 	* gdbarch.h: Likewise.
>> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
>> 	(decode_memtag_note): Likewise.
>> 	* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
>> 	(linux_process_address_in_memtag_page): ... this.
>> 	(linux_core_file_address_in_memtag_page): New function.
>> 	(linux_address_in_memtag_page): Likewise.
>> 	(linux_make_memtag_corefile_notes): Likewise.
>> 	(linux_make_corefile_notes): Handle memory tag notes.
>> 	* NEWS: Mention core file support for memory tagging.
>>
>> gdb/doc/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>> 	for memory tagging in core files.
>>
>> gdb/testsuite/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* gdb.arch/aarch64-mte-gcore.c: New file.
>> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
>> ---
>> gdb/NEWS                                     |   4 +
>> gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
>> gdb/arch/aarch64-mte-linux.h                 |  29 +++
>> gdb/corelow.c                                |  88 +++++++++
>> gdb/doc/gdb.texinfo                          |   4 +
>> gdb/gdbarch.c                                |  64 +++++++
>> gdb/gdbarch.h                                |  16 ++
>> gdb/gdbarch.sh                               |   6 +
>> gdb/linux-tdep.c                             | 141 ++++++++++++++-
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
>> 11 files changed, 736 insertions(+), 3 deletions(-)
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index ab678acec8b..58b9f739d4f 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,10 @@
>>
>> *** Changes since GDB 10
>>
>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>> +  reading memory tag data for AArch64 MTE from core files generated by
>> +  the gcore command or the Linux kernel.
>> +
>> * GDB now supports general memory tagging functionality if the underlying
>>    architecture supports the proper primitives and hooks.  Currently this is
>>    enabled only for AArch64 MTE.
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index e9761ed2189..663d0e1a215 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -53,6 +53,8 @@
>>
>> #include "gdbsupport/selftest.h"
>>
>> +#include "elf/common.h"
>> +
>> /* Signal frame handling.
>>
>>        +------------+  ^
>> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>      }
>> }
>>
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
>> +   gdbarch hook.  Create core file notes for memory tags.  */
>> +
>> +static std::vector<gdb::byte_vector>
>> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> +					      CORE_ADDR start_address,
>> +					      CORE_ADDR end_address)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  int granules = aarch64_mte_get_tag_granules (start_address,
>> +					       end_address - start_address,
>> +					       AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
>> +     at the moment).  */
>> +  std::vector<gdb::byte_vector> notes (1);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return notes;
>> +
>> +  /* Adjust the MTE note size to hold the header + tags.  */
>> +  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
>> +
>> +  CORE_ADDR address = start_address;
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
>> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +	  granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +				 static_cast<int> (memtag_type::allocation)))
>> +	{
>> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
>> +		     phex_nz (start_address, sizeof (start_address)),
>> +		     phex_nz (end_address, sizeof (end_address)));
>> +	  notes.resize (0);
>> +	  return notes;
>> +	}
>> +
>> +      /* Transfer over the tags that have been read.  */
>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>> +
>> +      /* Adjust the remaining granules and starting address.  */
>> +      granules -= tags_read.size ();
>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> +    }
>> +
>> +  /* Create the header.  */
>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> +  gdb_byte *buf = notes[0].data ();
>> +
>> +  /* Generic header.  */
>> +  /* Tag dump format */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +			  NT_MEMTAG_TYPE_AARCH_MTE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Start address */
>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* End address */
>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* MTE-specific header.  */
>> +  /* Granule byte size */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +			  AARCH64_MTE_GRANULE_SIZE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Tag bit size */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>> +			  AARCH64_MTE_TAG_BIT_SIZE);
>> +  buf += sizeof (uint16_t);
>> +
>> +  /* Unused value */
>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
>> +
>> +  /* Store the tags.  */
>> +  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
>> +	  tags.size ());
>> +
>> +  return notes;
>> +}
>> +
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> +   hook.  Decode a memory tag note and return the request tags.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> +				  gdb::array_view <const gdb_byte> note,
>> +				  CORE_ADDR address, size_t length)
>> +{
>> +  gdb::byte_vector tags;
>> +
>> +  /* Read the generic header.  */
>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> +  struct tag_dump_header header;
>> +  const gdb_byte *buf = note.data ();
>> +
>> +  header.format
>> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
>> +  buf += sizeof (uint16_t);
>> +
>> +  header.start_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  header.end_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* Sanity check  */
>> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
>> +    {
>> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
>> +      return tags;
>> +    }
>> +
>> +  /* Calculate how many granules we need to skip to get to the granule of
>> +     ADDRESS.  Align both the start address and the requested address
>> +     so it is easier to get the number of granules to skip.  This way we
>> +     don't need to consider cases where ADDRESS falls in the middle of a
>> +     tag granule range.  */
>> +  CORE_ADDR aligned_start_address
>> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
>> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  int skipped_granules
>> +    = aarch64_mte_get_tag_granules (aligned_start_address,
>> +				    aligned_address - aligned_start_address,
>> +				    AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* The amount of memory tag granules we need to fetch.  */
>> +  int granules
>> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to decode, just return.  */
>> +  if (granules == 0)
>> +    return tags;
>> +
>> +  /* Point to the block of data that contains the first granule we are
>> +     interested in.  */
>> +  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
>> +
>> +  /* Read the tag granules.  */
>> +  for (unsigned int i = 0; i < granules; i++)
>> +      tags.push_back (tags_data[i]);
>> +
>> +  return tags;
>> +}
>> +
>> static void
>> aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>> {
>> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>
>>        set_gdbarch_report_signal_info (gdbarch,
>> 				      aarch64_linux_report_signal_info);
>> +
>> +      /* Core file helpers.  */
>> +
>> +      /* Core file helper to create memory tag notes for a particular range of
>> +	 addresses.  */
>> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
>> +				  aarch64_linux_create_memtag_notes_from_range);
>> +
>> +      /* Core file helper to decode a memory tag note.  */
>> +      set_gdbarch_decode_memtag_note (gdbarch,
>> +				      aarch64_linux_decode_memtag_note);
>>      }
>>
>>    /* Initialize the aarch64_linux_record_tdep.  */
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index 2aa97eb861a..a4a26628847 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>
>> /* We have one tag per 16 bytes of memory.  */
>> #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>> #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>> #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>
>> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>     It is always possible to get the logical tag.  */
>> extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>
>> +/* Headers for the NT_MEMTAG notes.  */
>> +
>> +/* Generic NT_MEMTAG header.  */
>> +struct tag_dump_header
>> +{
>> +  /* Tag format.  */
>> +  uint16_t format;
>> +  /* Start address of the tagged range.  */
>> +  uint64_t start_vma;
>> +  /* End address of the tagged range.  */
>> +  uint64_t end_vma;
>> +};
>> +
>> +/* MTE-specific NT_MEMTAG header.  */
>> +struct tag_dump_mte
>> +{
>> +  /* Size of the tag granule in bytes.  */
>> +  uint16_t granule_byte_size;
>> +  /* Size of the tag in bits.  */
>> +  uint16_t tag_bit_size;
>> +  /* Reserved field for the future.  */
>> +  uint16_t __unused;
>> +};
>> +
>> +/* Memory tag note header size.  Includes both the generic and the
>> +   arch-specific parts.  */
>> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
> 
> This should compile down to a constant value: ?
> 
> #define MEMTAG_NOTE_HEADER_SIZE (sizeof(tag_dump_header)+sizeof(tag_dump_mte))
> 
> 

Right. But there may be padding between the members of the struct, and 
we don't want to rely on what the compiler is doing. We could force this 
to be "packed" maybe?

>> +
>> #endif /* ARCH_AARCH64_LINUX_H */
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index 452b4dd4f9a..33d16b7220a 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
>>
>>    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;
>> +
>>    /* A few helpers.  */
>>
>>    /* Getter, see variable definition.  */
>> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>>    return true;
>> }
>>
>> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
>> +
>> +bool
>> +core_target::supports_memory_tagging ()
>> +{
>> +  /* Look for memory tag notes.  If they exist, that means this core file
>> +     supports memory tagging.  */
>> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
>> +    return false;
>> +
>> +  return true;
>> +}
>> +
>> +/* 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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* Make sure we have a way to decode the memory tag notes.  */
>> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
>> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
>> +	       "architecture."));
>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> +  /* Remove the top byte for the memory range check.  */
>> +  address = address_significant (gdbarch, address);
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t note_size = bfd_section_size (section);
>> +
>> +      /* If the note is smaller than the size of the header, this core note
>> +	 is malformed.  */
>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
> 
> I’m not sure why 64+16 ?
> 

Actually 2 * 8 + 2. Two 64-bit entries and a 16-bit entry in the generic 
header.

Does that make sense?

>> +	{
>> +	  warning (_("malformed core note - too short for header"));
>> +	  return false;
>> +	}
>> +
>> +      gdb::byte_vector note (note_size);
>> +
>> +      /* Fetch the contents of this particular memtag note.  */
>> +      if (!bfd_get_section_contents (core_bfd, section,
>> +				     note.data (), 0, note_size))
>> +	{
>> +	  warning (_("could not get core note contents."));
>> +	  return false;
>> +	}
>> +
>> +      /* Read the generic header of the note.  Thos contains the format,
>> +	 start address and end address.  */
>> +      uint64_t start_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> +      uint64_t end_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> +				+ sizeof (uint64_t));
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +	  && address < end_address)
>> +	{
>> +	  /* Decode the memory tag note and return the tags.  */
>> +	  tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
>> +	  return true;
>> +	}
>> +
>> +      /* The requested address lies outside this particular memtag note.  Keep
>> +	 looking and get the next section.  */
>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>> +    }
>> +
>> +  return false;
>> +}
>> +
>> /* Get a pointer to the current core target.  If not connected to a
>>     core target, return NULL.  */
>>
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 56f37eb2288..738cb3b56eb 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>> option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>> documentation in the Linux kernel.
>>
>> +@value{GDBN} supports dumping memory tag data to core files through the
>> +@command{gcore} command and reading memory tag data from core files generated
>> +by the @command{gcore} command or the Linux kernel.
>> +
>> @node i386
>> @subsection x86 Architecture-specific Issues
>>
>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>> index 208cf4b5aaa..de384da2e9a 100644
>> --- a/gdb/gdbarch.c
>> +++ b/gdb/gdbarch.c
>> @@ -283,6 +283,8 @@ struct gdbarch
>>    gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>>    gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>    gdbarch_find_memory_regions_ftype *find_memory_regions;
>> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
>> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>>    gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>    gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>>    gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>    /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>    /* Skip verify of make_corefile_notes, has predicate.  */
>>    /* Skip verify of find_memory_regions, has predicate.  */
>> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
>> +  /* Skip verify of decode_memtag_note, has predicate.  */
>>    /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>    /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>    /* Skip verify of core_pid_to_str, has predicate.  */
>> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>>    fprintf_unfiltered (file,
>>                        "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>>                        host_address_to_string (gdbarch->core_xfer_siginfo));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
>> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
>> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
>> +                      gdbarch_decode_memtag_note_p (gdbarch));
>> +  fprintf_unfiltered (file,
>> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
>> +                      host_address_to_string (gdbarch->decode_memtag_note));
>>    fprintf_unfiltered (file,
>>                        "gdbarch_dump: decr_pc_after_break = %s\n",
>>                        core_addr_to_string_nz (gdbarch->decr_pc_after_break));
>> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>    gdbarch->find_memory_regions = find_memory_regions;
>> }
>>
>> +bool
>> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->create_memtag_notes_from_range != NULL;
>> +}
>> +
>> +std::vector<gdb::byte_vector>
>> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
>> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
>> +}
>> +
>> +void
>> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
>> +{
>> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
>> +}
>> +
>> +bool
>> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->decode_memtag_note != NULL;
>> +}
>> +
>> +gdb::byte_vector
>> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
>> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
>> +}
>> +
>> +void
>> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
>> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
>> +{
>> +  gdbarch->decode_memtag_note = decode_memtag_note;
>> +}
>> +
>> bool
>> gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>> {
>> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
>> index 7157e5596fd..80e244624de 100644
>> --- a/gdb/gdbarch.h
>> +++ b/gdb/gdbarch.h
>> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>> extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>> extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>
>> +/* Create memory tag core file notes given a range of addresses. */
>> +
>> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
>> +
>> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
>> +
>> +/* Decode a memory tag note and return the tags that it contains. */
>> +
>> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
>> +
>> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
>> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
>> +
>> /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>     core file into buffer READBUF with length LEN.  Return the number of bytes read
>>     (zero indicates failure).
>> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
>> index 43e51341f97..7c5eed0780c 100755
>> --- a/gdb/gdbarch.sh
>> +++ b/gdb/gdbarch.sh
>> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>> # Find core file memory regions
>> M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>>
>> +# Create memory tag core file notes given a range of addresses.
>> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
>> +
>> +# Decode a memory tag note and return the tags that it contains.
>> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
>> +
>> # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>> # core file into buffer READBUF with length LEN.  Return the number of bytes read
>> # (zero indicates failure).
>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>> index 927e69bf1e1..f4ff57dec87 100644
>> --- a/gdb/linux-tdep.c
>> +++ b/gdb/linux-tdep.c
>> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>>    return smaps;
>> }
>>
>> -/* See linux-tdep.h.  */
>> +/* Helper that checks if an address is in a memory tag page for a live
>> +   process.  */
>>
>> -bool
>> -linux_address_in_memtag_page (CORE_ADDR address)
>> +static bool
>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>> {
>>    if (current_inferior ()->fake_pid_p)
>>      return false;
>> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>    return false;
>> }
>>
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> +   process.  */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (core_bfd == nullptr)
>> +    return false;
>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t note_size = bfd_section_size (section);
>> +
>> +      /* If the note is smaller than the size of the header, this core note
>> +	 is malformed.  */
>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>> +	{
>> +	  warning (_("malformed core note - too short for header"));
>> +	  return false;
>> +	}
>> +
>> +      gdb::byte_vector note (note_size);
>> +
>> +      /* Fetch the contents of this particular memtag note.  */
>> +      if (!bfd_get_section_contents (core_bfd, section,
>> +				     note.data (), 0, note_size))
>> +	{
>> +	  warning (_("could not get core note contents."));
>> +	  return false;
>> +	}
>> +
>> +      /* Read the generic header of the note.  Those contain the format,
>> +	 start address and end address.  */
>> +      uint64_t start_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> +      uint64_t end_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> +				+ sizeof (uint64_t));
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +	  && address < end_address)
>> +	return true;
>> +
>> +      /* The requested address lies outside this particular memtag note.  Keep
>> +	 looking and get the next section.  */
>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>> +    }
>> +
>> +  return false;
>> +}
>> +
>> +/* See linux-tdep.h.  */
>> +
>> +bool
>> +linux_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (!target_has_execution ())
>> +    return linux_core_file_address_in_memtag_page (address);
>> +
>> +  return linux_process_address_in_memtag_page (address);
>> +}
>> +
>> +/* For each memory map entry that has memory tagging enabled, create a new
>> +   core file note that contains all of its memory tags.  Save the data to
>> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
>> +
>> +static void
>> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
>> +				  gdb::unique_xmalloc_ptr<char> &note_data,
>> +				  int *note_size)
>> +{
>> +  if (current_inferior ()->fake_pid_p)
>> +    return;
>> +
>> +  /* If the architecture doesn't have a hook to return memory tag notes,
>> +     there is nothing left to do.  */
>> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
>> +    return;
>> +
>> +  pid_t pid = current_inferior ()->pid;
>> +
>> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
>> +
>> +  gdb::unique_xmalloc_ptr<char> data
>> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
>> +
>> +  if (data == nullptr)
>> +    return;
>> +
>> +  std::vector<struct smaps_data> smaps;
>> +
>> +  /* Parse the contents of smaps into a vector.  */
>> +  smaps = parse_smaps_data (data.get (), smaps_file);
>> +
>> +  for (const smaps_data &map : smaps)
>> +    {
>> +      /* Does this mapping have memory tagging enabled? If so, save the
>> +	 memory tags to the core file note.  */
>> +      if (map.vmflags.memory_tagging == 0)
>> +	continue;
>> +
>> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
>> +	 this particular memory range, including the header.
>> +
>> +	 If the notes are too big, we may need to break up the transfer
>> +	 into smaller chunks.
>> +
>> +	 If the architecture returns an empty vector, that means there are
>> +	 no memory tag notes to write.  */
>> +      std::vector<gdb::byte_vector> memory_tag_notes;
>> +      memory_tag_notes
>> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
>> +						  map.start_address,
>> +						  map.end_address);
>> +      /* Write notes to the core file.  */
>> +      for (gdb::byte_vector note : memory_tag_notes)
>> +	{
>> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
>> +					       note_size, "CORE",
>> +					       NT_MEMTAG, note.data (),
>> +					       note.size ()));
>> +	}
>> +    }
>> +}
>> +
>> /* List memory regions in the inferior for a corefile.  */
>>
>> static int
>> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>> 	return NULL;
>>      }
>>
>> +  /* Dump the memory tags, if any.  */
>> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
>> +
>>    /* File mappings.  */
>>    linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>>
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> new file mode 100644
>> index 00000000000..b20ebcff424
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2021 Free Software Foundation, Inc.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
>> +
>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>> +   Extension from the Linux Kernel, found in the sources in
>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>> +
>> +#include <errno.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <sys/auxv.h>
>> +#include <sys/mman.h>
>> +#include <sys/prctl.h>
>> +
>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>> +#define HWCAP2_MTE              (1 << 18)
>> +
>> +/* From arch/arm64/include/uapi/asm/mman.h */
>> +#define PROT_MTE  0x20
>> +
>> +/* From include/uapi/linux/prctl.h */
>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
>> +#define PR_MTE_TCF_SHIFT	1
>> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
>> +#define PR_MTE_TAG_SHIFT	3
>> +
>> +void
>> +access_memory (unsigned char *tagged_ptr)
>> +{
>> +  tagged_ptr[0] = 'a';
>> +}
>> +
>> +int
>> +main (int argc, char **argv)
>> +{
>> +  unsigned char *tagged_ptr;
>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> +
>> +  /* Bail out if MTE is not supported.  */
>> +  if (!(hwcap2 & HWCAP2_MTE))
>> +    return 1;
>> +
>> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
>> +     allow all non-zero tags in the randomly generated set.  */
>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
>> +	     0, 0, 0))
>> +    {
>> +      perror ("prctl () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Create a mapping that will have PROT_MTE set.  */
>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> +  if (tagged_ptr == MAP_FAILED)
>> +    {
>> +      perror ("mmap () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Enable MTE on the above anonymous mmap.  */
>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>> +    {
>> +      perror ("mprotect () failed");
>> +      return 1;
>> +    }
>> +
>> +  access_memory (tagged_ptr);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..bb529a8b369
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,115 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> 
> 2018?
> 

I really copied the template from an existing file. The rule, AFAIR, is 
to keep the copyright years from the original file.

>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# This file is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> +    verbose "Skipping ${gdb_test_file_name}."
>> +    return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    untested "could not run to main"
>> +    return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> +    unsupported "memory tagging unsupported"
>> +    return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> +    return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%02x" $i]
>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> +	     $atag_msg \
>> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +	"Program received signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag $hex" \
>> +	"Logical tag $hex\." \
>> +	"$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> +    return -1
>> +}
>> +
>> +clean_restart
>> +
>> +# Load the program file.
>> +set program_filename [standard_output_file $testfile]
>> +set program_loaded [gdb_file_cmd $program_filename]
>> +
>> +if { $program_loaded } {
>> +    return -1
>> +}
>> +
>> +# Load the core file and make sure we see the tag violation fault
>> +# information.
>> +gdb_test "core $core_filename" \
>> +    [multi_line \
>> +	"Core was generated by.*\." \
>> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag 0xf" \
>> +	"Logical tag 0x0\." \
>> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "core file shows tag violation information"
>> +
>> +# Make sure we have the tag_ctl register.
>> +gdb_test "info register tag_ctl" \
>> +	 "tag_ctl.*$hex.*${::decimal}" \
>> +	 "tag_ctl is available"
>> +
>> +# Check if the tag granules have the expected values.  If they do, that
>> +# means the core file saved the tags properly and GDB has read them
>> +# correctly.
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%x" $i]
>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>> +	     "= 0x$tag" \
>> +	     "memory tag of &tagged_ptr\[$index\] is correct"
>> +}
>> -- 
>> 2.25.1
>>
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-21 15:30   ` Luis Machado
@ 2021-05-21 17:20     ` John Baldwin
  2021-05-24 13:41       ` Luis Machado
  2021-05-24  8:07     ` Alan Hayward
  1 sibling, 1 reply; 47+ messages in thread
From: John Baldwin @ 2021-05-21 17:20 UTC (permalink / raw)
  To: Luis Machado, Alan Hayward
  Cc: gdb-patches\@sourceware.org, david.spickett, Catalin Marinas, nd

On 5/21/21 8:30 AM, Luis Machado wrote:
> Hi Alan,
> 
> On 5/21/21 12:12 PM, Alan Hayward wrote:
>>
>>
>>> On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>>>
>>> Teach GDB how to dump memory tags when using the gcore command and how
>>> to read them back from a core file generated via gcore or the kernel.
>>>
>>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>>> own NT_MEMTAG note. A section named ".memtag" is created for each of those
>>> when reading the core file back.
>>>
>>> Dumping memory tags
>>> -
>>>
>>> When using the gcore command to dump a core file, GDB will go through the maps
>>> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
>>> passed to an arch-specific gdbarch hook that generates a vector of blobs of
>>> memory tag data that are blindly put into a NT_MEMTAG note.
>>>
>>> The vector is used because we may have, in the future,  multiple tag types for
>>> a particular memory range.
>>>
>>> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
>>> like so:
>>>
>>> struct tag_dump_header
>>> {
>>>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>>    uint64_t start_vma;
>>>    uint64_t end_vma;
>>> };
>>>
>>> struct tag_dump_mte
>>> {
>>>    uint16_t granule_byte_size;
>>>    uint16_t tag_bit_size;
>>>    uint16_t __unused;
>>> };
>>>
>>> The only bits meant to be generic are the tag_dump_format, start_vma and
>>> end_vma fields.
>>>
>>> The format-specific data is supposed to be opaque and only useful for the
>>> arch-specific code.
>>>
>>> We can extend the format in the future to make room for other memory tag
>>> layouts.
>>>
>>
>> This is a wider question than this patch - but is there someplace this format will
>> be documented? Ideally either Linux or GDB needs something written down in
>> text format.
>>
>> A few nits below too, but mostly looks fine.
> 
> Yes. I'm still thinking whether this should go into the Linux Kernel's
> documentation or somewhere in the GDB manual.
> 
> Given GDB reads core files generated by the Linux Kernel and also
> creates cores files itself, I'm thinking it may be best to put it in the
> GDB manual.
> 
> I want to wait for possible feedbacks about the format before we
> document how it will look like in the manual.

Also, NT_MEMTAG is not inherently OS specific and I plan to reuse whatever
format is used here for CHERI tags on RISC-V and Morello in CheriBSD (a
CHERI-aware version of FreeBSD).  If someone adds MTE support in FreeBSD
that would use the same format as well.  To that end, I think we should
aim to have the tag "type" values be OS independent (so just depending
on the arch).  Having an actual value of NT_MEMTAG that is portable is
harder as various OS's already have conflicting NT_* namespaces.  If we
wanted something truly independent we could perhaps choose a neutral
note name (e.g. "memtag" instead of "Linux", "CORE", or "FreeBSD") and
then the note type could instead take the place of the type of tag,
but that may be overly complicated compared to just having OS-specific
NT_MEMTAG values.

-- 
John Baldwin

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-21 15:30   ` Luis Machado
  2021-05-21 17:20     ` John Baldwin
@ 2021-05-24  8:07     ` Alan Hayward
  2021-05-24 12:45       ` Luis Machado
  1 sibling, 1 reply; 47+ messages in thread
From: Alan Hayward @ 2021-05-24  8:07 UTC (permalink / raw)
  To: Luis Machado
  Cc: gdb-patches\@sourceware.org, jhb@freebsd.org, david.spickett,
	Catalin Marinas, nd



On 21 May 2021, at 16:30, Luis Machado <luis.machado@linaro.org<mailto:luis.machado@linaro.org>> wrote:

Hi Alan,

On 5/21/21 12:12 PM, Alan Hayward wrote:
On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org<mailto:luis.machado@linaro.org>> wrote:

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
 uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
 uint64_t start_vma;
 uint64_t end_vma;
};

struct tag_dump_mte
{
 uint16_t granule_byte_size;
 uint16_t tag_bit_size;
 uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

This is a wider question than this patch - but is there someplace this format will
be documented? Ideally either Linux or GDB needs something written down in
text format.
A few nits below too, but mostly looks fine.

Yes. I'm still thinking whether this should go into the Linux Kernel's documentation or somewhere in the GDB manual.

Given GDB reads core files generated by the Linux Kernel and also creates cores files itself, I'm thinking it may be best to put it in the GDB manual.

I want to wait for possible feedbacks about the format before we document how it will look like in the manual.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* aarch64-linux-tdep.c: Include elf/common.h.
(MAX_TAGS_TO_TRANSFER): New constant.
(aarch64_linux_create_memtag_notes_from_range): New function.
(aarch64_linux_decode_memtag_note): Likewise.
(aarch64_linux_init_abi): Register new core file hooks.
* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
(struct tag_dump_header): New struct.
(struct tag_dump_mte): New struct.
(MEMTAG_NOTE_HEADER_SIZE): New constant.
* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
method overrides.
* gdbarch.c: Regenerate.
* gdbarch.h: Likewise.
* gdbarch.sh (create_memtag_notes_from_range): New hook.
(decode_memtag_note): Likewise.
* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
(linux_process_address_in_memtag_page): ... this.
(linux_core_file_address_in_memtag_page): New function.
(linux_address_in_memtag_page): Likewise.
(linux_make_memtag_corefile_notes): Likewise.
(linux_make_corefile_notes): Handle memory tag notes.
* NEWS: Mention core file support for memory tagging.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* gdb.arch/aarch64-mte-gcore.c: New file.
* gdb.arch/aarch64-mte-gcore.exp: New file.
---
gdb/NEWS                                     |   4 +
gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
gdb/arch/aarch64-mte-linux.h                 |  29 +++
gdb/corelow.c                                |  88 +++++++++
gdb/doc/gdb.texinfo                          |   4 +
gdb/gdbarch.c                                |  64 +++++++
gdb/gdbarch.h                                |  16 ++
gdb/gdbarch.sh                               |   6 +
gdb/linux-tdep.c                             | 141 ++++++++++++++-
gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
11 files changed, 736 insertions(+), 3 deletions(-)
create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@

*** Changes since GDB 10

+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
* GDB now supports general memory tagging functionality if the underlying
  architecture supports the proper primitives and hooks.  Currently this is
  enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..663d0e1a215 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -53,6 +53,8 @@

#include "gdbsupport/selftest.h"

+#include "elf/common.h"
+
/* Signal frame handling.

      +------------+  ^
@@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
    }
}

+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+       CORE_ADDR start_address,
+       CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  int granules = aarch64_mte_get_tag_granules (start_address,
+        end_address - start_address,
+        AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+ = (granules >= MAX_TAGS_TO_TRANSFER)?
+   MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+   granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+  static_cast<int> (memtag_type::allocation)))
+ {
+   warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+      phex_nz (start_address, sizeof (start_address)),
+      phex_nz (end_address, sizeof (end_address)));
+   notes.resize (0);
+   return notes;
+ }
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+   NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (uint16_t);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
+  buf += sizeof (uint64_t);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
+  buf += sizeof (uint64_t);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+   AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+   AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
+   tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the request tags.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+   gdb::array_view <const gdb_byte> note,
+   CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  struct tag_dump_header header;
+  const gdb_byte *buf = note.data ();
+
+  header.format
+    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
+  buf += sizeof (uint16_t);
+
+  header.start_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  header.end_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  /* Sanity check  */
+  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  int skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+     aligned_address - aligned_start_address,
+     AARCH64_MTE_GRANULE_SIZE);
+
+  /* The amount of memory tag granules we need to fetch.  */
+  int granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb_byte *tags_data = note.data () + sizeof (header) + skipped_granules;
+
+  /* Read the tag granules.  */
+  for (unsigned int i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
static void
aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
@@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)

      set_gdbarch_report_signal_info (gdbarch,
      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+  addresses.  */
+      set_gdbarch_create_memtag_notes_from_range (gdbarch,
+   aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+       aarch64_linux_decode_memtag_note);
    }

  /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..a4a26628847 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@

/* We have one tag per 16 bytes of memory.  */
#define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
#define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
#define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf

@@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
   It is always possible to get the logical tag.  */
extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);

+/* Headers for the NT_MEMTAG notes.  */
+
+/* Generic NT_MEMTAG header.  */
+struct tag_dump_header
+{
+  /* Tag format.  */
+  uint16_t format;
+  /* Start address of the tagged range.  */
+  uint64_t start_vma;
+  /* End address of the tagged range.  */
+  uint64_t end_vma;
+};
+
+/* MTE-specific NT_MEMTAG header.  */
+struct tag_dump_mte
+{
+  /* Size of the tag granule in bytes.  */
+  uint16_t granule_byte_size;
+  /* Size of the tag in bits.  */
+  uint16_t tag_bit_size;
+  /* Reserved field for the future.  */
+  uint16_t __unused;
+};
+
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
This should compile down to a constant value: ?
#define MEMTAG_NOTE_HEADER_SIZE (sizeof(tag_dump_header)+sizeof(tag_dump_mte))

Right. But there may be padding between the members of the struct, and we don't want to rely on what the compiler is doing. We could force this to be "packed" maybe?

Should the data inside the core file be packed?
Packed saves the space, but padded ensures the file is aligned and maybe easier to copy.


+
#endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..33d16b7220a 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -100,6 +100,13 @@ class core_target final : public process_stratum_target

  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;
+
  /* A few helpers.  */

  /* Getter, see variable definition.  */
@@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, enum info_proc_what request)
  return true;
}

+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
+    return false;
+
+  return true;
+}
+
+/* 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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    warning (_("gdbarch_decode_memtag_note not implemented for this "
+        "architecture."));
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Remove the top byte for the memory range check.  */
+  address = address_significant (gdbarch, address);
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+  is malformed.  */
+      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
I’m not sure why 64+16 ?

Actually 2 * 8 + 2. Two 64-bit entries and a 16-bit entry in the generic header.

Does that make sense?

I completely missed the 2.
Does this need a #define TAG_NOTE_SIZE then? (And use it in the
MEMTAG_NOTE_HEADER_SIZE)


+ {
+   warning (_("malformed core note - too short for header"));
+   return false;
+ }
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+      note.data (), 0, note_size))
+ {
+   warning (_("could not get core note contents."));
+   return false;
+ }
+
+      /* Read the generic header of the note.  Thos contains the format,
+  start address and end address.  */
+      uint64_t start_address
+ = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+ = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+ + sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+   && address < end_address)
+ {
+   /* Decode the memory tag note and return the tags.  */
+   tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
+   return true;
+ }
+
+      /* The requested address lies outside this particular memtag note.  Keep
+  looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
/* Get a pointer to the current core target.  If not connected to a
   core target, return NULL.  */

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 56f37eb2288..738cb3b56eb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
documentation in the Linux kernel.

+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
@node i386
@subsection x86 Architecture-specific Issues

diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
  gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
  gdbarch_make_corefile_notes_ftype *make_corefile_notes;
  gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
  gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
  gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
  gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
  /* Skip verify of iterate_over_regset_sections, has predicate.  */
  /* Skip verify of make_corefile_notes, has predicate.  */
  /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
  /* Skip verify of core_xfer_shared_libraries, has predicate.  */
  /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
  /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
  fprintf_unfiltered (file,
                      "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                      host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
  fprintf_unfiltered (file,
                      "gdbarch_dump: decr_pc_after_break = %s\n",
                      core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
  gdbarch->find_memory_regions = find_memory_regions;
}

+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
bool
gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
{
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);

+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
/* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
   core file into buffer READBUF with length LEN.  Return the number of bytes read
   (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
# Find core file memory regions
M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data

+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
# Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
# core file into buffer READBUF with length LEN.  Return the number of bytes read
# (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..f4ff57dec87 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
  return smaps;
}

-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */

-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
{
  if (current_inferior ()->fake_pid_p)
    return false;
@@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
  return false;
}

+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+  is malformed.  */
+      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
+ {
+   warning (_("malformed core note - too short for header"));
+   return false;
+ }
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+      note.data (), 0, note_size))
+ {
+   warning (_("could not get core note contents."));
+   return false;
+ }
+
+      /* Read the generic header of the note.  Those contain the format,
+  start address and end address.  */
+      uint64_t start_address
+ = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+ = bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+ + sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+   && address < end_address)
+ return true;
+
+      /* The requested address lies outside this particular memtag note.  Keep
+  looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+   gdb::unique_xmalloc_ptr<char> &note_data,
+   int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  std::vector<struct smaps_data> smaps;
+
+  /* Parse the contents of smaps into a vector.  */
+  smaps = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+  memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+ continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+  this particular memory range, including the header.
+
+  If the notes are too big, we may need to break up the transfer
+  into smaller chunks.
+
+  If the architecture returns an empty vector, that means there are
+  no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes;
+      memory_tag_notes
+ = gdbarch_create_memtag_notes_from_range (gdbarch,
+   map.start_address,
+   map.end_address);
+      /* Write notes to the core file.  */
+      for (gdb::byte_vector note : memory_tag_notes)
+ {
+   note_data.reset (elfcore_write_note (obfd, note_data.release (),
+        note_size, "CORE",
+        NT_MEMTAG, note.data (),
+        note.size ()));
+ }
+    }
+}
+
/* List memory regions in the inferior for a corefile.  */

static int
@@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
return NULL;
    }

+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
  /* File mappings.  */
  linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);

diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE (1UL << 0)
+#define PR_MTE_TCF_SHIFT 1
+#define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT 3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+      PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+      | (0xfffe << PR_MTE_TAG_SHIFT),
+      0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..bb529a8b369
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,115 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
2018?

I really copied the template from an existing file. The rule, AFAIR, is to keep the copyright years from the original file.

Ok


+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+      $atag_msg \
+      "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+ "Program received signal SIGSEGV, Segmentation fault" \
+ "Memory tag violation while accessing address $hex" \
+ "Allocation tag $hex" \
+ "Logical tag $hex\." \
+ "$hex in access_memory \\(.*\\) at .*" \
+ ".*tagged_ptr\\\[0\\\] = 'a';"] \
+  "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart
+
+# Load the program file.
+set program_filename [standard_output_file $testfile]
+set program_loaded [gdb_file_cmd $program_filename]
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+ "Core was generated by.*\." \
+ "Program terminated with signal SIGSEGV, Segmentation fault" \
+ "Memory tag violation while accessing address $hex" \
+ "Allocation tag 0xf" \
+ "Logical tag 0x0\." \
+ "#0.*$hex in access_memory \\(.*\\) at .*" \
+ ".*tagged_ptr\\\[0\\\] = 'a';"] \
+  "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+  "tag_ctl.*$hex.*${::decimal}" \
+  "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+      "= 0x$tag" \
+      "memory tag of &tagged_ptr\[$index\] is correct"
+}
--
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-24  8:07     ` Alan Hayward
@ 2021-05-24 12:45       ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-24 12:45 UTC (permalink / raw)
  To: Alan Hayward
  Cc: gdb-patches\@sourceware.org, jhb@freebsd.org, david.spickett,
	Catalin Marinas, nd

On 5/24/21 5:07 AM, Alan Hayward wrote:
> 
> 
>> On 21 May 2021, at 16:30, Luis Machado <luis.machado@linaro.org 
>> <mailto:luis.machado@linaro.org>> wrote:
>>
>> Hi Alan,
>>
>> On 5/21/21 12:12 PM, Alan Hayward wrote:
>>>> On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org 
>>>> <mailto:luis.machado@linaro.org>> wrote:
>>>>
>>>> Teach GDB how to dump memory tags when using the gcore command and how
>>>> to read them back from a core file generated via gcore or the kernel.
>>>>
>>>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped 
>>>> to its
>>>> own NT_MEMTAG note. A section named ".memtag" is created for each of 
>>>> those
>>>> when reading the core file back.
>>>>
>>>> Dumping memory tags
>>>> -
>>>>
>>>> When using the gcore command to dump a core file, GDB will go 
>>>> through the maps
>>>> in /proc/<pid>/smaps looking for tagged ranges. Each of those 
>>>> entries gets
>>>> passed to an arch-specific gdbarch hook that generates a vector of 
>>>> blobs of
>>>> memory tag data that are blindly put into a NT_MEMTAG note.
>>>>
>>>> The vector is used because we may have, in the future,  multiple tag 
>>>> types for
>>>> a particular memory range.
>>>>
>>>> Each of the NT_MEMTAG notes have a generic header and a 
>>>> arch-specific header,
>>>> like so:
>>>>
>>>> struct tag_dump_header
>>>> {
>>>>  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>>>  uint64_t start_vma;
>>>>  uint64_t end_vma;
>>>> };
>>>>
>>>> struct tag_dump_mte
>>>> {
>>>>  uint16_t granule_byte_size;
>>>>  uint16_t tag_bit_size;
>>>>  uint16_t __unused;
>>>> };
>>>>
>>>> The only bits meant to be generic are the tag_dump_format, start_vma and
>>>> end_vma fields.
>>>>
>>>> The format-specific data is supposed to be opaque and only useful 
>>>> for the
>>>> arch-specific code.
>>>>
>>>> We can extend the format in the future to make room for other memory tag
>>>> layouts.
>>>>
>>> This is a wider question than this patch - but is there someplace 
>>> this format will
>>> be documented? Ideally either Linux or GDB needs something written 
>>> down in
>>> text format.
>>> A few nits below too, but mostly looks fine.
>>
>> Yes. I'm still thinking whether this should go into the Linux Kernel's 
>> documentation or somewhere in the GDB manual.
>>
>> Given GDB reads core files generated by the Linux Kernel and also 
>> creates cores files itself, I'm thinking it may be best to put it in 
>> the GDB manual.
>>
>> I want to wait for possible feedbacks about the format before we 
>> document how it will look like in the manual.
>>
>>>> Reading memory tags
>>>> -
>>>>
>>>> When reading a core file that contains NT_MEMTAG entries, GDB will use
>>>> a different approach to check for tagged memory range. Rather than 
>>>> looking
>>>> at /proc/<pid>/smaps, it will now look for ".memtag" sections with 
>>>> the right
>>>> memory range.
>>>>
>>>> When reading tags, GDB will now use the core target's implementation of
>>>> fetch_memtags (store_memtags doesn't exist for core targets). Then 
>>>> the data
>>>> is fed into an arch-specific hook that will decode the memory tag 
>>>> format and
>>>> return a vector of tags.
>>>>
>>>> I've added a test to exercise writing and reading of memory tags in core
>>>> files.
>>>>
>>>> gdb/ChangeLog:
>>>>
>>>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org 
>>>> <mailto:luis.machado@linaro.org>>
>>>>
>>>> * aarch64-linux-tdep.c: Include elf/common.h.
>>>> (MAX_TAGS_TO_TRANSFER): New constant.
>>>> (aarch64_linux_create_memtag_notes_from_range): New function.
>>>> (aarch64_linux_decode_memtag_note): Likewise.
>>>> (aarch64_linux_init_abi): Register new core file hooks.
>>>> * arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
>>>> (struct tag_dump_header): New struct.
>>>> (struct tag_dump_mte): New struct.
>>>> (MEMTAG_NOTE_HEADER_SIZE): New constant.
>>>> * corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
>>>> method overrides.
>>>> * gdbarch.c: Regenerate.
>>>> * gdbarch.h: Likewise.
>>>> * gdbarch.sh (create_memtag_notes_from_range): New hook.
>>>> (decode_memtag_note): Likewise.
>>>> * linux-tdep.c (linux_address_in_memtag_page): Renamed to...
>>>> (linux_process_address_in_memtag_page): ... this.
>>>> (linux_core_file_address_in_memtag_page): New function.
>>>> (linux_address_in_memtag_page): Likewise.
>>>> (linux_make_memtag_corefile_notes): Likewise.
>>>> (linux_make_corefile_notes): Handle memory tag notes.
>>>> * NEWS: Mention core file support for memory tagging.
>>>>
>>>> gdb/doc/ChangeLog:
>>>>
>>>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org 
>>>> <mailto:luis.machado@linaro.org>>
>>>>
>>>> * gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>>>> for memory tagging in core files.
>>>>
>>>> gdb/testsuite/ChangeLog:
>>>>
>>>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org 
>>>> <mailto:luis.machado@linaro.org>>
>>>>
>>>> * gdb.arch/aarch64-mte-gcore.c: New file.
>>>> * gdb.arch/aarch64-mte-gcore.exp: New file.
>>>> ---
>>>> gdb/NEWS                                     |   4 +
>>>> gdb/aarch64-linux-tdep.c                     | 179 +++++++++++++++++++
>>>> gdb/arch/aarch64-mte-linux.h                 |  29 +++
>>>> gdb/corelow.c                                |  88 +++++++++
>>>> gdb/doc/gdb.texinfo                          |   4 +
>>>> gdb/gdbarch.c                                |  64 +++++++
>>>> gdb/gdbarch.h                                |  16 ++
>>>> gdb/gdbarch.sh                               |   6 +
>>>> gdb/linux-tdep.c                             | 141 ++++++++++++++-
>>>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++++
>>>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 ++++++++++++
>>>> 11 files changed, 736 insertions(+), 3 deletions(-)
>>>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>>>
>>>> diff --git a/gdb/NEWS b/gdb/NEWS
>>>> index ab678acec8b..58b9f739d4f 100644
>>>> --- a/gdb/NEWS
>>>> +++ b/gdb/NEWS
>>>> @@ -3,6 +3,10 @@
>>>>
>>>> *** Changes since GDB 10
>>>>
>>>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It 
>>>> also supports
>>>> +  reading memory tag data for AArch64 MTE from core files generated by
>>>> +  the gcore command or the Linux kernel.
>>>> +
>>>> * GDB now supports general memory tagging functionality if the 
>>>> underlying
>>>>   architecture supports the proper primitives and hooks.  Currently 
>>>> this is
>>>>   enabled only for AArch64 MTE.
>>>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>>>> index e9761ed2189..663d0e1a215 100644
>>>> --- a/gdb/aarch64-linux-tdep.c
>>>> +++ b/gdb/aarch64-linux-tdep.c
>>>> @@ -53,6 +53,8 @@
>>>>
>>>> #include "gdbsupport/selftest.h"
>>>>
>>>> +#include "elf/common.h"
>>>> +
>>>> /* Signal frame handling.
>>>>
>>>>       +------------+  ^
>>>> @@ -1779,6 +1781,172 @@ aarch64_linux_report_signal_info (struct 
>>>> gdbarch *gdbarch,
>>>>     }
>>>> }
>>>>
>>>> +/* Maximum number of tags to request.  */
>>>> +#define MAX_TAGS_TO_TRANSFER 1024
>>>> +
>>>> +/* AArch64 Linux implementation of the 
>>>> aarch64_create_memtag_notes_from_range
>>>> +   gdbarch hook.  Create core file notes for memory tags.  */
>>>> +
>>>> +static std::vector<gdb::byte_vector>
>>>> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>>>> +     CORE_ADDR start_address,
>>>> +     CORE_ADDR end_address)
>>>> +{
>>>> +  /* We only handle MTE tags for now.  */
>>>> +
>>>> +  /* Figure out how many tags we need to store in this memory 
>>>> range.  */
>>>> +  int granules = aarch64_mte_get_tag_granules (start_address,
>>>> +      end_address - start_address,
>>>> +      AARCH64_MTE_GRANULE_SIZE);
>>>> +
>>>> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE 
>>>> tags
>>>> +     at the moment).  */
>>>> +  std::vector<gdb::byte_vector> notes (1);
>>>> +
>>>> +  /* If there are no tag granules to fetch, just return.  */
>>>> +  if (granules == 0)
>>>> +    return notes;
>>>> +
>>>> +  /* Adjust the MTE note size to hold the header + tags.  */
>>>> +  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
>>>> +
>>>> +  CORE_ADDR address = start_address;
>>>> +  /* Vector of tags.  */
>>>> +  gdb::byte_vector tags;
>>>> +
>>>> +  while (granules > 0)
>>>> +    {
>>>> +      /* Transfer tags in chunks.  */
>>>> +      gdb::byte_vector tags_read;
>>>> +      size_t xfer_len
>>>> += (granules >= MAX_TAGS_TO_TRANSFER)?
>>>> + MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>>>> + granules * AARCH64_MTE_GRANULE_SIZE;
>>>> +
>>>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>>>> +static_cast<int> (memtag_type::allocation)))
>>>> +{
>>>> + warning (_("Failed to read MTE tags from memory range [%s,%s]."),
>>>> +    phex_nz (start_address, sizeof (start_address)),
>>>> +    phex_nz (end_address, sizeof (end_address)));
>>>> + notes.resize (0);
>>>> + return notes;
>>>> +}
>>>> +
>>>> +      /* Transfer over the tags that have been read.  */
>>>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>>>> +
>>>> +      /* Adjust the remaining granules and starting address.  */
>>>> +      granules -= tags_read.size ();
>>>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>>>> +    }
>>>> +
>>>> +  /* Create the header.  */
>>>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>>>> +  gdb_byte *buf = notes[0].data ();
>>>> +
>>>> +  /* Generic header.  */
>>>> +  /* Tag dump format */
>>>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>>>> + NT_MEMTAG_TYPE_AARCH_MTE);
>>>> +  buf += sizeof (uint16_t);
>>>> +
>>>> +  /* Start address */
>>>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, 
>>>> start_address);
>>>> +  buf += sizeof (uint64_t);
>>>> +
>>>> +  /* End address */
>>>> +  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, 
>>>> end_address);
>>>> +  buf += sizeof (uint64_t);
>>>> +
>>>> +  /* MTE-specific header.  */
>>>> +  /* Granule byte size */
>>>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>>>> + AARCH64_MTE_GRANULE_SIZE);
>>>> +  buf += sizeof (uint16_t);
>>>> +
>>>> +  /* Tag bit size */
>>>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
>>>> + AARCH64_MTE_TAG_BIT_SIZE);
>>>> +  buf += sizeof (uint16_t);
>>>> +
>>>> +  /* Unused value */
>>>> +  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
>>>> +
>>>> +  /* Store the tags.  */
>>>> +  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
>>>> + tags.size ());
>>>> +
>>>> +  return notes;
>>>> +}
>>>> +
>>>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>>>> +   hook.  Decode a memory tag note and return the request tags.  */
>>>> +
>>>> +static gdb::byte_vector
>>>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>>>> + gdb::array_view <const gdb_byte> note,
>>>> + CORE_ADDR address, size_t length)
>>>> +{
>>>> +  gdb::byte_vector tags;
>>>> +
>>>> +  /* Read the generic header.  */
>>>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>>>> +  struct tag_dump_header header;
>>>> +  const gdb_byte *buf = note.data ();
>>>> +
>>>> +  header.format
>>>> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
>>>> +  buf += sizeof (uint16_t);
>>>> +
>>>> +  header.start_vma
>>>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>>>> +  buf += sizeof (uint64_t);
>>>> +
>>>> +  header.end_vma
>>>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>>>> +  buf += sizeof (uint64_t);
>>>> +
>>>> +  /* Sanity check  */
>>>> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
>>>> +    {
>>>> +      warning (_("Unexpected memory tag note format (%x).\n"), 
>>>> header.format);
>>>> +      return tags;
>>>> +    }
>>>> +
>>>> +  /* Calculate how many granules we need to skip to get to the 
>>>> granule of
>>>> +     ADDRESS.  Align both the start address and the requested address
>>>> +     so it is easier to get the number of granules to skip.  This 
>>>> way we
>>>> +     don't need to consider cases where ADDRESS falls in the middle 
>>>> of a
>>>> +     tag granule range.  */
>>>> +  CORE_ADDR aligned_start_address
>>>> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
>>>> +  CORE_ADDR aligned_address = align_down (address, 
>>>> AARCH64_MTE_GRANULE_SIZE);
>>>> +
>>>> +  int skipped_granules
>>>> +    = aarch64_mte_get_tag_granules (aligned_start_address,
>>>> +   aligned_address - aligned_start_address,
>>>> +   AARCH64_MTE_GRANULE_SIZE);
>>>> +
>>>> +  /* The amount of memory tag granules we need to fetch.  */
>>>> +  int granules
>>>> +    = aarch64_mte_get_tag_granules (address, length, 
>>>> AARCH64_MTE_GRANULE_SIZE);
>>>> +
>>>> +  /* If there are no tag granules to decode, just return.  */
>>>> +  if (granules == 0)
>>>> +    return tags;
>>>> +
>>>> +  /* Point to the block of data that contains the first granule we are
>>>> +     interested in.  */
>>>> +  const gdb_byte *tags_data = note.data () + sizeof (header) + 
>>>> skipped_granules;
>>>> +
>>>> +  /* Read the tag granules.  */
>>>> +  for (unsigned int i = 0; i < granules; i++)
>>>> +      tags.push_back (tags_data[i]);
>>>> +
>>>> +  return tags;
>>>> +}
>>>> +
>>>> static void
>>>> aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch 
>>>> *gdbarch)
>>>> {
>>>> @@ -1862,6 +2030,17 @@ aarch64_linux_init_abi (struct gdbarch_info 
>>>> info, struct gdbarch *gdbarch)
>>>>
>>>>       set_gdbarch_report_signal_info (gdbarch,
>>>>      aarch64_linux_report_signal_info);
>>>> +
>>>> +      /* Core file helpers.  */
>>>> +
>>>> +      /* Core file helper to create memory tag notes for a 
>>>> particular range of
>>>> +addresses.  */
>>>> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
>>>> + aarch64_linux_create_memtag_notes_from_range);
>>>> +
>>>> +      /* Core file helper to decode a memory tag note.  */
>>>> +      set_gdbarch_decode_memtag_note (gdbarch,
>>>> +     aarch64_linux_decode_memtag_note);
>>>>     }
>>>>
>>>>   /* Initialize the aarch64_linux_record_tdep.  */
>>>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>>>> index 2aa97eb861a..a4a26628847 100644
>>>> --- a/gdb/arch/aarch64-mte-linux.h
>>>> +++ b/gdb/arch/aarch64-mte-linux.h
>>>> @@ -32,6 +32,7 @@
>>>>
>>>> /* We have one tag per 16 bytes of memory.  */
>>>> #define AARCH64_MTE_GRANULE_SIZE 16
>>>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>>> #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>>> #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>>>
>>>> @@ -71,4 +72,32 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR 
>>>> address, CORE_ADDR tag);
>>>>    It is always possible to get the logical tag.  */
>>>> extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>>>
>>>> +/* Headers for the NT_MEMTAG notes.  */
>>>> +
>>>> +/* Generic NT_MEMTAG header.  */
>>>> +struct tag_dump_header
>>>> +{
>>>> +  /* Tag format.  */
>>>> +  uint16_t format;
>>>> +  /* Start address of the tagged range.  */
>>>> +  uint64_t start_vma;
>>>> +  /* End address of the tagged range.  */
>>>> +  uint64_t end_vma;
>>>> +};
>>>> +
>>>> +/* MTE-specific NT_MEMTAG header.  */
>>>> +struct tag_dump_mte
>>>> +{
>>>> +  /* Size of the tag granule in bytes.  */
>>>> +  uint16_t granule_byte_size;
>>>> +  /* Size of the tag in bits.  */
>>>> +  uint16_t tag_bit_size;
>>>> +  /* Reserved field for the future.  */
>>>> +  uint16_t __unused;
>>>> +};
>>>> +
>>>> +/* Memory tag note header size.  Includes both the generic and the
>>>> +   arch-specific parts.  */
>>>> +#define MEMTAG_NOTE_HEADER_SIZE (2 + 8 + 8 + 2 + 2 + 2)
>>> This should compile down to a constant value: ?
>>> #define MEMTAG_NOTE_HEADER_SIZE 
>>> (sizeof(tag_dump_header)+sizeof(tag_dump_mte))
>>
>> Right. But there may be padding between the members of the struct, and 
>> we don't want to rely on what the compiler is doing. We could force 
>> this to be "packed" maybe?
> 
> Should the data inside the core file be packed?
> Packed saves the space, but padded ensures the file is aligned and maybe 
> easier to copy. >

Packing it makes the layout predictable. With padding, we may have 
differing layouts between a 32-bit target and a 64-bit target. I wanted 
to avoid that sort of thing. At least on the generic header that is used 
by GDB's core target.

>>
>>>> +
>>>> #endif /* ARCH_AARCH64_LINUX_H */
>>>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>>>> index 452b4dd4f9a..33d16b7220a 100644
>>>> --- a/gdb/corelow.c
>>>> +++ b/gdb/corelow.c
>>>> @@ -100,6 +100,13 @@ class core_target final : public 
>>>> process_stratum_target
>>>>
>>>>   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;
>>>> +
>>>>   /* A few helpers.  */
>>>>
>>>>   /* Getter, see variable definition.  */
>>>> @@ -1115,6 +1122,87 @@ core_target::info_proc (const char *args, 
>>>> enum info_proc_what request)
>>>>   return true;
>>>> }
>>>>
>>>> +/* Implementation of the "supports_memory_tagging" target_ops 
>>>> method.  */
>>>> +
>>>> +bool
>>>> +core_target::supports_memory_tagging ()
>>>> +{
>>>> +  /* Look for memory tag notes.  If they exist, that means this 
>>>> core file
>>>> +     supports memory tagging.  */
>>>> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
>>>> +    return false;
>>>> +
>>>> +  return true;
>>>> +}
>>>> +
>>>> +/* 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)
>>>> +{
>>>> +  struct gdbarch *gdbarch = target_gdbarch ();
>>>> +
>>>> +  /* Make sure we have a way to decode the memory tag notes.  */
>>>> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
>>>> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
>>>> +      "architecture."));
>>>> +
>>>> +  asection *section
>>>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>>>> +
>>>> +  /* Remove the top byte for the memory range check.  */
>>>> +  address = address_significant (gdbarch, address);
>>>> +
>>>> +  /* Go through all the memtag sections and figure out if ADDRESS
>>>> +     falls within one of the memory ranges that contain tags.  */
>>>> +  while (section != nullptr)
>>>> +    {
>>>> +      size_t note_size = bfd_section_size (section);
>>>> +
>>>> +      /* If the note is smaller than the size of the header, this 
>>>> core note
>>>> +is malformed.  */
>>>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>>> I’m not sure why 64+16 ?
>>
>> Actually 2 * 8 + 2. Two 64-bit entries and a 16-bit entry in the 
>> generic header.
>>
>> Does that make sense?
> 
> I completely missed the 2.
> Does this need a #define TAG_NOTE_SIZE then? (And use it in the
> MEMTAG_NOTE_HEADER_SIZE)
> 

It does. I'll add it. Thanks!

>>
>>>> +{
>>>> + warning (_("malformed core note - too short for header"));
>>>> + return false;
>>>> +}
>>>> +
>>>> +      gdb::byte_vector note (note_size);
>>>> +
>>>> +      /* Fetch the contents of this particular memtag note.  */
>>>> +      if (!bfd_get_section_contents (core_bfd, section,
>>>> +    note.data (), 0, note_size))
>>>> +{
>>>> + warning (_("could not get core note contents."));
>>>> + return false;
>>>> +}
>>>> +
>>>> +      /* Read the generic header of the note.  Thos contains the 
>>>> format,
>>>> +start address and end address.  */
>>>> +      uint64_t start_address
>>>> += bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>>>> +      uint64_t end_address
>>>> += bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>>>> ++ sizeof (uint64_t));
>>>> +
>>>> +      /* Is the address within [start_address, end_address)?  */
>>>> +      if (address >= start_address
>>>> + && address < end_address)
>>>> +{
>>>> + /* Decode the memory tag note and return the tags.  */
>>>> + tags = gdbarch_decode_memtag_note (gdbarch, note, address, len);
>>>> + return true;
>>>> +}
>>>> +
>>>> +      /* The requested address lies outside this particular memtag 
>>>> note.  Keep
>>>> +looking and get the next section.  */
>>>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>>>> +    }
>>>> +
>>>> +  return false;
>>>> +}
>>>> +
>>>> /* Get a pointer to the current core target.  If not connected to a
>>>>    core target, return NULL.  */
>>>>
>>>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>>>> index 56f37eb2288..738cb3b56eb 100644
>>>> --- a/gdb/doc/gdb.texinfo
>>>> +++ b/gdb/doc/gdb.texinfo
>>>> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime 
>>>> and emulates the @code{prctl}
>>>> option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>>>> documentation in the Linux kernel.
>>>>
>>>> +@value{GDBN} supports dumping memory tag data to core files through the
>>>> +@command{gcore} command and reading memory tag data from core files 
>>>> generated
>>>> +by the @command{gcore} command or the Linux kernel.
>>>> +
>>>> @node i386
>>>> @subsection x86 Architecture-specific Issues
>>>>
>>>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>>>> index 208cf4b5aaa..de384da2e9a 100644
>>>> --- a/gdb/gdbarch.c
>>>> +++ b/gdb/gdbarch.c
>>>> @@ -283,6 +283,8 @@ struct gdbarch
>>>>   gdbarch_iterate_over_regset_sections_ftype 
>>>> *iterate_over_regset_sections;
>>>>   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>>>   gdbarch_find_memory_regions_ftype *find_memory_regions;
>>>> +  gdbarch_create_memtag_notes_from_range_ftype 
>>>> *create_memtag_notes_from_range;
>>>> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>>>>   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>>>   gdbarch_core_xfer_shared_libraries_aix_ftype 
>>>> *core_xfer_shared_libraries_aix;
>>>>   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>>>> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>>>   /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>>>   /* Skip verify of make_corefile_notes, has predicate.  */
>>>>   /* Skip verify of find_memory_regions, has predicate.  */
>>>> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
>>>> +  /* Skip verify of decode_memtag_note, has predicate.  */
>>>>   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>>>   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>>>   /* Skip verify of core_pid_to_str, has predicate.  */
>>>> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct 
>>>> ui_file *file)
>>>>   fprintf_unfiltered (file,
>>>>                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>>>>                       host_address_to_string 
>>>> (gdbarch->core_xfer_siginfo));
>>>> +  fprintf_unfiltered (file,
>>>> +                      "gdbarch_dump: 
>>>> gdbarch_create_memtag_notes_from_range_p() = %d\n",
>>>> +                      gdbarch_create_memtag_notes_from_range_p 
>>>> (gdbarch));
>>>> +  fprintf_unfiltered (file,
>>>> +                      "gdbarch_dump: create_memtag_notes_from_range 
>>>> = <%s>\n",
>>>> +                      host_address_to_string 
>>>> (gdbarch->create_memtag_notes_from_range));
>>>> +  fprintf_unfiltered (file,
>>>> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() 
>>>> = %d\n",
>>>> +                      gdbarch_decode_memtag_note_p (gdbarch));
>>>> +  fprintf_unfiltered (file,
>>>> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
>>>> +                      host_address_to_string 
>>>> (gdbarch->decode_memtag_note));
>>>>   fprintf_unfiltered (file,
>>>>                       "gdbarch_dump: decr_pc_after_break = %s\n",
>>>>                       core_addr_to_string_nz 
>>>> (gdbarch->decr_pc_after_break));
>>>> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct 
>>>> gdbarch *gdbarch,
>>>>   gdbarch->find_memory_regions = find_memory_regions;
>>>> }
>>>>
>>>> +bool
>>>> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
>>>> +{
>>>> +  gdb_assert (gdbarch != NULL);
>>>> +  return gdbarch->create_memtag_notes_from_range != NULL;
>>>> +}
>>>> +
>>>> +std::vector<gdb::byte_vector>
>>>> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, 
>>>> CORE_ADDR start_address, CORE_ADDR end_address)
>>>> +{
>>>> +  gdb_assert (gdbarch != NULL);
>>>> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
>>>> +  if (gdbarch_debug >= 2)
>>>> +    fprintf_unfiltered (gdb_stdlog, 
>>>> "gdbarch_create_memtag_notes_from_range called\n");
>>>> +  return gdbarch->create_memtag_notes_from_range (gdbarch, 
>>>> start_address, end_address);
>>>> +}
>>>> +
>>>> +void
>>>> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>>>> + 
>>>>                                            gdbarch_create_memtag_notes_from_range_ftype 
>>>> create_memtag_notes_from_range)
>>>> +{
>>>> +  gdbarch->create_memtag_notes_from_range = 
>>>> create_memtag_notes_from_range;
>>>> +}
>>>> +
>>>> +bool
>>>> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
>>>> +{
>>>> +  gdb_assert (gdbarch != NULL);
>>>> +  return gdbarch->decode_memtag_note != NULL;
>>>> +}
>>>> +
>>>> +gdb::byte_vector
>>>> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, 
>>>> gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
>>>> +{
>>>> +  gdb_assert (gdbarch != NULL);
>>>> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
>>>> +  if (gdbarch_debug >= 2)
>>>> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note 
>>>> called\n");
>>>> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
>>>> +}
>>>> +
>>>> +void
>>>> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
>>>> +                                gdbarch_decode_memtag_note_ftype 
>>>> decode_memtag_note)
>>>> +{
>>>> +  gdbarch->decode_memtag_note = decode_memtag_note;
>>>> +}
>>>> +
>>>> bool
>>>> gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>>> {
>>>> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
>>>> index 7157e5596fd..80e244624de 100644
>>>> --- a/gdb/gdbarch.h
>>>> +++ b/gdb/gdbarch.h
>>>> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) 
>>>> (struct gdbarch *gdbarch, find_m
>>>> extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, 
>>>> find_memory_region_ftype func, void *data);
>>>> extern void set_gdbarch_find_memory_regions (struct gdbarch 
>>>> *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>>>
>>>> +/* Create memory tag core file notes given a range of addresses. */
>>>> +
>>>> +extern bool gdbarch_create_memtag_notes_from_range_p (struct 
>>>> gdbarch *gdbarch);
>>>> +
>>>> +typedef std::vector<gdb::byte_vector> 
>>>> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch 
>>>> *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
>>>> +extern std::vector<gdb::byte_vector> 
>>>> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, 
>>>> CORE_ADDR start_address, CORE_ADDR end_address);
>>>> +extern void set_gdbarch_create_memtag_notes_from_range (struct 
>>>> gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype 
>>>> *create_memtag_notes_from_range);
>>>> +
>>>> +/* Decode a memory tag note and return the tags that it contains. */
>>>> +
>>>> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
>>>> +
>>>> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct 
>>>> gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR 
>>>> address, size_t length);
>>>> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch 
>>>> *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, 
>>>> size_t length);
>>>> +extern void set_gdbarch_decode_memtag_note (struct gdbarch 
>>>> *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
>>>> +
>>>> /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared 
>>>> libraries list from
>>>>    core file into buffer READBUF with length LEN.  Return the number 
>>>> of bytes read
>>>>    (zero indicates failure).
>>>> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
>>>> index 43e51341f97..7c5eed0780c 100755
>>>> --- a/gdb/gdbarch.sh
>>>> +++ b/gdb/gdbarch.sh
>>>> @@ -745,6 +745,12 @@ 
>>>> M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int 
>>>> *note_size;ob
>>>> # Find core file memory regions
>>>> M;int;find_memory_regions;find_memory_region_ftype func, void 
>>>> *data;func, data
>>>>
>>>> +# Create memory tag core file notes given a range of addresses.
>>>> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR 
>>>> start_address, CORE_ADDR end_address;start_address, end_address
>>>> +
>>>> +# Decode a memory tag note and return the tags that it contains.
>>>> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const 
>>>> gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
>>>> +
>>>> # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared 
>>>> libraries list from
>>>> # core file into buffer READBUF with length LEN.  Return the number 
>>>> of bytes read
>>>> # (zero indicates failure).
>>>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>>>> index 927e69bf1e1..f4ff57dec87 100644
>>>> --- a/gdb/linux-tdep.c
>>>> +++ b/gdb/linux-tdep.c
>>>> @@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
>>>>   return smaps;
>>>> }
>>>>
>>>> -/* See linux-tdep.h.  */
>>>> +/* Helper that checks if an address is in a memory tag page for a live
>>>> +   process.  */
>>>>
>>>> -bool
>>>> -linux_address_in_memtag_page (CORE_ADDR address)
>>>> +static bool
>>>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>>> {
>>>>   if (current_inferior ()->fake_pid_p)
>>>>     return false;
>>>> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>>>   return false;
>>>> }
>>>>
>>>> +/* Helper that checks if an address is in a memory tag page for a 
>>>> core file
>>>> +   process.  */
>>>> +
>>>> +static bool
>>>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>>>> +{
>>>> +  if (core_bfd == nullptr)
>>>> +    return false;
>>>> +
>>>> +  asection *section
>>>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>>>> +
>>>> +  /* Go through all the memtag sections and figure out if ADDRESS
>>>> +     falls within one of the memory ranges that contain tags.  */
>>>> +  while (section != nullptr)
>>>> +    {
>>>> +      size_t note_size = bfd_section_size (section);
>>>> +
>>>> +      /* If the note is smaller than the size of the header, this 
>>>> core note
>>>> +is malformed.  */
>>>> +      if (note_size < 2 * sizeof (uint64_t) + sizeof (uint16_t))
>>>> +{
>>>> + warning (_("malformed core note - too short for header"));
>>>> + return false;
>>>> +}
>>>> +
>>>> +      gdb::byte_vector note (note_size);
>>>> +
>>>> +      /* Fetch the contents of this particular memtag note.  */
>>>> +      if (!bfd_get_section_contents (core_bfd, section,
>>>> +    note.data (), 0, note_size))
>>>> +{
>>>> + warning (_("could not get core note contents."));
>>>> + return false;
>>>> +}
>>>> +
>>>> +      /* Read the generic header of the note.  Those contain the 
>>>> format,
>>>> +start address and end address.  */
>>>> +      uint64_t start_address
>>>> += bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>>>> +      uint64_t end_address
>>>> += bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>>>> ++ sizeof (uint64_t));
>>>> +
>>>> +      /* Is the address within [start_address, end_address)?  */
>>>> +      if (address >= start_address
>>>> + && address < end_address)
>>>> +return true;
>>>> +
>>>> +      /* The requested address lies outside this particular memtag 
>>>> note.  Keep
>>>> +looking and get the next section.  */
>>>> +      section = bfd_get_next_section_by_name (core_bfd, section);
>>>> +    }
>>>> +
>>>> +  return false;
>>>> +}
>>>> +
>>>> +/* See linux-tdep.h.  */
>>>> +
>>>> +bool
>>>> +linux_address_in_memtag_page (CORE_ADDR address)
>>>> +{
>>>> +  if (!target_has_execution ())
>>>> +    return linux_core_file_address_in_memtag_page (address);
>>>> +
>>>> +  return linux_process_address_in_memtag_page (address);
>>>> +}
>>>> +
>>>> +/* For each memory map entry that has memory tagging enabled, 
>>>> create a new
>>>> +   core file note that contains all of its memory tags.  Save the 
>>>> data to
>>>> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
>>>> +
>>>> +static void
>>>> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
>>>> + gdb::unique_xmalloc_ptr<char> &note_data,
>>>> + int *note_size)
>>>> +{
>>>> +  if (current_inferior ()->fake_pid_p)
>>>> +    return;
>>>> +
>>>> +  /* If the architecture doesn't have a hook to return memory tag 
>>>> notes,
>>>> +     there is nothing left to do.  */
>>>> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
>>>> +    return;
>>>> +
>>>> +  pid_t pid = current_inferior ()->pid;
>>>> +
>>>> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
>>>> +
>>>> +  gdb::unique_xmalloc_ptr<char> data
>>>> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
>>>> +
>>>> +  if (data == nullptr)
>>>> +    return;
>>>> +
>>>> +  std::vector<struct smaps_data> smaps;
>>>> +
>>>> +  /* Parse the contents of smaps into a vector.  */
>>>> +  smaps = parse_smaps_data (data.get (), smaps_file);
>>>> +
>>>> +  for (const smaps_data &map : smaps)
>>>> +    {
>>>> +      /* Does this mapping have memory tagging enabled? If so, save the
>>>> +memory tags to the core file note.  */
>>>> +      if (map.vmflags.memory_tagging == 0)
>>>> +continue;
>>>> +
>>>> +      /* Ask the architecture to create (one or more) NT_MEMTAG 
>>>> notes for
>>>> +this particular memory range, including the header.
>>>> +
>>>> +If the notes are too big, we may need to break up the transfer
>>>> +into smaller chunks.
>>>> +
>>>> +If the architecture returns an empty vector, that means there are
>>>> +no memory tag notes to write.  */
>>>> +      std::vector<gdb::byte_vector> memory_tag_notes;
>>>> +      memory_tag_notes
>>>> += gdbarch_create_memtag_notes_from_range (gdbarch,
>>>> + map.start_address,
>>>> + map.end_address);
>>>> +      /* Write notes to the core file.  */
>>>> +      for (gdb::byte_vector note : memory_tag_notes)
>>>> +{
>>>> + note_data.reset (elfcore_write_note (obfd, note_data.release (),
>>>> +      note_size, "CORE",
>>>> +      NT_MEMTAG, note.data (),
>>>> +      note.size ()));
>>>> +}
>>>> +    }
>>>> +}
>>>> +
>>>> /* List memory regions in the inferior for a corefile.  */
>>>>
>>>> static int
>>>> @@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch 
>>>> *gdbarch, bfd *obfd, int *note_size)
>>>> return NULL;
>>>>     }
>>>>
>>>> +  /* Dump the memory tags, if any.  */
>>>> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, 
>>>> note_size);
>>>> +
>>>>   /* File mappings.  */
>>>>   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, 
>>>> note_size);
>>>>
>>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c 
>>>> b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>>> new file mode 100644
>>>> index 00000000000..b20ebcff424
>>>> --- /dev/null
>>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>>> @@ -0,0 +1,93 @@
>>>> +/* This test program is part of GDB, the GNU debugger.
>>>> +
>>>> +   Copyright 2021 Free Software Foundation, Inc.
>>>> +
>>>> +   This program is free software; you can redistribute it and/or modify
>>>> +   it under the terms of the GNU General Public License as published by
>>>> +   the Free Software Foundation; either version 3 of the License, or
>>>> +   (at your option) any later version.
>>>> +
>>>> +   This program is distributed in the hope that it will be useful,
>>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>> +   GNU General Public License for more details.
>>>> +
>>>> +   You should have received a copy of the GNU General Public License
>>>> +   along with this program.  If not, see 
>>>> <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.  */
>>>> +
>>>> +/* Exercise AArch64's Memory Tagging Extension with tagged 
>>>> pointers.  */
>>>> +
>>>> +/* This test was based on the documentation for the AArch64 Memory 
>>>> Tagging
>>>> +   Extension from the Linux Kernel, found in the sources in
>>>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>>>> +
>>>> +#include <errno.h>
>>>> +#include <stdio.h>
>>>> +#include <stdlib.h>
>>>> +#include <unistd.h>
>>>> +#include <sys/auxv.h>
>>>> +#include <sys/mman.h>
>>>> +#include <sys/prctl.h>
>>>> +
>>>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>>>> +#define HWCAP2_MTE              (1 << 18)
>>>> +
>>>> +/* From arch/arm64/include/uapi/asm/mman.h */
>>>> +#define PROT_MTE  0x20
>>>> +
>>>> +/* From include/uapi/linux/prctl.h */
>>>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>>>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>>>> +#define PR_TAGGED_ADDR_ENABLE(1UL << 0)
>>>> +#define PR_MTE_TCF_SHIFT1
>>>> +#define PR_MTE_TCF_SYNC(1UL << PR_MTE_TCF_SHIFT)
>>>> +#define PR_MTE_TAG_SHIFT3
>>>> +
>>>> +void
>>>> +access_memory (unsigned char *tagged_ptr)
>>>> +{
>>>> +  tagged_ptr[0] = 'a';
>>>> +}
>>>> +
>>>> +int
>>>> +main (int argc, char **argv)
>>>> +{
>>>> +  unsigned char *tagged_ptr;
>>>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>>>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>>>> +
>>>> +  /* Bail out if MTE is not supported.  */
>>>> +  if (!(hwcap2 & HWCAP2_MTE))
>>>> +    return 1;
>>>> +
>>>> +  /* Enable the tagged address ABI, synchronous MTE tag check 
>>>> faults and
>>>> +     allow all non-zero tags in the randomly generated set.  */
>>>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>>>> +    PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>>>> +    | (0xfffe << PR_MTE_TAG_SHIFT),
>>>> +    0, 0, 0))
>>>> +    {
>>>> +      perror ("prctl () failed");
>>>> +      return 1;
>>>> +    }
>>>> +
>>>> +  /* Create a mapping that will have PROT_MTE set.  */
>>>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>>>> +    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>>>> +  if (tagged_ptr == MAP_FAILED)
>>>> +    {
>>>> +      perror ("mmap () failed");
>>>> +      return 1;
>>>> +    }
>>>> +
>>>> +  /* Enable MTE on the above anonymous mmap.  */
>>>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | 
>>>> PROT_MTE))
>>>> +    {
>>>> +      perror ("mprotect () failed");
>>>> +      return 1;
>>>> +    }
>>>> +
>>>> +  access_memory (tagged_ptr);
>>>> +
>>>> +  return 0;
>>>> +}
>>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp 
>>>> b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>>> new file mode 100644
>>>> index 00000000000..bb529a8b369
>>>> --- /dev/null
>>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>>> @@ -0,0 +1,115 @@
>>>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>>> 2018?
>>
>> I really copied the template from an existing file. The rule, AFAIR, 
>> is to keep the copyright years from the original file.
> 
> Ok
> 
>>
>>>> +#
>>>> +# 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/ <http://www.gnu.org/licenses/>>.
>>>> +
>>>> +# This file is part of the gdb testsuite.
>>>> +
>>>> +# Test generating and reading a core file with MTE memory tags.
>>>> +
>>>> +if {![is_aarch64_target]} {
>>>> +    verbose "Skipping ${gdb_test_file_name}."
>>>> +    return
>>>> +}
>>>> +
>>>> +standard_testfile
>>>> +if { [prepare_for_testing "failed to prepare" ${testfile} 
>>>> ${srcfile}] } {
>>>> +    return -1
>>>> +}
>>>> +
>>>> +if ![runto_main] {
>>>> +    untested "could not run to main"
>>>> +    return -1
>>>> +}
>>>> +
>>>> +# Targets that don't support memory tagging should not execute the
>>>> +# runtime memory tagging tests.
>>>> +if {![supports_memtag]} {
>>>> +    unsupported "memory tagging unsupported"
>>>> +    return -1
>>>> +}
>>>> +
>>>> +gdb_breakpoint "access_memory"
>>>> +
>>>> +if [gdb_continue "access_memory"] {
>>>> +    return -1
>>>> +}
>>>> +
>>>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>>>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>>> +    set index [expr [expr 15 - $i] * 16]
>>>> +    set tag [format "%02x" $i]
>>>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 
>>>> $tag" \
>>>> +    $atag_msg \
>>>> +    "set memory tag of &tagged_ptr\[$index\] to $tag"
>>>> +}
>>>> +
>>>> +# Run until a crash and confirm GDB displays memory tag violation
>>>> +# information.
>>>> +gdb_test "continue" \
>>>> +    [multi_line \
>>>> +"Program received signal SIGSEGV, Segmentation fault" \
>>>> +"Memory tag violation while accessing address $hex" \
>>>> +"Allocation tag $hex" \
>>>> +"Logical tag $hex\." \
>>>> +"$hex in access_memory \\(.*\\) at .*" \
>>>> +".*tagged_ptr\\\[0\\\] = 'a';"] \
>>>> +"display tag violation information for live process"
>>>> +
>>>> +# Generate the core file.
>>>> +set core_filename [standard_output_file "$testfile.core"]
>>>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core 
>>>> file"]
>>>> +
>>>> +if { !$core_generated } {
>>>> +    return -1
>>>> +}
>>>> +
>>>> +clean_restart
>>>> +
>>>> +# Load the program file.
>>>> +set program_filename [standard_output_file $testfile]
>>>> +set program_loaded [gdb_file_cmd $program_filename]
>>>> +
>>>> +if { $program_loaded } {
>>>> +    return -1
>>>> +}
>>>> +
>>>> +# Load the core file and make sure we see the tag violation fault
>>>> +# information.
>>>> +gdb_test "core $core_filename" \
>>>> +    [multi_line \
>>>> +"Core was generated by.*\." \
>>>> +"Program terminated with signal SIGSEGV, Segmentation fault" \
>>>> +"Memory tag violation while accessing address $hex" \
>>>> +"Allocation tag 0xf" \
>>>> +"Logical tag 0x0\." \
>>>> +"#0.*$hex in access_memory \\(.*\\) at .*" \
>>>> +".*tagged_ptr\\\[0\\\] = 'a';"] \
>>>> +"core file shows tag violation information"
>>>> +
>>>> +# Make sure we have the tag_ctl register.
>>>> +gdb_test "info register tag_ctl" \
>>>> +"tag_ctl.*$hex.*${::decimal}" \
>>>> +"tag_ctl is available"
>>>> +
>>>> +# Check if the tag granules have the expected values.  If they do, that
>>>> +# means the core file saved the tags properly and GDB has read them
>>>> +# correctly.
>>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>>> +    set index [expr [expr 15 - $i] * 16]
>>>> +    set tag [format "%x" $i]
>>>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>>>> +    "= 0x$tag" \
>>>> +    "memory tag of &tagged_ptr\[$index\] is correct"
>>>> +}
>>>> --
>>>> 2.25.1
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH] [AArch64] MTE corefile support
  2021-05-21 17:20     ` John Baldwin
@ 2021-05-24 13:41       ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-24 13:41 UTC (permalink / raw)
  To: John Baldwin, Alan Hayward
  Cc: gdb-patches\@sourceware.org, david.spickett, Catalin Marinas, nd

Hi John,

On 5/21/21 2:20 PM, John Baldwin wrote:
> On 5/21/21 8:30 AM, Luis Machado wrote:
>> Hi Alan,
>>
>> On 5/21/21 12:12 PM, Alan Hayward wrote:
>>>
>>>
>>>> On 18 May 2021, at 21:20, Luis Machado <luis.machado@linaro.org> wrote:
>>>>
>>>> Teach GDB how to dump memory tags when using the gcore command and how
>>>> to read them back from a core file generated via gcore or the kernel.
>>>>
>>>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped 
>>>> to its
>>>> own NT_MEMTAG note. A section named ".memtag" is created for each of 
>>>> those
>>>> when reading the core file back.
>>>>
>>>> Dumping memory tags
>>>> -
>>>>
>>>> When using the gcore command to dump a core file, GDB will go 
>>>> through the maps
>>>> in /proc/<pid>/smaps looking for tagged ranges. Each of those 
>>>> entries gets
>>>> passed to an arch-specific gdbarch hook that generates a vector of 
>>>> blobs of
>>>> memory tag data that are blindly put into a NT_MEMTAG note.
>>>>
>>>> The vector is used because we may have, in the future,  multiple tag 
>>>> types for
>>>> a particular memory range.
>>>>
>>>> Each of the NT_MEMTAG notes have a generic header and a 
>>>> arch-specific header,
>>>> like so:
>>>>
>>>> struct tag_dump_header
>>>> {
>>>>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>>>    uint64_t start_vma;
>>>>    uint64_t end_vma;
>>>> };
>>>>
>>>> struct tag_dump_mte
>>>> {
>>>>    uint16_t granule_byte_size;
>>>>    uint16_t tag_bit_size;
>>>>    uint16_t __unused;
>>>> };
>>>>
>>>> The only bits meant to be generic are the tag_dump_format, start_vma 
>>>> and
>>>> end_vma fields.
>>>>
>>>> The format-specific data is supposed to be opaque and only useful 
>>>> for the
>>>> arch-specific code.
>>>>
>>>> We can extend the format in the future to make room for other memory 
>>>> tag
>>>> layouts.
>>>>
>>>
>>> This is a wider question than this patch - but is there someplace 
>>> this format will
>>> be documented? Ideally either Linux or GDB needs something written 
>>> down in
>>> text format.
>>>
>>> A few nits below too, but mostly looks fine.
>>
>> Yes. I'm still thinking whether this should go into the Linux Kernel's
>> documentation or somewhere in the GDB manual.
>>
>> Given GDB reads core files generated by the Linux Kernel and also
>> creates cores files itself, I'm thinking it may be best to put it in the
>> GDB manual.
>>
>> I want to wait for possible feedbacks about the format before we
>> document how it will look like in the manual.
> 
> Also, NT_MEMTAG is not inherently OS specific and I plan to reuse whatever
> format is used here for CHERI tags on RISC-V and Morello in CheriBSD (a
> CHERI-aware version of FreeBSD).  If someone adds MTE support in FreeBSD
> that would use the same format as well.  To that end, I think we should
> aim to have the tag "type" values be OS independent (so just depending
> on the arch).  Having an actual value of NT_MEMTAG that is portable is
> harder as various OS's already have conflicting NT_* namespaces.  If we

Just recently Andrew added the NT_GDB_TDESC note type, which is supposed 
to be OS-independent. The value for that (0xff000000) was picked out of 
a range that doesn't overlap with existing OS-specific definitions.

I'm doing the same for NT_MEMTAG. You can use it for any OS (or 
bare-metal), as it doesn't have anything Linux-specific.

> wanted something truly independent we could perhaps choose a neutral
> note name (e.g. "memtag" instead of "Linux", "CORE", or "FreeBSD") and

I'm using CORE right now. But we could change it to something else if 
needed. Isn't CORE something any OS can work with?

> then the note type could instead take the place of the type of tag,
> but that may be overly complicated compared to just having OS-specific
> NT_MEMTAG values.
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH,v2] [AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
                   ` (2 preceding siblings ...)
  2021-05-21 15:12 ` Alan Hayward
@ 2021-05-26 14:08 ` Luis Machado
  2021-05-29  3:14   ` Simon Marchi
  2021-05-31 14:15   ` [PATCH,v3][AArch64] " Luis Machado
  2021-05-31 16:44 ` [PATCH,v4][AArch64] " Luis Machado
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
  5 siblings, 2 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-26 14:08 UTC (permalink / raw)
  To: gdb-patches; +Cc: alan.hayward, jhb, david.spickett, catalin.marinas

Updates on v2:

- Reworked core_target::fetch_memtags to handle cases where address + len runs
  over the NT_MEMTAG note.
- Turned a few magic numbers into constants. There is an unfortunate duplication
  of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
  no file generic enough that gets included by both corelow and linux-tdep.
- More sanity checks to make sure the note format is correct.
- Documented aarch64_linux_decode_memtag_note a little more.
- Moved struct tag_dump_header to linux-tdep.h.

---

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
  uint64_t start_vma;
  uint64_t end_vma;
};

struct tag_dump_mte
{
  uint16_t granule_byte_size;
  uint16_t tag_bit_size;
  uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* aarch64-linux-tdep.c: Include elf/common.h.
	(MAX_TAGS_TO_TRANSFER): New constant.
	(aarch64_linux_create_memtag_notes_from_range): New function.
	(aarch64_linux_decode_memtag_note): Likewise.
	(aarch64_linux_init_abi): Register new core file hooks.
	(MEMTAG_NOTE_HEADER_SIZE): New constant.
	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
	(struct tag_dump_mte): New struct.
	* corelow.c (core_target) <supports_memory_tagging, fetch_memtags>: New
	method overrides.
	(NT_MEMTAG_HEADER_SIZE): New constant.
	* gdbarch.c: Regenerate.
	* gdbarch.h: Likewise.
	* gdbarch.sh (create_memtag_notes_from_range): New hook.
	(decode_memtag_note): Likewise.
	* linux-tdep.c (linux_address_in_memtag_page): Renamed to...
	(linux_process_address_in_memtag_page): ... this.
	(linux_core_file_address_in_memtag_page): New function.
	(linux_address_in_memtag_page): Likewise.
	(linux_make_memtag_corefile_notes): Likewise.
	(linux_make_corefile_notes): Handle memory tag notes.
	* linux-tdep.h (NT_MEMTAG_HEADER_SIZE): New constant.
	(struct tag_dump_header): New struct.
	* NEWS: Mention core file support for memory tagging.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
	for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.arch/aarch64-mte-gcore.c: New file.
	* gdb.arch/aarch64-mte-gcore.exp: New file.
---
 gdb/NEWS                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 198 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.h                 |  15 ++
 gdb/corelow.c                                | 108 ++++++++++
 gdb/doc/gdb.texinfo                          |   4 +
 gdb/gdbarch.c                                |  64 ++++++
 gdb/gdbarch.h                                |  16 ++
 gdb/gdbarch.sh                               |   6 +
 gdb/linux-tdep.c                             | 141 ++++++++++++-
 gdb/linux-tdep.h                             |  13 ++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 115 +++++++++++
 12 files changed, 774 insertions(+), 3 deletions(-)
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 10
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
 * GDB now supports general memory tagging functionality if the underlying
   architecture supports the proper primitives and hooks.  Currently this is
   enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..c0cd0a44746 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -53,6 +53,8 @@
 
 #include "gdbsupport/selftest.h"
 
+#include "elf/common.h"
+
 /* Signal frame handling.
 
       +------------+  ^
@@ -1779,6 +1781,191 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define MEMTAG_NOTE_HEADER_SIZE (NT_MEMTAG_HEADER_SIZE \
+				 + MEMTAG_MTE_HEADER_SIZE)
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+					      CORE_ADDR start_address,
+					      CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  int granules = aarch64_mte_get_tag_granules (start_address,
+					       end_address - start_address,
+					       AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (MEMTAG_NOTE_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  notes.resize (0);
+	  return notes;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (uint16_t);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
+  buf += sizeof (uint64_t);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
+  buf += sizeof (uint64_t);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + MEMTAG_NOTE_HEADER_SIZE, tags.data (),
+	  tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the requested tags.
+
+   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
+   range.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+				  gdb::array_view <const gdb_byte> note,
+				  CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Sanity check.  */
+  if (note.size () < MEMTAG_NOTE_HEADER_SIZE)
+    {
+      warning (_("malformed core note - too short for MTE header"));
+      return tags;
+    }
+
+  /* The amount of memory tag granules we need to fetch.  */
+  int granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  struct tag_dump_header header;
+  const gdb_byte *buf = note.data ();
+
+  header.format
+    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
+  buf += sizeof (uint16_t);
+
+  header.start_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  header.end_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  /* Sanity check.  */
+  gdb_assert (address + length < header.end_vma);
+
+  /* Sanity check  */
+  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  int skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+				    aligned_address - aligned_start_address,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb_byte *tags_data
+    = note.data () + MEMTAG_NOTE_HEADER_SIZE + skipped_granules;
+
+  /* Read the tag granules.  */
+  for (unsigned int i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1862,6 +2049,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+	 addresses.  */
+      set_gdbarch_create_memtag_notes_from_range (gdbarch,
+				  aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+				      aarch64_linux_decode_memtag_note);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..0672522d507 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,18 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* MTE-specific NT_MEMTAG header.  */
+struct tag_dump_mte
+{
+  /* Size of the tag granule in bytes.  */
+  uint16_t granule_byte_size;
+  /* Size of the tag in bits.  */
+  uint16_t tag_bit_size;
+  /* Reserved field for the future.  */
+  uint16_t __unused;
+};
+
+/* Size of the MTE header for a NT_MEMTAG note.  */
+#define MEMTAG_MTE_HEADER_SIZE (3 * sizeof (uint16_t))
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..25580591ea5 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -100,6 +100,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1115,6 +1122,107 @@ core_target::info_proc (const char *args, enum info_proc_what request)
   return true;
 }
 
+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
+    return false;
+
+  return true;
+}
+
+/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
+   and should be shared with OS' and arch-specific code.  */
+#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))
+
+/* 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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    warning (_("gdbarch_decode_memtag_note not implemented for this "
+	       "architecture."));
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Remove the top byte for the memory range check.  */
+  address = address_significant (gdbarch, address);
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < NT_MEMTAG_HEADER_SIZE)
+	{
+	  warning (_("malformed core note - too short for NT_MEMTAG header"));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  It contains the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+				+ sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  gdb::byte_vector tags_read;
+	  size_t adjusted_length
+	    = (address + len < end_address)? len : (end_address - address);
+
+	  /* Decode the memory tag note and return the tags.  */
+	  tags_read = gdbarch_decode_memtag_note (gdbarch, note, 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 NT_MEMTAG
+	     note.  Check if we need to fetch tags from a different section.  */
+	  if (address + len < end_address)
+	    return true;
+
+	  /* There are more tags to fetch.  Update ADDRESS and LEN.  */
+	  address = end_address;
+	  len -= (end_address - address);
+	}
+
+      /* The requested address lies outside this particular memtag note.  Keep
+	 looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 90d827a50e7..1b001e6cacb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   fprintf_unfiltered (file,
                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                       host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
   fprintf_unfiltered (file,
                       "gdbarch_dump: decr_pc_after_break = %s\n",
                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
 # Find core file memory regions
 M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
 
+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
 # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
 # core file into buffer READBUF with length LEN.  Return the number of bytes read
 # (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..4a58f672ed8 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1438,10 +1438,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  asection *section
+    = bfd_get_section_by_name (core_bfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < NT_MEMTAG_HEADER_SIZE)
+	{
+	  warning (_("malformed core note - too short for header"));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (core_bfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  Those contain the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
+				+ sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	return true;
+
+      /* The requested address lies outside this particular memtag note.  Keep
+	 looking and get the next section.  */
+      section = bfd_get_next_section_by_name (core_bfd, section);
+    }
+
+  return false;
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+				  gdb::unique_xmalloc_ptr<char> &note_data,
+				  int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  std::vector<struct smaps_data> smaps;
+
+  /* Parse the contents of smaps into a vector.  */
+  smaps = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+	 memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+	continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+	 this particular memory range, including the header.
+
+	 If the notes are too big, we may need to break up the transfer
+	 into smaller chunks.
+
+	 If the architecture returns an empty vector, that means there are
+	 no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes;
+      memory_tag_notes
+	= gdbarch_create_memtag_notes_from_range (gdbarch,
+						  map.start_address,
+						  map.end_address);
+      /* Write notes to the core file.  */
+      for (gdb::byte_vector note : memory_tag_notes)
+	{
+	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
+					       note_size, "CORE",
+					       NT_MEMTAG, note.data (),
+					       note.size ()));
+	}
+    }
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -2051,6 +2183,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	return NULL;
     }
 
+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
   /* File mappings.  */
   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
 
diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
index 28b60e46579..1ff4d008df4 100644
--- a/gdb/linux-tdep.h
+++ b/gdb/linux-tdep.h
@@ -26,6 +26,19 @@
 struct inferior;
 struct regcache;
 
+#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))
+
+/* Generic NT_MEMTAG header.  */
+struct tag_dump_header
+{
+  /* Tag format.  */
+  uint16_t format;
+  /* Start address of the tagged range.  */
+  uint64_t start_vma;
+  /* End address of the tagged range.  */
+  uint64_t end_vma;
+};
+
 /* Enum used to define the extra fields of the siginfo type used by an
    architecture.  */
 enum linux_siginfo_extra_field_values
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..bb529a8b369
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,115 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart
+
+# Load the program file.
+set program_filename [standard_output_file $testfile]
+set program_loaded [gdb_file_cmd $program_filename]
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v2] [AArch64] MTE corefile support
  2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
@ 2021-05-29  3:14   ` Simon Marchi
  2021-05-31 14:12     ` Luis Machado
  2021-05-31 14:15   ` [PATCH,v3][AArch64] " Luis Machado
  1 sibling, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2021-05-29  3:14 UTC (permalink / raw)
  To: Luis Machado, gdb-patches; +Cc: david.spickett, catalin.marinas

On 2021-05-26 10:08 a.m., Luis Machado via Gdb-patches wrote:
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < MEMTAG_NOTE_HEADER_SIZE)
> +    {
> +      warning (_("malformed core note - too short for MTE header"));

I would suggest indicating the expected minimum size and the actualy
size.  If that ever fails, this bit of information could help the user.

> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  int granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  struct tag_dump_header header;
> +  const gdb_byte *buf = note.data ();
> +
> +  header.format
> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
> +  buf += sizeof (uint16_t);
> +
> +  header.start_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  header.end_vma
> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
> +  buf += sizeof (uint64_t);
> +
> +  /* Sanity check.  */
> +  gdb_assert (address + length < header.end_vma);
> +
> +  /* Sanity check  */
> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);

Here, it's ambiguous (from the point of view of the user) whether the
value you print is the expected value or actual value.  For clarity,
might was well print both.

> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  int skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);

aarch64_mte_get_tag_granules returns size_t, so we might as well make
the local variable size_t...

> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb_byte *tags_data
> +    = note.data () + MEMTAG_NOTE_HEADER_SIZE + skipped_granules;

To benefit from (potential, I've been wanting to add that for a while)
array_view bound_checking, I suggest making an array_view slice instead
of falling back on a plain pointer.  You should be able to call

  note.slice (MEMTAG_NOTE_HEADER_SIZE + skipped_granules)

which will get view another array view that starts where you need.

> +
> +  /* Read the tag granules.  */
> +  for (unsigned int i = 0; i < granules; i++)

... and the iteration variable size_t too.  This might apply to other
calls to aarch64_mte_get_tag_granules.

> +      tags.push_back (tags_data[i]);

I feel like we are missing some bound checking here.  Couldn't malformed
data send us past the array_view's window?

> +
> +  return tags;
> +}

In the computations above, the code seems to assume that each granule's
tag takes up one byte.  That is a safe assumption?

> @@ -1862,6 +2049,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>  
>        set_gdbarch_report_signal_info (gdbarch,
>  				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
> +				  aarch64_linux_create_memtag_notes_from_range);

Indent.

> @@ -71,4 +72,18 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>     It is always possible to get the logical tag.  */
>  extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>  
> +/* MTE-specific NT_MEMTAG header.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};

This struct is unused.

> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define MEMTAG_MTE_HEADER_SIZE (3 * sizeof (uint16_t))

NT_MEMTAG_MTE_HEADER_SIZE?  Otherwise there's nothing in the name that
refers to the fact that it's the header of the note.

> @@ -1115,6 +1122,107 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>    return true;
>  }
>  
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
> +    return false;
> +
> +  return true;

Use:

  bfd_get_section_by_name (...) != nullptr;

> +}
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS' and arch-specific code.  */
> +#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))

It's a little strange, NT_MEMTAG_HEADER_SIZE is defined at two places,
linux-tdep.h and here.  If this is really OS-independent, there should
probably be just one definition, in an OS-independent header file.  Not
sure where though...

> +
> +/* 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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
> +	       "architecture."));

Just a warning?  You want to continue this function even if this
happens?

> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");

Fits on one line.

> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>    return false;
>  }
>  
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  asection *section
> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_HEADER_SIZE)
> +	{
> +	  warning (_("malformed core note - too short for header"));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (core_bfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  Those contain the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
> +      uint64_t end_address
> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
> +				+ sizeof (uint64_t));

The code of this function approximately up to here is really similar to
the code in corelow.c.  Is there a change there could be a single
implementation of it, that takes a callback?  Like

  for_each_matching_memtag_note_section_matching_address
    (CORE_ADDR address, gdb::function_view<...> callback)

except with a better name.

> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  std::vector<struct smaps_data> smaps;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  smaps = parse_smaps_data (data.get (), smaps_file);

Declare when assigning:

  std::vector<smaps_data> smaps = parse_smaps_data (...);

> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes;
> +      memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);

Here too.

> +      /* Write notes to the core file.  */
> +      for (gdb::byte_vector note : memory_tag_notes)

This copies each byte_vector in the iteration variable - iterate with
const reference:

  for (const gdb::byte_vector &note : memory_tag_notes)

> diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
> index 28b60e46579..1ff4d008df4 100644
> --- a/gdb/linux-tdep.h
> +++ b/gdb/linux-tdep.h
> @@ -26,6 +26,19 @@
>  struct inferior;
>  struct regcache;
>  
> +#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};

This struct is used once, in aarch64_linux_decode_memtag_note, but where
it's used it's not very useful, it could very well be replaced with 3
local variables, and then it could be removed.  I'm guessing that an
earlier version of this patch used this struct to read the data contents
directly from the buffer, but that didn't work with different byte
orders?

> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..bb529a8b369
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,115 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart
> +
> +# Load the program file.
> +set program_filename [standard_output_file $testfile]
> +set program_loaded [gdb_file_cmd $program_filename]

Can the two lines above just be replaced with passing $binfile to
clean_restart?

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v2] [AArch64] MTE corefile support
  2021-05-29  3:14   ` Simon Marchi
@ 2021-05-31 14:12     ` Luis Machado
  2021-05-31 14:49       ` Simon Marchi
  0 siblings, 1 reply; 47+ messages in thread
From: Luis Machado @ 2021-05-31 14:12 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: david.spickett, catalin.marinas, John Baldwin

On 5/29/21 12:14 AM, Simon Marchi wrote:
> On 2021-05-26 10:08 a.m., Luis Machado via Gdb-patches wrote:
>> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
>> +   hook.  Decode a memory tag note and return the requested tags.
>> +
>> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
>> +   range.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
>> +				  gdb::array_view <const gdb_byte> note,
>> +				  CORE_ADDR address, size_t length)
>> +{
>> +  gdb::byte_vector tags;
>> +
>> +  /* Sanity check.  */
>> +  if (note.size () < MEMTAG_NOTE_HEADER_SIZE)
>> +    {
>> +      warning (_("malformed core note - too short for MTE header"));
> 
> I would suggest indicating the expected minimum size and the actualy
> size.  If that ever fails, this bit of information could help the user.
> 

Fixed now.

>> +      return tags;
>> +    }
>> +
>> +  /* The amount of memory tag granules we need to fetch.  */
>> +  int granules
>> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to decode, just return.  */
>> +  if (granules == 0)
>> +    return tags;
>> +
>> +  /* Read the generic header.  */
>> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
>> +  struct tag_dump_header header;
>> +  const gdb_byte *buf = note.data ();
>> +
>> +  header.format
>> +    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
>> +  buf += sizeof (uint16_t);
>> +
>> +  header.start_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  header.end_vma
>> +    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
>> +  buf += sizeof (uint64_t);
>> +
>> +  /* Sanity check.  */
>> +  gdb_assert (address + length < header.end_vma);
>> +
>> +  /* Sanity check  */
>> +  if (header.format != NT_MEMTAG_TYPE_AARCH_MTE)
>> +    {
>> +      warning (_("Unexpected memory tag note format (%x).\n"), header.format);
> 
> Here, it's ambiguous (from the point of view of the user) whether the
> value you print is the expected value or actual value.  For clarity,
> might was well print both.
> 

Updated to show the expected format hex value and what we really got.

>> +      return tags;
>> +    }
>> +
>> +  /* Calculate how many granules we need to skip to get to the granule of
>> +     ADDRESS.  Align both the start address and the requested address
>> +     so it is easier to get the number of granules to skip.  This way we
>> +     don't need to consider cases where ADDRESS falls in the middle of a
>> +     tag granule range.  */
>> +  CORE_ADDR aligned_start_address
>> +    = align_down (header.start_vma, AARCH64_MTE_GRANULE_SIZE);
>> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  int skipped_granules
>> +    = aarch64_mte_get_tag_granules (aligned_start_address,
>> +				    aligned_address - aligned_start_address,
>> +				    AARCH64_MTE_GRANULE_SIZE);
> 
> aarch64_mte_get_tag_granules returns size_t, so we might as well make
> the local variable size_t...
> 

Updated all the variables now, and the loop variables.

>> +
>> +  /* Point to the block of data that contains the first granule we are
>> +     interested in.  */
>> +  const gdb_byte *tags_data
>> +    = note.data () + MEMTAG_NOTE_HEADER_SIZE + skipped_granules;
> 
> To benefit from (potential, I've been wanting to add that for a while)
> array_view bound_checking, I suggest making an array_view slice instead
> of falling back on a plain pointer.  You should be able to call
> 
>    note.slice (MEMTAG_NOTE_HEADER_SIZE + skipped_granules)

Done now.

> 
> which will get view another array view that starts where you need.
> 
>> +
>> +  /* Read the tag granules.  */
>> +  for (unsigned int i = 0; i < granules; i++)
> 
> ... and the iteration variable size_t too.  This might apply to other
> calls to aarch64_mte_get_tag_granules.
> 

Done.

>> +      tags.push_back (tags_data[i]);
> 
> I feel like we are missing some bound checking here.  Couldn't malformed
> data send us past the array_view's window?
> 

I've added a new check after we read the header, but before reading the 
tag data. It validates the note has enough tag bytes for the specified 
start_vma/end_vma range. It could have more though, in which case I 
don't do anything and assume it is OK as is.

>> +
>> +  return tags;
>> +}
> 
> In the computations above, the code seems to assume that each granule's
> tag takes up one byte.  That is a safe assumption? >

Even though MTE tags are 4 bits in size, we write them down as 1 byte 
tags for simplicity. So it is correct to handle the tag data here as 1 
tag per byte.

There are plans to add the capability of compressing the tag data. That 
would make it smaller. But we're not looking at that at this time.

>> @@ -1862,6 +2049,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>   
>>         set_gdbarch_report_signal_info (gdbarch,
>>   				      aarch64_linux_report_signal_info);
>> +
>> +      /* Core file helpers.  */
>> +
>> +      /* Core file helper to create memory tag notes for a particular range of
>> +	 addresses.  */
>> +      set_gdbarch_create_memtag_notes_from_range (gdbarch,
>> +				  aarch64_linux_create_memtag_notes_from_range);
> 
> Indent.
> 

Done.

>> @@ -71,4 +72,18 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>      It is always possible to get the logical tag.  */
>>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>   
>> +/* MTE-specific NT_MEMTAG header.  */
>> +struct tag_dump_mte
>> +{
>> +  /* Size of the tag granule in bytes.  */
>> +  uint16_t granule_byte_size;
>> +  /* Size of the tag in bits.  */
>> +  uint16_t tag_bit_size;
>> +  /* Reserved field for the future.  */
>> +  uint16_t __unused;
>> +};
> 
> This struct is unused.
> 

I'm inclined to leave that as documentation instead, so it is clear what 
the header sizes are referring to.

>> +
>> +/* Size of the MTE header for a NT_MEMTAG note.  */
>> +#define MEMTAG_MTE_HEADER_SIZE (3 * sizeof (uint16_t))
> 
> NT_MEMTAG_MTE_HEADER_SIZE?  Otherwise there's nothing in the name that
> refers to the fact that it's the header of the note.
> 

Done now.

>> @@ -1115,6 +1122,107 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>>     return true;
>>   }
>>   
>> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
>> +
>> +bool
>> +core_target::supports_memory_tagging ()
>> +{
>> +  /* Look for memory tag notes.  If they exist, that means this core file
>> +     supports memory tagging.  */
>> +  if (bfd_get_section_by_name (core_bfd, ".memtag") == nullptr)
>> +    return false;
>> +
>> +  return true;
> 
> Use:
> 
>    bfd_get_section_by_name (...) != nullptr;
> 

Changed now.

>> +}
>> +
>> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
>> +   and should be shared with OS' and arch-specific code.  */
>> +#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))
> 
> It's a little strange, NT_MEMTAG_HEADER_SIZE is defined at two places,
> linux-tdep.h and here.  If this is really OS-independent, there should
> probably be just one definition, in an OS-independent header file.  Not
> sure where though...
> 

Right. I ran into this as well. I thought about this a bit, and I guess 
the simplest way is to have a new memtag.h header file and define it there.

It would be generic enough and other OS' would be able to include it as 
well. And it would work for Core files.

>> +
>> +/* 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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* Make sure we have a way to decode the memory tag notes.  */
>> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
>> +    warning (_("gdbarch_decode_memtag_note not implemented for this "
>> +	       "architecture."));
> 
> Just a warning?  You want to continue this function even if this
> happens?
> 

This should've been an error. Thanks.

>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
> 
> Fits on one line.
> 

Fixed.

>> @@ -1473,6 +1474,137 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>     return false;
>>   }
>>   
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> +   process.  */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (core_bfd == nullptr)
>> +    return false;
>> +
>> +  asection *section
>> +    = bfd_get_section_by_name (core_bfd, ".memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t note_size = bfd_section_size (section);
>> +
>> +      /* If the note is smaller than the size of the header, this core note
>> +	 is malformed.  */
>> +      if (note_size < NT_MEMTAG_HEADER_SIZE)
>> +	{
>> +	  warning (_("malformed core note - too short for header"));
>> +	  return false;
>> +	}
>> +

I've updated this warning to show the expected and the actual size of 
the header.

>> +      gdb::byte_vector note (note_size);
>> +
>> +      /* Fetch the contents of this particular memtag note.  */
>> +      if (!bfd_get_section_contents (core_bfd, section,
>> +				     note.data (), 0, note_size))
>> +	{
>> +	  warning (_("could not get core note contents."));
>> +	  return false;
>> +	}
>> +
>> +      /* Read the generic header of the note.  Those contain the format,
>> +	 start address and end address.  */
>> +      uint64_t start_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t));
>> +      uint64_t end_address
>> +	= bfd_get_64 (core_bfd, note.data () + sizeof (uint16_t)
>> +				+ sizeof (uint64_t));
> 
> The code of this function approximately up to here is really similar to
> the code in corelow.c.  Is there a change there could be a single
> implementation of it, that takes a callback?  Like
> 
>    for_each_matching_memtag_note_section_matching_address
>      (CORE_ADDR address, gdb::function_view<...> callback)
> 
> except with a better name.
> 

I'm aware of that. I was hoping to leave this cleanup for a later stage.

A complicating factor here is that we need to traverse the NT_MEMTAG 
notes for two slightly different reasons.

The first is to decide if an address (no length specified) falls within 
one of the NT_MEMTAG entries, in which case we just return true/false;

The second is to actually fetch the tags the user has requested (via 
address + length). This may potentially span multiple NT_MEMTAG notes, 
so the callback may be invoked multiple times.

It should be possible to refactor the code to invoke callbacks, but we 
would need to have two callbacks (one for corelow and another for 
linux-tdep). The one for linux-tdep just needs to check if a note exists 
for a particular range, so it would be an useless callback in my opinion.

With that said, I reworked things and moved the common code to a new 
function. This new function goes through the notes and returns whenever 
we hit a note that contains the address we're looking for. Then we have 
a chance to do some processing, return or continue going through notes.

I think this simplifies things.

>> +/* For each memory map entry that has memory tagging enabled, create a new
>> +   core file note that contains all of its memory tags.  Save the data to
>> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
>> +
>> +static void
>> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
>> +				  gdb::unique_xmalloc_ptr<char> &note_data,
>> +				  int *note_size)
>> +{
>> +  if (current_inferior ()->fake_pid_p)
>> +    return;
>> +
>> +  /* If the architecture doesn't have a hook to return memory tag notes,
>> +     there is nothing left to do.  */
>> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
>> +    return;
>> +
>> +  pid_t pid = current_inferior ()->pid;
>> +
>> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
>> +
>> +  gdb::unique_xmalloc_ptr<char> data
>> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
>> +
>> +  if (data == nullptr)
>> +    return;
>> +
>> +  std::vector<struct smaps_data> smaps;
>> +
>> +  /* Parse the contents of smaps into a vector.  */
>> +  smaps = parse_smaps_data (data.get (), smaps_file);
> 
> Declare when assigning:
> 
>    std::vector<smaps_data> smaps = parse_smaps_data (...);
> 

Done.

>> +
>> +  for (const smaps_data &map : smaps)
>> +    {
>> +      /* Does this mapping have memory tagging enabled? If so, save the
>> +	 memory tags to the core file note.  */
>> +      if (map.vmflags.memory_tagging == 0)
>> +	continue;
>> +
>> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
>> +	 this particular memory range, including the header.
>> +
>> +	 If the notes are too big, we may need to break up the transfer
>> +	 into smaller chunks.
>> +
>> +	 If the architecture returns an empty vector, that means there are
>> +	 no memory tag notes to write.  */
>> +      std::vector<gdb::byte_vector> memory_tag_notes;
>> +      memory_tag_notes
>> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
>> +						  map.start_address,
>> +						  map.end_address);
> 
> Here too.
> 

Done.

>> +      /* Write notes to the core file.  */
>> +      for (gdb::byte_vector note : memory_tag_notes)
> 
> This copies each byte_vector in the iteration variable - iterate with
> const reference:
> 
>    for (const gdb::byte_vector &note : memory_tag_notes)
> 

Done.

>> diff --git a/gdb/linux-tdep.h b/gdb/linux-tdep.h
>> index 28b60e46579..1ff4d008df4 100644
>> --- a/gdb/linux-tdep.h
>> +++ b/gdb/linux-tdep.h
>> @@ -26,6 +26,19 @@
>>   struct inferior;
>>   struct regcache;
>>   
>> +#define NT_MEMTAG_HEADER_SIZE (sizeof (uint16_t) + 2 * sizeof (uint64_t))
>> +
>> +/* Generic NT_MEMTAG header.  */
>> +struct tag_dump_header
>> +{
>> +  /* Tag format.  */
>> +  uint16_t format;
>> +  /* Start address of the tagged range.  */
>> +  uint64_t start_vma;
>> +  /* End address of the tagged range.  */
>> +  uint64_t end_vma;
>> +};
> 
> This struct is used once, in aarch64_linux_decode_memtag_note, but where
> it's used it's not very useful, it could very well be replaced with 3
> local variables, and then it could be removed.  I'm guessing that an
> earlier version of this patch used this struct to read the data contents
> directly from the buffer, but that didn't work with different byte
> orders?
> 

Like the MTE NT_MEMTAG header, this is mostly for documentation 
purposes. We don't have a lot of use for the header structs right now, 
but may have more uses for them in the future. We may want to pass the 
header around and access it in a simpler way than just using local 
variables and skipping individual sizes.

This is because we're using a packed struct and we don't want to run 
into issues with alignments and padding inserted by the compilers, or 
having to handle endianness differences at the corelow.c level.

I'll do the same as I've done with the NT_MEMTAG_MTE_HEADER_SIZE case 
and will document this struct alongside the definition of 
NT_MEMTAG_HEADER_SIZE (in a new generic file, memtag.h).

>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..bb529a8b369
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,115 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# This file is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> +    verbose "Skipping ${gdb_test_file_name}."
>> +    return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    untested "could not run to main"
>> +    return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> +    unsupported "memory tagging unsupported"
>> +    return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> +    return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%02x" $i]
>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> +	     $atag_msg \
>> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +	"Program received signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag $hex" \
>> +	"Logical tag $hex\." \
>> +	"$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> +    return -1
>> +}
>> +
>> +clean_restart
>> +
>> +# Load the program file.
>> +set program_filename [standard_output_file $testfile]
>> +set program_loaded [gdb_file_cmd $program_filename]
> 
> Can the two lines above just be replaced with passing $binfile to
> clean_restart?

Done now. Thanks.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH,v3][AArch64] MTE corefile support
  2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
  2021-05-29  3:14   ` Simon Marchi
@ 2021-05-31 14:15   ` Luis Machado
  1 sibling, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-31 14:15 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas

Updates on v3:

- Addressed review comments.
- New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
- Updated code documentation.
- Removed code duplication.

Updates on v2:

- Reworked core_target::fetch_memtags to handle cases where address + len runs
  over the NT_MEMTAG note.
- Turned a few magic numbers into constants. There is an unfortunate duplication
  of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
  no file generic enough that gets included by both corelow and linux-tdep.
- More sanity checks to make sure the note format is correct.
- Documented aarch64_linux_decode_memtag_note a little more.

---

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
  uint64_t start_vma;
  uint64_t end_vma;
};

struct tag_dump_mte
{
  uint16_t granule_byte_size;
  uint16_t tag_bit_size;
  uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* Makefile.in (COMMON_SFILES): Add memtag.c.
	* NEWS: Mention core file support for memory tagging.
	* aarch64-linux-tdep.c: Include elf/common.h.
	Include gdbsupport/memtag.h.
	(MAX_TAGS_TO_TRANSFER): New constant.
	(aarch64_linux_create_memtag_notes_from_range): New function.
	(aarch64_linux_decode_memtag_note): Likewise.
	(aarch64_linux_init_abi): Register new core file hooks.
	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
	* arch/aarch64-mte-linux.h (AARCH64_MTE_TAG_BIT_SIZE): New constant.
	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
	(core_target) <supports_memory_tagging, fetch_memtags>: New
	method overrides.
	* gdbarch.c: Regenerate.
	* gdbarch.h: Likewise.
	* gdbarch.sh (create_memtag_notes_from_range): New hook.
	(decode_memtag_note): Likewise.
	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
	(linux_address_in_memtag_page): Renamed to...
	(linux_process_address_in_memtag_page): ... this.
	(linux_core_file_address_in_memtag_page): New function.
	(linux_address_in_memtag_page): Likewise.
	(linux_make_memtag_corefile_notes): Likewise.
	(linux_make_corefile_notes): Handle memory tag notes.
	* memtag.c: New file.
	* memtag.h: New file.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
	for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.arch/aarch64-mte-gcore.c: New file.
	* gdb.arch/aarch64-mte-gcore.exp: New file.

gdbsupport/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* memtag.h: New file.
---
 gdb/Makefile.in                              |   1 +
 gdb/NEWS                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 216 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.h                 |  13 ++
 gdb/corelow.c                                |  63 ++++++
 gdb/doc/gdb.texinfo                          |   4 +
 gdb/gdbarch.c                                |  64 ++++++
 gdb/gdbarch.h                                |  16 ++
 gdb/gdbarch.sh                               |   6 +
 gdb/linux-tdep.c                             |  97 ++++++++-
 gdb/memtag.c                                 |  88 ++++++++
 gdb/memtag.h                                 |  46 ++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
 gdbsupport/memtag.h                          |  37 ++++
 15 files changed, 856 insertions(+), 3 deletions(-)
 create mode 100644 gdb/memtag.c
 create mode 100644 gdb/memtag.h
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
 create mode 100644 gdbsupport/memtag.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index bb6c5dfa784..89149c17808 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1099,6 +1099,7 @@ COMMON_SFILES = \
 	memattr.c \
 	memory-map.c \
 	memrange.c \
+	memtag.c \
 	minidebug.c \
 	minsyms.c \
 	mipsread.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 10
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
 * GDB now supports general memory tagging functionality if the underlying
   architecture supports the proper primitives and hooks.  Currently this is
   enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..fd02989dfb6 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -52,6 +52,9 @@
 #include "value.h"
 
 #include "gdbsupport/selftest.h"
+#include "gdbsupport/memtag.h"
+
+#include "elf/common.h"
 
 /* Signal frame handling.
 
@@ -1779,6 +1782,208 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
+				     + NT_MEMTAG_MTE_HEADER_SIZE)
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+					      CORE_ADDR start_address,
+					      CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (start_address,
+						  end_address - start_address,
+						  AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  notes.resize (0);
+	  return notes;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (uint16_t);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, start_address);
+  buf += sizeof (uint64_t);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (uint64_t), byte_order, end_address);
+  buf += sizeof (uint64_t);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order,
+			  AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (uint16_t);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (uint16_t), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
+	  tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the requested tags.
+
+   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
+   range.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+				  gdb::array_view <const gdb_byte> note,
+				  CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Sanity check.  */
+  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
+    {
+      warning (_("Malformed core note - too short for MTE header.\n"
+		 "Expected %s bytes but got %s bytes."),
+	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
+	       pulongest (note.size ()));
+      return tags;
+    }
+
+  /* The amount of memory tag granules we need to fetch.  */
+  size_t granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  const gdb_byte *buf = note.data ();
+
+  unsigned int format
+    = extract_unsigned_integer (buf, sizeof (uint16_t), byte_order);
+  buf += sizeof (uint16_t);
+
+  CORE_ADDR start_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  CORE_ADDR end_vma
+    = extract_unsigned_integer (buf, sizeof (uint64_t), byte_order);
+  buf += sizeof (uint64_t);
+
+  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
+     range of addresses.  */
+  gdb_assert (address + length < end_vma);
+
+  /* Is the tag header format correct for this note?  */
+  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format.\n"
+		 "Expected %lx but got %x."), NT_MEMTAG_MTE_HEADER_SIZE,
+	       format);
+      return tags;
+    }
+
+  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
+
+  /* Does the number of tag bytes in this note match the expected number
+     of tag bytes the note says it has?  */
+  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
+    {
+      warning (_("Unexpected tag data size.\n"
+		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
+	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  size_t skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+				    aligned_address - aligned_start_address,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb::array_view<const gdb_byte> tags_data
+    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
+
+  /* Read the tag granules.  */
+  for (size_t i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1862,6 +2067,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+	 addresses.  */
+      set_gdbarch_create_memtag_notes_from_range
+	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+				      aarch64_linux_decode_memtag_note);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..fe07a49c6bf 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,16 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* Size of the MTE header for a NT_MEMTAG note.  It represents the following
+   structure:
+
+   struct tag_dump_mte
+   {
+     uint16_t granule_byte_size;
+     uint16_t tag_bit_size;
+     uint16_t __unused;
+   };
+*/
+#define NT_MEMTAG_MTE_HEADER_SIZE (3 * sizeof (uint16_t))
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..24c5bf29a11 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -51,6 +51,8 @@
 #include "gdbcmd.h"
 #include "xml-tdesc.h"
 #include "observable.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
   return true;
 }
 
+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+
+  return (bfd_get_section_by_name (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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    error (_("gdbarch_decode_memtag_note not implemented for this "
+	     "architecture."));
+
+  memtag_note_info info;
+  info.memtag_section = nullptr;
+
+  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
+       note.  Check if we need to fetch tags from a different section.  */
+    if (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;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 90d827a50e7..1b001e6cacb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   fprintf_unfiltered (file,
                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                       host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
   fprintf_unfiltered (file,
                       "gdbarch_dump: decr_pc_after_break = %s\n",
                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
 # Find core file memory regions
 M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
 
+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
 # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
 # core file into buffer READBUF with length LEN.  Return the number of bytes read
 # (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..6192cc4421b 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -41,6 +41,8 @@
 #include "gdbsupport/gdb_optional.h"
 #include "gcore.h"
 #include "gcore-elf.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #include <ctype.h>
 
@@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  memtag_note_info info;
+  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+				  gdb::unique_xmalloc_ptr<char> &note_data,
+				  int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  /* Parse the contents of smaps into a vector.  */
+  std::vector<struct smaps_data> smaps
+    = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+	 memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+	continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+	 this particular memory range, including the header.
+
+	 If the notes are too big, we may need to break up the transfer
+	 into smaller chunks.
+
+	 If the architecture returns an empty vector, that means there are
+	 no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes
+	= gdbarch_create_memtag_notes_from_range (gdbarch,
+						  map.start_address,
+						  map.end_address);
+      /* Write notes to the core file.  */
+      for (const gdb::byte_vector &note : memory_tag_notes)
+	{
+	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
+					       note_size, "CORE",
+					       NT_MEMTAG, note.data (),
+					       note.size ()));
+	}
+    }
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	return NULL;
     }
 
+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
   /* File mappings.  */
   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
 
diff --git a/gdb/memtag.c b/gdb/memtag.c
new file mode 100644
index 00000000000..60869701e94
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,88 @@
+/* GDB generic memory tagging functions.
+
+   Copyright (C) 2021 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 "memtag.h"
+#include "gdbsupport/memtag.h"
+#include "bfd.h"
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return a pointer to a .memtag section containing ADDRESS or nullptr
+   of none are found.
+
+   If SECTION is provided, search from that section onwards.  */
+
+bool
+get_next_core_memtag_section (bfd *abfd, asection *section,
+			      CORE_ADDR address, memtag_note_info &info)
+{
+  /* If SECTION is nullptr, start a fresh lookup.  */
+  if (section == nullptr)
+    section = bfd_get_section_by_name (abfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
+	{
+	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
+		     "header.\n"
+		     "Expected %s bytes but got %s bytes."),
+		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
+		   pulongest (note_size));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (abfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  It contains the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (abfd, note.data () + sizeof (uint16_t));
+      uint64_t end_address
+	= bfd_get_64 (abfd, note.data () + sizeof (uint16_t)
+				+ sizeof (uint64_t));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  info.start_address = start_address;
+	  info.end_address = end_address;
+	  info.note = note;
+	  info.memtag_section = section;
+	  return true;
+	}
+    }
+  return false;
+}
diff --git a/gdb/memtag.h b/gdb/memtag.h
new file mode 100644
index 00000000000..43c9efb39a3
--- /dev/null
+++ b/gdb/memtag.h
@@ -0,0 +1,46 @@
+/* GDB generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef MEMTAG_H
+#define MEMTAG_H
+
+#include "bfd.h"
+
+struct memtag_note_info
+{
+  CORE_ADDR start_address;
+  CORE_ADDR end_address;
+  gdb::byte_vector note;
+  asection *memtag_section;
+};
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
+   otherwise.
+
+   If SECTION is provided, search from that section onwards. If SECTION is
+   nullptr, then start a new search.
+
+   If a .memtag section containing ADDRESS is found, fill INFO with data
+   about such section.  Otherwise leave it unchanged.  */
+
+bool get_next_core_memtag_section (bfd *abfd, asection *section,
+				   CORE_ADDR address, memtag_note_info &info);
+
+#endif /* MEMTAG_H */
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..d0bcd036972
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,111 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart $binfile
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
new file mode 100644
index 00000000000..58049927696
--- /dev/null
+++ b/gdbsupport/memtag.h
@@ -0,0 +1,37 @@
+/* Generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef GDBSUPPORT_MEMTAG_H
+#define GDBSUPPORT_MEMTAG_H
+
+/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
+   and should be shared with OS-specific and arch-specific code.
+
+   The size comes from the following struct:
+
+   struct tag_dump_header
+   {
+     uint16_t format;
+     uint64_t start_vma;
+     uint64_t end_vma;
+   };
+*/
+#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (uint16_t) \
+				       + 2 * sizeof (uint64_t))
+
+#endif /* GDBSUPPORT_MEMTAG_H */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v2] [AArch64] MTE corefile support
  2021-05-31 14:12     ` Luis Machado
@ 2021-05-31 14:49       ` Simon Marchi
  2021-05-31 14:56         ` Luis Machado
  0 siblings, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2021-05-31 14:49 UTC (permalink / raw)
  To: Luis Machado, gdb-patches; +Cc: david.spickett, catalin.marinas, John Baldwin

On 2021-05-31 10:12 a.m., Luis Machado wrote:
>>> @@ -71,4 +72,18 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>>      It is always possible to get the logical tag.  */
>>>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>>   +/* MTE-specific NT_MEMTAG header.  */
>>> +struct tag_dump_mte
>>> +{
>>> +  /* Size of the tag granule in bytes.  */
>>> +  uint16_t granule_byte_size;
>>> +  /* Size of the tag in bits.  */
>>> +  uint16_t tag_bit_size;
>>> +  /* Reserved field for the future.  */
>>> +  uint16_t __unused;
>>> +};
>>
>> This struct is unused.
>>
> 
> I'm inclined to leave that as documentation instead, so it is clear what the header sizes are referring to.

The thing is that somebody reading the code in the .c seeing the
arbitrary sizeofs  will note have a clue to go look at
arch/aarch64-mte-linux.h for explanations.  So another option would be
to write it as a comment near the code.  Something like:

  /* The header follows the following format:

     struct
     {
       /* Size of the tag granule in bytes.  */
       uint16_t granule_byte_size;
       /* Size of the tag in bits.  */
       uint16_t tag_bit_size;
       /* Reserved field for the future.  */
       uint16_t __unused;
     };
   */

Another option would be to have the structure and refer to its fields in
sizeof:

    store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
			    byte_order, AARCH64_MTE_GRANULE_SIZE);

This has the advantage that the structure can still be present as documentation
in a central place, the reader of that code will naturally be pointed to
it if they need more information, and since the structure type is
actually used, it's fine to keep it there.

>> It's a little strange, NT_MEMTAG_HEADER_SIZE is defined at two places,
>> linux-tdep.h and here.  If this is really OS-independent, there should
>> probably be just one definition, in an OS-independent header file.  Not
>> sure where though...
>>
> 
> Right. I ran into this as well. I thought about this a bit, and I guess the simplest way is to have a new memtag.h header file and define it there.
> 
> It would be generic enough and other OS' would be able to include it as well. And it would work for Core files.

Sounds good.

>> The code of this function approximately up to here is really similar to
>> the code in corelow.c.  Is there a change there could be a single
>> implementation of it, that takes a callback?  Like
>>
>>    for_each_matching_memtag_note_section_matching_address
>>      (CORE_ADDR address, gdb::function_view<...> callback)
>>
>> except with a better name.
>>
> 
> I'm aware of that. I was hoping to leave this cleanup for a later stage.
> 
> A complicating factor here is that we need to traverse the NT_MEMTAG notes for two slightly different reasons.
> 
> The first is to decide if an address (no length specified) falls within one of the NT_MEMTAG entries, in which case we just return true/false;
> 
> The second is to actually fetch the tags the user has requested (via address + length). This may potentially span multiple NT_MEMTAG notes, so the callback may be invoked multiple times.
> 
> It should be possible to refactor the code to invoke callbacks, but we would need to have two callbacks (one for corelow and another for linux-tdep). The one for linux-tdep just needs to check if a note exists for a particular range, so it would be an useless callback in my opinion.
> 
> With that said, I reworked things and moved the common code to a new function. This new function goes through the notes and returns whenever we hit a note that contains the address we're looking for. Then we have a chance to do some processing, return or continue going through notes.
> 
> I think this simplifies things.

Ok, I'll see in the new version.

>> This struct is used once, in aarch64_linux_decode_memtag_note, but where
>> it's used it's not very useful, it could very well be replaced with 3
>> local variables, and then it could be removed.  I'm guessing that an
>> earlier version of this patch used this struct to read the data contents
>> directly from the buffer, but that didn't work with different byte
>> orders?
>>
> 
> Like the MTE NT_MEMTAG header, this is mostly for documentation purposes. We don't have a lot of use for the header structs right now, but may have more uses for them in the future. We may want to pass the header around and access it in a simpler way than just using local variables and skipping individual sizes.
> 
> This is because we're using a packed struct and we don't want to run into issues with alignments and padding inserted by the compilers, or having to handle endianness differences at the corelow.c level.

Yep.

> I'll do the same as I've done with the NT_MEMTAG_MTE_HEADER_SIZE case and will document this struct alongside the definition of NT_MEMTAG_HEADER_SIZE (in a new generic file, memtag.h).

As I suggested above, I think having the structure and referring to its
fields in sizeofs would be a nice solution.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v2] [AArch64] MTE corefile support
  2021-05-31 14:49       ` Simon Marchi
@ 2021-05-31 14:56         ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-31 14:56 UTC (permalink / raw)
  To: Simon Marchi, gdb-patches; +Cc: david.spickett, catalin.marinas, John Baldwin

On 5/31/21 11:49 AM, Simon Marchi wrote:
> On 2021-05-31 10:12 a.m., Luis Machado wrote:
>>>> @@ -71,4 +72,18 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>>>       It is always possible to get the logical tag.  */
>>>>    extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>>>    +/* MTE-specific NT_MEMTAG header.  */
>>>> +struct tag_dump_mte
>>>> +{
>>>> +  /* Size of the tag granule in bytes.  */
>>>> +  uint16_t granule_byte_size;
>>>> +  /* Size of the tag in bits.  */
>>>> +  uint16_t tag_bit_size;
>>>> +  /* Reserved field for the future.  */
>>>> +  uint16_t __unused;
>>>> +};
>>>
>>> This struct is unused.
>>>
>>
>> I'm inclined to leave that as documentation instead, so it is clear what the header sizes are referring to.
> 
> The thing is that somebody reading the code in the .c seeing the
> arbitrary sizeofs  will note have a clue to go look at
> arch/aarch64-mte-linux.h for explanations.  So another option would be
> to write it as a comment near the code.  Something like:
> 
>    /* The header follows the following format:
> 
>       struct
>       {
>         /* Size of the tag granule in bytes.  */
>         uint16_t granule_byte_size;
>         /* Size of the tag in bits.  */
>         uint16_t tag_bit_size;
>         /* Reserved field for the future.  */
>         uint16_t __unused;
>       };
>     */
> 
> Another option would be to have the structure and refer to its fields in
> sizeof:
> 
>      store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> 			    byte_order, AARCH64_MTE_GRANULE_SIZE);

I think that would work as well. I can change it for the next iteration.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH,v4][AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
                   ` (3 preceding siblings ...)
  2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
@ 2021-05-31 16:44 ` Luis Machado
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
  5 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-05-31 16:44 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas

Updates on v4:

- Calculate sizes based on individual struct field sizes.

Updates on v3:

- Addressed review comments.
- New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
- Updated code documentation.
- Removed code duplication.

Updates on v2:

- Reworked core_target::fetch_memtags to handle cases where address + len runs
  over the NT_MEMTAG note.
- Turned a few magic numbers into constants. There is an unfortunate duplication
  of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
  no file generic enough that gets included by both corelow and linux-tdep.
- More sanity checks to make sure the note format is correct.
- Documented aarch64_linux_decode_memtag_note a little more.

---

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
  uint64_t start_vma;
  uint64_t end_vma;
};

struct tag_dump_mte
{
  uint16_t granule_byte_size;
  uint16_t tag_bit_size;
  uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* Makefile.in (COMMON_SFILES): Add memtag.c.
	* NEWS: Mention core file support for memory tagging.
	* aarch64-linux-tdep.c: Include elf/common.h.
	Include gdbsupport/memtag.h.
	(MAX_TAGS_TO_TRANSFER): New constant.
	(aarch64_linux_create_memtag_notes_from_range): New function.
	(aarch64_linux_decode_memtag_note): Likewise.
	(aarch64_linux_init_abi): Register new core file hooks.
	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
	(core_target) <supports_memory_tagging, fetch_memtags>: New
	method overrides.
	* gdbarch.c: Regenerate.
	* gdbarch.h: Likewise.
	* gdbarch.sh (create_memtag_notes_from_range): New hook.
	(decode_memtag_note): Likewise.
	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
	(linux_address_in_memtag_page): Renamed to...
	(linux_process_address_in_memtag_page): ... this.
	(linux_core_file_address_in_memtag_page): New function.
	(linux_address_in_memtag_page): Likewise.
	(linux_make_memtag_corefile_notes): Likewise.
	(linux_make_corefile_notes): Handle memory tag notes.
	* memtag.c: New file.
	* memtag.h: New file.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
	for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.arch/aarch64-mte-gcore.c: New file.
	* gdb.arch/aarch64-mte-gcore.exp: New file.

gdbsupport/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* memtag.h: New file.
---
 gdb/Makefile.in                              |   1 +
 gdb/NEWS                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.h                 |  17 ++
 gdb/corelow.c                                |  63 ++++++
 gdb/doc/gdb.texinfo                          |   4 +
 gdb/gdbarch.c                                |  64 ++++++
 gdb/gdbarch.h                                |  16 ++
 gdb/gdbarch.sh                               |   6 +
 gdb/linux-tdep.c                             |  97 +++++++-
 gdb/memtag.c                                 |  88 ++++++++
 gdb/memtag.h                                 |  46 ++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
 gdbsupport/memtag.h                          |  39 ++++
 15 files changed, 867 insertions(+), 3 deletions(-)
 create mode 100644 gdb/memtag.c
 create mode 100644 gdb/memtag.h
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
 create mode 100644 gdbsupport/memtag.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index bb6c5dfa784..89149c17808 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1099,6 +1099,7 @@ COMMON_SFILES = \
 	memattr.c \
 	memory-map.c \
 	memrange.c \
+	memtag.c \
 	minidebug.c \
 	minsyms.c \
 	mipsread.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 10
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
 * GDB now supports general memory tagging functionality if the underlying
   architecture supports the proper primitives and hooks.  Currently this is
   enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..b4a8d09d5e2 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -52,6 +52,9 @@
 #include "value.h"
 
 #include "gdbsupport/selftest.h"
+#include "gdbsupport/memtag.h"
+
+#include "elf/common.h"
 
 /* Signal frame handling.
 
@@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
+				     + NT_MEMTAG_MTE_HEADER_SIZE)
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+					      CORE_ADDR start_address,
+					      CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (start_address,
+						  end_address - start_address,
+						  AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  notes.resize (0);
+	  return notes;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
+			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (tag_dump_header::format);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
+			  byte_order, start_address);
+  buf += sizeof (tag_dump_header::start_vma);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
+			  byte_order, end_address);
+  buf += sizeof (tag_dump_header::end_vma);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
+			  byte_order, AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (tag_dump_mte::granule_byte_size);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
+			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (tag_dump_mte::tag_bit_size);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
+	  tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the requested tags.
+
+   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
+   range.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+				  gdb::array_view <const gdb_byte> note,
+				  CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Sanity check.  */
+  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
+    {
+      warning (_("Malformed core note - too short for MTE header.\n"
+		 "Expected %s bytes but got %s bytes."),
+	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
+	       pulongest (note.size ()));
+      return tags;
+    }
+
+  /* The amount of memory tag granules we need to fetch.  */
+  size_t granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  const gdb_byte *buf = note.data ();
+
+  unsigned int format
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
+				byte_order);
+  buf += sizeof (tag_dump_header::format);
+
+  CORE_ADDR start_vma
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
+				byte_order);
+  buf += sizeof (tag_dump_header::start_vma);
+
+  CORE_ADDR end_vma
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
+				byte_order);
+  buf += sizeof (tag_dump_header::end_vma);
+
+  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
+     range of addresses.  */
+  gdb_assert (address + length < end_vma);
+
+  /* Is the tag header format correct for this note?  */
+  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format.\n"
+		 "Expected %lx but got %x."), NT_MEMTAG_MTE_HEADER_SIZE,
+	       format);
+      return tags;
+    }
+
+  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
+
+  /* Does the number of tag bytes in this note match the expected number
+     of tag bytes the note says it has?  */
+  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
+    {
+      warning (_("Unexpected tag data size.\n"
+		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
+	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  size_t skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+				    aligned_address - aligned_start_address,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb::array_view<const gdb_byte> tags_data
+    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
+
+  /* Read the tag granules.  */
+  for (size_t i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+	 addresses.  */
+      set_gdbarch_create_memtag_notes_from_range
+	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+				      aarch64_linux_decode_memtag_note);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..7da9de4aefb 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* NT_MEMTAG header for MTE tags.  */
+struct tag_dump_mte
+{
+  /* Size of the tag granule in bytes.  */
+  uint16_t granule_byte_size;
+  /* Size of the tag in bits.  */
+  uint16_t tag_bit_size;
+  /* Reserved field for the future.  */
+  uint16_t __unused;
+};
+
+/* Size of the MTE header for a NT_MEMTAG note.  */
+#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
+				   + sizeof (tag_dump_mte::tag_bit_size) \
+				   + sizeof (tag_dump_mte::__unused))
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..24c5bf29a11 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -51,6 +51,8 @@
 #include "gdbcmd.h"
 #include "xml-tdesc.h"
 #include "observable.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
   return true;
 }
 
+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+
+  return (bfd_get_section_by_name (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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    error (_("gdbarch_decode_memtag_note not implemented for this "
+	     "architecture."));
+
+  memtag_note_info info;
+  info.memtag_section = nullptr;
+
+  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
+       note.  Check if we need to fetch tags from a different section.  */
+    if (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;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 90d827a50e7..1b001e6cacb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   fprintf_unfiltered (file,
                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                       host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
   fprintf_unfiltered (file,
                       "gdbarch_dump: decr_pc_after_break = %s\n",
                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
 # Find core file memory regions
 M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
 
+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
 # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
 # core file into buffer READBUF with length LEN.  Return the number of bytes read
 # (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..6192cc4421b 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -41,6 +41,8 @@
 #include "gdbsupport/gdb_optional.h"
 #include "gcore.h"
 #include "gcore-elf.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #include <ctype.h>
 
@@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  memtag_note_info info;
+  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+				  gdb::unique_xmalloc_ptr<char> &note_data,
+				  int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  /* Parse the contents of smaps into a vector.  */
+  std::vector<struct smaps_data> smaps
+    = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+	 memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+	continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+	 this particular memory range, including the header.
+
+	 If the notes are too big, we may need to break up the transfer
+	 into smaller chunks.
+
+	 If the architecture returns an empty vector, that means there are
+	 no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes
+	= gdbarch_create_memtag_notes_from_range (gdbarch,
+						  map.start_address,
+						  map.end_address);
+      /* Write notes to the core file.  */
+      for (const gdb::byte_vector &note : memory_tag_notes)
+	{
+	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
+					       note_size, "CORE",
+					       NT_MEMTAG, note.data (),
+					       note.size ()));
+	}
+    }
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	return NULL;
     }
 
+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
   /* File mappings.  */
   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
 
diff --git a/gdb/memtag.c b/gdb/memtag.c
new file mode 100644
index 00000000000..4d92ecde84a
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,88 @@
+/* GDB generic memory tagging functions.
+
+   Copyright (C) 2021 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 "memtag.h"
+#include "gdbsupport/memtag.h"
+#include "bfd.h"
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return a pointer to a .memtag section containing ADDRESS or nullptr
+   of none are found.
+
+   If SECTION is provided, search from that section onwards.  */
+
+bool
+get_next_core_memtag_section (bfd *abfd, asection *section,
+			      CORE_ADDR address, memtag_note_info &info)
+{
+  /* If SECTION is nullptr, start a fresh lookup.  */
+  if (section == nullptr)
+    section = bfd_get_section_by_name (abfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
+	{
+	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
+		     "header.\n"
+		     "Expected %s bytes but got %s bytes."),
+		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
+		   pulongest (note_size));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (abfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  It contains the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
+      uint64_t end_address
+	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
+			    + sizeof (tag_dump_header::start_vma));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  info.start_address = start_address;
+	  info.end_address = end_address;
+	  info.note = note;
+	  info.memtag_section = section;
+	  return true;
+	}
+    }
+  return false;
+}
diff --git a/gdb/memtag.h b/gdb/memtag.h
new file mode 100644
index 00000000000..43c9efb39a3
--- /dev/null
+++ b/gdb/memtag.h
@@ -0,0 +1,46 @@
+/* GDB generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef MEMTAG_H
+#define MEMTAG_H
+
+#include "bfd.h"
+
+struct memtag_note_info
+{
+  CORE_ADDR start_address;
+  CORE_ADDR end_address;
+  gdb::byte_vector note;
+  asection *memtag_section;
+};
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
+   otherwise.
+
+   If SECTION is provided, search from that section onwards. If SECTION is
+   nullptr, then start a new search.
+
+   If a .memtag section containing ADDRESS is found, fill INFO with data
+   about such section.  Otherwise leave it unchanged.  */
+
+bool get_next_core_memtag_section (bfd *abfd, asection *section,
+				   CORE_ADDR address, memtag_note_info &info);
+
+#endif /* MEMTAG_H */
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..d0bcd036972
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,111 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart $binfile
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
new file mode 100644
index 00000000000..bb47eed220b
--- /dev/null
+++ b/gdbsupport/memtag.h
@@ -0,0 +1,39 @@
+/* Generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef GDBSUPPORT_MEMTAG_H
+#define GDBSUPPORT_MEMTAG_H
+
+/* Generic NT_MEMTAG header.  */
+struct tag_dump_header
+{
+  /* Tag format.  */
+  uint16_t format;
+  /* Start address of the tagged range.  */
+  uint64_t start_vma;
+  /* End address of the tagged range.  */
+  uint64_t end_vma;
+};
+
+/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
+   and should be shared with OS-specific and arch-specific code.  */
+#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
+				       + sizeof (tag_dump_header::start_vma) \
+				       + sizeof (tag_dump_header::end_vma))
+
+#endif /* GDBSUPPORT_MEMTAG_H */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH,v5][AArch64] MTE corefile support
  2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
                   ` (4 preceding siblings ...)
  2021-05-31 16:44 ` [PATCH,v4][AArch64] " Luis Machado
@ 2021-06-01 17:45 ` Luis Machado
  2021-06-15 14:10   ` [Ping][PATCH,v5][AArch64] " Luis Machado
                     ` (4 more replies)
  5 siblings, 5 replies; 47+ messages in thread
From: Luis Machado @ 2021-06-01 17:45 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas

Updates on v5:

- Fixed format warning output.

Updates on v4:

- Calculate sizes based on individual struct field sizes.

Updates on v3:

- Addressed review comments.
- New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
- Updated code documentation.
- Removed code duplication.

Updates on v2:

- Reworked core_target::fetch_memtags to handle cases where address + len runs
  over the NT_MEMTAG note.
- Turned a few magic numbers into constants. There is an unfortunate duplication
  of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
  no file generic enough that gets included by both corelow and linux-tdep.
- More sanity checks to make sure the note format is correct.
- Documented aarch64_linux_decode_memtag_note a little more.

---

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
  uint64_t start_vma;
  uint64_t end_vma;
};

struct tag_dump_mte
{
  uint16_t granule_byte_size;
  uint16_t tag_bit_size;
  uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* Makefile.in (COMMON_SFILES): Add memtag.c.
	* NEWS: Mention core file support for memory tagging.
	* aarch64-linux-tdep.c: Include elf/common.h.
	Include gdbsupport/memtag.h.
	(MAX_TAGS_TO_TRANSFER): New constant.
	(aarch64_linux_create_memtag_notes_from_range): New function.
	(aarch64_linux_decode_memtag_note): Likewise.
	(aarch64_linux_init_abi): Register new core file hooks.
	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
	(core_target) <supports_memory_tagging, fetch_memtags>: New
	method overrides.
	* gdbarch.c: Regenerate.
	* gdbarch.h: Likewise.
	* gdbarch.sh (create_memtag_notes_from_range): New hook.
	(decode_memtag_note): Likewise.
	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
	(linux_address_in_memtag_page): Renamed to...
	(linux_process_address_in_memtag_page): ... this.
	(linux_core_file_address_in_memtag_page): New function.
	(linux_address_in_memtag_page): Likewise.
	(linux_make_memtag_corefile_notes): Likewise.
	(linux_make_corefile_notes): Handle memory tag notes.
	* memtag.c: New file.
	* memtag.h: New file.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
	for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* gdb.arch/aarch64-mte-gcore.c: New file.
	* gdb.arch/aarch64-mte-gcore.exp: New file.

gdbsupport/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>

	* memtag.h: New file.
---
 gdb/Makefile.in                              |   1 +
 gdb/NEWS                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.h                 |  17 ++
 gdb/corelow.c                                |  63 ++++++
 gdb/doc/gdb.texinfo                          |   4 +
 gdb/gdbarch.c                                |  64 ++++++
 gdb/gdbarch.h                                |  16 ++
 gdb/gdbarch.sh                               |   6 +
 gdb/linux-tdep.c                             |  97 +++++++-
 gdb/memtag.c                                 |  88 ++++++++
 gdb/memtag.h                                 |  46 ++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
 gdbsupport/memtag.h                          |  39 ++++
 15 files changed, 867 insertions(+), 3 deletions(-)
 create mode 100644 gdb/memtag.c
 create mode 100644 gdb/memtag.h
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
 create mode 100644 gdbsupport/memtag.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index f664d964536..12fb3b390b1 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1100,6 +1100,7 @@ COMMON_SFILES = \
 	memattr.c \
 	memory-map.c \
 	memrange.c \
+	memtag.c \
 	minidebug.c \
 	minsyms.c \
 	mipsread.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** Changes since GDB 10
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
 * GDB now supports general memory tagging functionality if the underlying
   architecture supports the proper primitives and hooks.  Currently this is
   enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..04498f3b6c0 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -52,6 +52,9 @@
 #include "value.h"
 
 #include "gdbsupport/selftest.h"
+#include "gdbsupport/memtag.h"
+
+#include "elf/common.h"
 
 /* Signal frame handling.
 
@@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
+				     + NT_MEMTAG_MTE_HEADER_SIZE)
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+					      CORE_ADDR start_address,
+					      CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (start_address,
+						  end_address - start_address,
+						  AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  notes.resize (0);
+	  return notes;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Create the header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  gdb_byte *buf = notes[0].data ();
+
+  /* Generic header.  */
+  /* Tag dump format */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
+			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
+  buf += sizeof (tag_dump_header::format);
+
+  /* Start address */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
+			  byte_order, start_address);
+  buf += sizeof (tag_dump_header::start_vma);
+
+  /* End address */
+  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
+			  byte_order, end_address);
+  buf += sizeof (tag_dump_header::end_vma);
+
+  /* MTE-specific header.  */
+  /* Granule byte size */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
+			  byte_order, AARCH64_MTE_GRANULE_SIZE);
+  buf += sizeof (tag_dump_mte::granule_byte_size);
+
+  /* Tag bit size */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
+			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
+  buf += sizeof (tag_dump_mte::tag_bit_size);
+
+  /* Unused value */
+  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
+
+  /* Store the tags.  */
+  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
+	  tags.size ());
+
+  return notes;
+}
+
+/* AArch64 Linux implementation of the decode_memtag_note gdbarch
+   hook.  Decode a memory tag note and return the requested tags.
+
+   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
+   range.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
+				  gdb::array_view <const gdb_byte> note,
+				  CORE_ADDR address, size_t length)
+{
+  gdb::byte_vector tags;
+
+  /* Sanity check.  */
+  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
+    {
+      warning (_("Malformed core note - too short for MTE header.\n"
+		 "Expected %s bytes but got %s bytes."),
+	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
+	       pulongest (note.size ()));
+      return tags;
+    }
+
+  /* The amount of memory tag granules we need to fetch.  */
+  size_t granules
+    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to decode, just return.  */
+  if (granules == 0)
+    return tags;
+
+  /* Read the generic header.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  const gdb_byte *buf = note.data ();
+
+  unsigned int format
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
+				byte_order);
+  buf += sizeof (tag_dump_header::format);
+
+  CORE_ADDR start_vma
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
+				byte_order);
+  buf += sizeof (tag_dump_header::start_vma);
+
+  CORE_ADDR end_vma
+    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
+				byte_order);
+  buf += sizeof (tag_dump_header::end_vma);
+
+  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
+     range of addresses.  */
+  gdb_assert (address + length < end_vma);
+
+  /* Is the tag header format correct for this note?  */
+  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
+    {
+      warning (_("Unexpected memory tag note format.\n"
+		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
+	       format);
+      return tags;
+    }
+
+  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
+
+  /* Does the number of tag bytes in this note match the expected number
+     of tag bytes the note says it has?  */
+  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
+    {
+      warning (_("Unexpected tag data size.\n"
+		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
+	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
+      return tags;
+    }
+
+  /* Calculate how many granules we need to skip to get to the granule of
+     ADDRESS.  Align both the start address and the requested address
+     so it is easier to get the number of granules to skip.  This way we
+     don't need to consider cases where ADDRESS falls in the middle of a
+     tag granule range.  */
+  CORE_ADDR aligned_start_address
+    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
+  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
+
+  size_t skipped_granules
+    = aarch64_mte_get_tag_granules (aligned_start_address,
+				    aligned_address - aligned_start_address,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* Point to the block of data that contains the first granule we are
+     interested in.  */
+  const gdb::array_view<const gdb_byte> tags_data
+    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
+
+  /* Read the tag granules.  */
+  for (size_t i = 0; i < granules; i++)
+      tags.push_back (tags_data[i]);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create memory tag notes for a particular range of
+	 addresses.  */
+      set_gdbarch_create_memtag_notes_from_range
+	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
+
+      /* Core file helper to decode a memory tag note.  */
+      set_gdbarch_decode_memtag_note (gdbarch,
+				      aarch64_linux_decode_memtag_note);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index 2aa97eb861a..7da9de4aefb 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* NT_MEMTAG header for MTE tags.  */
+struct tag_dump_mte
+{
+  /* Size of the tag granule in bytes.  */
+  uint16_t granule_byte_size;
+  /* Size of the tag in bits.  */
+  uint16_t tag_bit_size;
+  /* Reserved field for the future.  */
+  uint16_t __unused;
+};
+
+/* Size of the MTE header for a NT_MEMTAG note.  */
+#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
+				   + sizeof (tag_dump_mte::tag_bit_size) \
+				   + sizeof (tag_dump_mte::__unused))
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 452b4dd4f9a..24c5bf29a11 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -51,6 +51,8 @@
 #include "gdbcmd.h"
 #include "xml-tdesc.h"
 #include "observable.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
   return true;
 }
 
+/* Implementation of the "supports_memory_tagging" target_ops method.  */
+
+bool
+core_target::supports_memory_tagging ()
+{
+  /* Look for memory tag notes.  If they exist, that means this core file
+     supports memory tagging.  */
+
+  return (bfd_get_section_by_name (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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* Make sure we have a way to decode the memory tag notes.  */
+  if (!gdbarch_decode_memtag_note_p (gdbarch))
+    error (_("gdbarch_decode_memtag_note not implemented for this "
+	     "architecture."));
+
+  memtag_note_info info;
+  info.memtag_section = nullptr;
+
+  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
+       note.  Check if we need to fetch tags from a different section.  */
+    if (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;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 90d827a50e7..1b001e6cacb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index 208cf4b5aaa..de384da2e9a 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -283,6 +283,8 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
+  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
+  /* Skip verify of decode_memtag_note, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   fprintf_unfiltered (file,
                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
                       host_address_to_string (gdbarch->core_xfer_siginfo));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
+                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
+                      gdbarch_decode_memtag_note_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: decode_memtag_note = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_note));
   fprintf_unfiltered (file,
                       "gdbarch_dump: decr_pc_after_break = %s\n",
                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
@@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_notes_from_range != NULL;
+}
+
+std::vector<gdb::byte_vector>
+gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
+  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
+}
+
+void
+set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
+{
+  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
+}
+
+bool
+gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_note != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_note != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
+  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
+                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
+{
+  gdbarch->decode_memtag_note = decode_memtag_note;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 7157e5596fd..80e244624de 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Create memory tag core file notes given a range of addresses. */
+
+extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
+
+typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
+extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
+
+/* Decode a memory tag note and return the tags that it contains. */
+
+extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 43e51341f97..7c5eed0780c 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
 # Find core file memory regions
 M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
 
+# Create memory tag core file notes given a range of addresses.
+M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
+
+# Decode a memory tag note and return the tags that it contains.
+M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
+
 # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
 # core file into buffer READBUF with length LEN.  Return the number of bytes read
 # (zero indicates failure).
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 927e69bf1e1..6192cc4421b 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -41,6 +41,8 @@
 #include "gdbsupport/gdb_optional.h"
 #include "gcore.h"
 #include "gcore-elf.h"
+#include "gdbsupport/memtag.h"
+#include "memtag.h"
 
 #include <ctype.h>
 
@@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  memtag_note_info info;
+  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
+/* For each memory map entry that has memory tagging enabled, create a new
+   core file note that contains all of its memory tags.  Save the data to
+   NOTE_DATA and update NOTE_SIZE accordingly.  */
+
+static void
+linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
+				  gdb::unique_xmalloc_ptr<char> &note_data,
+				  int *note_size)
+{
+  if (current_inferior ()->fake_pid_p)
+    return;
+
+  /* If the architecture doesn't have a hook to return memory tag notes,
+     there is nothing left to do.  */
+  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
+    return;
+
+  pid_t pid = current_inferior ()->pid;
+
+  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
+
+  gdb::unique_xmalloc_ptr<char> data
+    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
+
+  if (data == nullptr)
+    return;
+
+  /* Parse the contents of smaps into a vector.  */
+  std::vector<struct smaps_data> smaps
+    = parse_smaps_data (data.get (), smaps_file);
+
+  for (const smaps_data &map : smaps)
+    {
+      /* Does this mapping have memory tagging enabled? If so, save the
+	 memory tags to the core file note.  */
+      if (map.vmflags.memory_tagging == 0)
+	continue;
+
+      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
+	 this particular memory range, including the header.
+
+	 If the notes are too big, we may need to break up the transfer
+	 into smaller chunks.
+
+	 If the architecture returns an empty vector, that means there are
+	 no memory tag notes to write.  */
+      std::vector<gdb::byte_vector> memory_tag_notes
+	= gdbarch_create_memtag_notes_from_range (gdbarch,
+						  map.start_address,
+						  map.end_address);
+      /* Write notes to the core file.  */
+      for (const gdb::byte_vector &note : memory_tag_notes)
+	{
+	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
+					       note_size, "CORE",
+					       NT_MEMTAG, note.data (),
+					       note.size ()));
+	}
+    }
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
 	return NULL;
     }
 
+  /* Dump the memory tags, if any.  */
+  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
+
   /* File mappings.  */
   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
 
diff --git a/gdb/memtag.c b/gdb/memtag.c
new file mode 100644
index 00000000000..4d92ecde84a
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,88 @@
+/* GDB generic memory tagging functions.
+
+   Copyright (C) 2021 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 "memtag.h"
+#include "gdbsupport/memtag.h"
+#include "bfd.h"
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return a pointer to a .memtag section containing ADDRESS or nullptr
+   of none are found.
+
+   If SECTION is provided, search from that section onwards.  */
+
+bool
+get_next_core_memtag_section (bfd *abfd, asection *section,
+			      CORE_ADDR address, memtag_note_info &info)
+{
+  /* If SECTION is nullptr, start a fresh lookup.  */
+  if (section == nullptr)
+    section = bfd_get_section_by_name (abfd, ".memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t note_size = bfd_section_size (section);
+
+      /* If the note is smaller than the size of the header, this core note
+	 is malformed.  */
+      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
+	{
+	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
+		     "header.\n"
+		     "Expected %s bytes but got %s bytes."),
+		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
+		   pulongest (note_size));
+	  return false;
+	}
+
+      gdb::byte_vector note (note_size);
+
+      /* Fetch the contents of this particular memtag note.  */
+      if (!bfd_get_section_contents (abfd, section,
+				     note.data (), 0, note_size))
+	{
+	  warning (_("could not get core note contents."));
+	  return false;
+	}
+
+      /* Read the generic header of the note.  It contains the format,
+	 start address and end address.  */
+      uint64_t start_address
+	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
+      uint64_t end_address
+	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
+			    + sizeof (tag_dump_header::start_vma));
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  info.start_address = start_address;
+	  info.end_address = end_address;
+	  info.note = note;
+	  info.memtag_section = section;
+	  return true;
+	}
+    }
+  return false;
+}
diff --git a/gdb/memtag.h b/gdb/memtag.h
new file mode 100644
index 00000000000..43c9efb39a3
--- /dev/null
+++ b/gdb/memtag.h
@@ -0,0 +1,46 @@
+/* GDB generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef MEMTAG_H
+#define MEMTAG_H
+
+#include "bfd.h"
+
+struct memtag_note_info
+{
+  CORE_ADDR start_address;
+  CORE_ADDR end_address;
+  gdb::byte_vector note;
+  asection *memtag_section;
+};
+
+/* Helper function to walk through NT_MEMTAG notes in a core file.
+
+   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
+   otherwise.
+
+   If SECTION is provided, search from that section onwards. If SECTION is
+   nullptr, then start a new search.
+
+   If a .memtag section containing ADDRESS is found, fill INFO with data
+   about such section.  Otherwise leave it unchanged.  */
+
+bool get_next_core_memtag_section (bfd *abfd, asection *section,
+				   CORE_ADDR address, memtag_note_info &info);
+
+#endif /* MEMTAG_H */
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..d0bcd036972
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,111 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart $binfile
+
+if { $program_loaded } {
+    return -1
+}
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
new file mode 100644
index 00000000000..bb47eed220b
--- /dev/null
+++ b/gdbsupport/memtag.h
@@ -0,0 +1,39 @@
+/* Generic memory tagging definitions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef GDBSUPPORT_MEMTAG_H
+#define GDBSUPPORT_MEMTAG_H
+
+/* Generic NT_MEMTAG header.  */
+struct tag_dump_header
+{
+  /* Tag format.  */
+  uint16_t format;
+  /* Start address of the tagged range.  */
+  uint64_t start_vma;
+  /* End address of the tagged range.  */
+  uint64_t end_vma;
+};
+
+/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
+   and should be shared with OS-specific and arch-specific code.  */
+#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
+				       + sizeof (tag_dump_header::start_vma) \
+				       + sizeof (tag_dump_header::end_vma))
+
+#endif /* GDBSUPPORT_MEMTAG_H */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

* [Ping][PATCH,v5][AArch64] MTE corefile support
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
@ 2021-06-15 14:10   ` Luis Machado
  2021-06-24 14:00   ` [PATCH,v5][AArch64] " Alan Hayward
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-06-15 14:10 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas


On 6/1/21 2:45 PM, Luis Machado wrote:
> Updates on v5:
> 
> - Fixed format warning output.
> 
> Updates on v4:
> 
> - Calculate sizes based on individual struct field sizes.
> 
> Updates on v3:
> 
> - Addressed review comments.
> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> - Updated code documentation.
> - Removed code duplication.
> 
> Updates on v2:
> 
> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>    over the NT_MEMTAG note.
> - Turned a few magic numbers into constants. There is an unfortunate duplication
>    of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>    no file generic enough that gets included by both corelow and linux-tdep.
> - More sanity checks to make sure the note format is correct.
> - Documented aarch64_linux_decode_memtag_note a little more.
> 
> ---
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>    uint64_t start_vma;
>    uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>    uint16_t granule_byte_size;
>    uint16_t tag_bit_size;
>    uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 
> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> 	* NEWS: Mention core file support for memory tagging.
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	Include gdbsupport/memtag.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> 	(linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* memtag.c: New file.
> 	* memtag.h: New file.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> 
> gdbsupport/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* memtag.h: New file.
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |   4 +
>   gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.h                 |  17 ++
>   gdb/corelow.c                                |  63 ++++++
>   gdb/doc/gdb.texinfo                          |   4 +
>   gdb/gdbarch.c                                |  64 ++++++
>   gdb/gdbarch.h                                |  16 ++
>   gdb/gdbarch.sh                               |   6 +
>   gdb/linux-tdep.c                             |  97 +++++++-
>   gdb/memtag.c                                 |  88 ++++++++
>   gdb/memtag.h                                 |  46 ++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
>   gdbsupport/memtag.h                          |  39 ++++
>   15 files changed, 867 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>   create mode 100644 gdbsupport/memtag.h
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index f664d964536..12fb3b390b1 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>   
>   *** Changes since GDB 10
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
>   * GDB now supports general memory tagging functionality if the underlying
>     architecture supports the proper primitives and hooks.  Currently this is
>     enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..04498f3b6c0 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -52,6 +52,9 @@
>   #include "value.h"
>   
>   #include "gdbsupport/selftest.h"
> +#include "gdbsupport/memtag.h"
> +
> +#include "elf/common.h"
>   
>   /* Signal frame handling.
>   
> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> +						  end_address - start_address,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);
> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +			  byte_order, start_address);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +			  byte_order, end_address);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (tag_dump_mte::granule_byte_size);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (tag_dump_mte::tag_bit_size);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> +    {
> +      warning (_("Malformed core note - too short for MTE header.\n"
> +		 "Expected %s bytes but got %s bytes."),
> +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> +	       pulongest (note.size ()));
> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  size_t granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  const gdb_byte *buf = note.data ();
> +
> +  unsigned int format
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  CORE_ADDR start_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  CORE_ADDR end_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> +     range of addresses.  */
> +  gdb_assert (address + length < end_vma);
> +
> +  /* Is the tag header format correct for this note?  */
> +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format.\n"
> +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> +	       format);
> +      return tags;
> +    }
> +
> +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> +
> +  /* Does the number of tag bytes in this note match the expected number
> +     of tag bytes the note says it has?  */
> +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> +    {
> +      warning (_("Unexpected tag data size.\n"
> +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  size_t skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb::array_view<const gdb_byte> tags_data
> +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> +
> +  /* Read the tag granules.  */
> +  for (size_t i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range
> +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..7da9de4aefb 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* NT_MEMTAG header for MTE tags.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> +				   + sizeof (tag_dump_mte::tag_bit_size) \
> +				   + sizeof (tag_dump_mte::__unused))
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..24c5bf29a11 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -51,6 +51,8 @@
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
>   #include "observable.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>     return true;
>   }
>   
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +
> +  return (bfd_get_section_by_name (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    error (_("gdbarch_decode_memtag_note not implemented for this "
> +	     "architecture."));
> +
> +  memtag_note_info info;
> +  info.memtag_section = nullptr;
> +
> +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> +       note.  Check if we need to fetch tags from a different section.  */
> +    if (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 90d827a50e7..1b001e6cacb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                         host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: decr_pc_after_break = %s\n",
>                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>   # Find core file memory regions
>   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>   
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
>   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>   # core file into buffer READBUF with length LEN.  Return the number of bytes read
>   # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..6192cc4421b 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -41,6 +41,8 @@
>   #include "gdbsupport/gdb_optional.h"
>   #include "gcore.h"
>   #include "gcore-elf.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   
> @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_note_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  std::vector<struct smaps_data> smaps
> +    = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (const gdb::byte_vector &note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>   	return NULL;
>       }
>   
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>     /* File mappings.  */
>     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>   
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..4d92ecde84a
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,88 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 2021 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 "memtag.h"
> +#include "gdbsupport/memtag.h"
> +#include "bfd.h"
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> +   of none are found.
> +
> +   If SECTION is provided, search from that section onwards.  */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_note_info &info)
> +{
> +  /* If SECTION is nullptr, start a fresh lookup.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> +	{
> +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> +		     "header.\n"
> +		     "Expected %s bytes but got %s bytes."),
> +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> +		   pulongest (note_size));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (abfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  It contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> +      uint64_t end_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> +			    + sizeof (tag_dump_header::start_vma));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.note = note;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..43c9efb39a3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,46 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_note_info
> +{
> +  CORE_ADDR start_address;
> +  CORE_ADDR end_address;
> +  gdb::byte_vector note;
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a .memtag section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address, memtag_note_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..d0bcd036972
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,111 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> new file mode 100644
> index 00000000000..bb47eed220b
> --- /dev/null
> +++ b/gdbsupport/memtag.h
> @@ -0,0 +1,39 @@
> +/* Generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef GDBSUPPORT_MEMTAG_H
> +#define GDBSUPPORT_MEMTAG_H
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS-specific and arch-specific code.  */
> +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> +				       + sizeof (tag_dump_header::start_vma) \
> +				       + sizeof (tag_dump_header::end_vma))
> +
> +#endif /* GDBSUPPORT_MEMTAG_H */
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v5][AArch64] MTE corefile support
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
  2021-06-15 14:10   ` [Ping][PATCH,v5][AArch64] " Luis Machado
@ 2021-06-24 14:00   ` Alan Hayward
  2021-06-24 14:37     ` Luis Machado
  2021-07-01 13:50   ` [PING][PATCH,v5][AArch64] " Luis Machado
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 47+ messages in thread
From: Alan Hayward @ 2021-06-24 14:00 UTC (permalink / raw)
  To: Luis Machado
  Cc: gdb-patches\@sourceware.org, Simon Marchi, jhb@freebsd.org,
	david.spickett, Catalin Marinas



> On 1 Jun 2021, at 18:45, Luis Machado <luis.machado@linaro.org> wrote:
> 
> Updates on v5:
> 
> - Fixed format warning output.
> 
> Updates on v4:
> 
> - Calculate sizes based on individual struct field sizes.
> 
> Updates on v3:
> 
> - Addressed review comments.
> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> - Updated code documentation.
> - Removed code duplication.
> 
> Updates on v2:
> 
> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>  over the NT_MEMTAG note.
> - Turned a few magic numbers into constants. There is an unfortunate duplication
>  of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>  no file generic enough that gets included by both corelow and linux-tdep.
> - More sanity checks to make sure the note format is correct.
> - Documented aarch64_linux_decode_memtag_note a little more.
> 
> ---
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>  uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>  uint64_t start_vma;
>  uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>  uint16_t granule_byte_size;
>  uint16_t tag_bit_size;
>  uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 
> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> 	* NEWS: Mention core file support for memory tagging.
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	Include gdbsupport/memtag.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> 	(linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* memtag.c: New file.
> 	* memtag.h: New file.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> 
> gdbsupport/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* memtag.h: New file.
> ---
> gdb/Makefile.in                              |   1 +
> gdb/NEWS                                     |   4 +
> gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
> gdb/arch/aarch64-mte-linux.h                 |  17 ++
> gdb/corelow.c                                |  63 ++++++
> gdb/doc/gdb.texinfo                          |   4 +
> gdb/gdbarch.c                                |  64 ++++++
> gdb/gdbarch.h                                |  16 ++
> gdb/gdbarch.sh                               |   6 +
> gdb/linux-tdep.c                             |  97 +++++++-
> gdb/memtag.c                                 |  88 ++++++++
> gdb/memtag.h                                 |  46 ++++
> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
> gdbsupport/memtag.h                          |  39 ++++
> 15 files changed, 867 insertions(+), 3 deletions(-)
> create mode 100644 gdb/memtag.c
> create mode 100644 gdb/memtag.h
> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> create mode 100644 gdbsupport/memtag.h
> 

Note there were a few minor merge conflicts, nothing to worry about though.


> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index f664d964536..12fb3b390b1 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
> 	memattr.c \
> 	memory-map.c \
> 	memrange.c \
> +	memtag.c \
> 	minidebug.c \
> 	minsyms.c \
> 	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
> 
> *** Changes since GDB 10
> 
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> * GDB now supports general memory tagging functionality if the underlying
>   architecture supports the proper primitives and hooks.  Currently this is
>   enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..04498f3b6c0 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -52,6 +52,9 @@
> #include "value.h"
> 
> #include "gdbsupport/selftest.h"
> +#include "gdbsupport/memtag.h"
> +
> +#include "elf/common.h"
> 
> /* Signal frame handling.
> 
> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>     }
> }
> 
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> +						  end_address - start_address,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);

Should this be NT_MEMTAG_TOTAL_HEADER_SIZE + (granules * AARCH64_MTE_GRANULE_SIZE)
Get_granules has already divided by the granule size.

> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);

If you do the original resize after the while loop, then there would be no need to resize here.

> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +			  byte_order, start_address);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +			  byte_order, end_address);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (tag_dump_mte::granule_byte_size);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (tag_dump_mte::tag_bit_size);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> +    {
> +      warning (_("Malformed core note - too short for MTE header.\n"
> +		 "Expected %s bytes but got %s bytes."),
> +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> +	       pulongest (note.size ()));
> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  size_t granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  const gdb_byte *buf = note.data ();
> +
> +  unsigned int format
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  CORE_ADDR start_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  CORE_ADDR end_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> +     range of addresses.  */
> +  gdb_assert (address + length < end_vma);
> +
> +  /* Is the tag header format correct for this note?  */
> +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format.\n"
> +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> +	       format);
> +      return tags;
> +    }
> +
> +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> +
> +  /* Does the number of tag bytes in this note match the expected number
> +     of tag bytes the note says it has?  */
> +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> +    {
> +      warning (_("Unexpected tag data size.\n"
> +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  size_t skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb::array_view<const gdb_byte> tags_data
> +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> +
> +  /* Read the tag granules.  */
> +  for (size_t i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
> static void
> aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> {
> @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> 
>       set_gdbarch_report_signal_info (gdbarch,
> 				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range
> +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>     }
> 
>   /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..7da9de4aefb 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
> 
> /* We have one tag per 16 bytes of memory.  */
> #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
> #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
> #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
> 
> @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>    It is always possible to get the logical tag.  */
> extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
> 
> +/* NT_MEMTAG header for MTE tags.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> +				   + sizeof (tag_dump_mte::tag_bit_size) \
> +				   + sizeof (tag_dump_mte::__unused))
> +
> #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..24c5bf29a11 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -51,6 +51,8 @@
> #include "gdbcmd.h"
> #include "xml-tdesc.h"
> #include "observable.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
> 
> #ifndef O_LARGEFILE
> #define O_LARGEFILE 0
> @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
> 
>   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;
> +
>   /* A few helpers.  */
> 
>   /* Getter, see variable definition.  */
> @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>   return true;
> }
> 
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +
> +  return (bfd_get_section_by_name (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    error (_("gdbarch_decode_memtag_note not implemented for this "
> +	     "architecture."));
> +
> +  memtag_note_info info;
> +  info.memtag_section = nullptr;
> +
> +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> +       note.  Check if we need to fetch tags from a different section.  */
> +    if (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;
> +}
> +
> /* Get a pointer to the current core target.  If not connected to a
>    core target, return NULL.  */
> 
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 90d827a50e7..1b001e6cacb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
> option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
> documentation in the Linux kernel.
> 
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> @node i386
> @subsection x86 Architecture-specific Issues
> 
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>   gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>   /* Skip verify of iterate_over_regset_sections, has predicate.  */
>   /* Skip verify of make_corefile_notes, has predicate.  */
>   /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>   /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>   fprintf_unfiltered (file,
>                       "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                       host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>   fprintf_unfiltered (file,
>                       "gdbarch_dump: decr_pc_after_break = %s\n",
>                       core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>   gdbarch->find_memory_regions = find_memory_regions;
> }
> 
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
> bool
> gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
> {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
> extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
> extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
> 
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
> /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>    core file into buffer READBUF with length LEN.  Return the number of bytes read
>    (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
> # Find core file memory regions
> M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
> 
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
> # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> # core file into buffer READBUF with length LEN.  Return the number of bytes read
> # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..6192cc4421b 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -41,6 +41,8 @@
> #include "gdbsupport/gdb_optional.h"
> #include "gcore.h"
> #include "gcore-elf.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
> 
> #include <ctype.h>
> 
> @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
>   return smaps;
> }
> 
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
> 
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
> {
>   if (current_inferior ()->fake_pid_p)
>     return false;
> @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
>   return false;
> }
> 
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_note_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  std::vector<struct smaps_data> smaps
> +    = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (const gdb::byte_vector &note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
> /* List memory regions in the inferior for a corefile.  */
> 
> static int
> @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
> 	return NULL;
>     }
> 
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>   /* File mappings.  */
>   linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
> 
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..4d92ecde84a
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,88 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 2021 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 "memtag.h"
> +#include "gdbsupport/memtag.h"
> +#include "bfd.h"
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> +   of none are found.
> +
> +   If SECTION is provided, search from that section onwards.  */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_note_info &info)
> +{
> +  /* If SECTION is nullptr, start a fresh lookup.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> +	{
> +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> +		     "header.\n"
> +		     "Expected %s bytes but got %s bytes."),
> +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> +		   pulongest (note_size));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (abfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  It contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> +      uint64_t end_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> +			    + sizeof (tag_dump_header::start_vma));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.note = note;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..43c9efb39a3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,46 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_note_info
> +{
> +  CORE_ADDR start_address;
> +  CORE_ADDR end_address;
> +  gdb::byte_vector note;
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a .memtag section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address, memtag_note_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..d0bcd036972
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,111 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> new file mode 100644
> index 00000000000..bb47eed220b
> --- /dev/null
> +++ b/gdbsupport/memtag.h
> @@ -0,0 +1,39 @@
> +/* Generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef GDBSUPPORT_MEMTAG_H
> +#define GDBSUPPORT_MEMTAG_H
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS-specific and arch-specific code.  */
> +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> +				       + sizeof (tag_dump_header::start_vma) \
> +				       + sizeof (tag_dump_header::end_vma))
> +
> +#endif /* GDBSUPPORT_MEMTAG_H */
> -- 
> 2.25.1
> 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v5][AArch64] MTE corefile support
  2021-06-24 14:00   ` [PATCH,v5][AArch64] " Alan Hayward
@ 2021-06-24 14:37     ` Luis Machado
  2021-06-24 15:18       ` Alan Hayward
  0 siblings, 1 reply; 47+ messages in thread
From: Luis Machado @ 2021-06-24 14:37 UTC (permalink / raw)
  To: Alan Hayward
  Cc: gdb-patches\@sourceware.org, Simon Marchi, jhb@freebsd.org,
	david.spickett, Catalin Marinas

On 6/24/21 11:00 AM, Alan Hayward wrote:
> 
> 
>> On 1 Jun 2021, at 18:45, Luis Machado <luis.machado@linaro.org> wrote:
>>
>> Updates on v5:
>>
>> - Fixed format warning output.
>>
>> Updates on v4:
>>
>> - Calculate sizes based on individual struct field sizes.
>>
>> Updates on v3:
>>
>> - Addressed review comments.
>> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
>> - Updated code documentation.
>> - Removed code duplication.
>>
>> Updates on v2:
>>
>> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>>   over the NT_MEMTAG note.
>> - Turned a few magic numbers into constants. There is an unfortunate duplication
>>   of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>>   no file generic enough that gets included by both corelow and linux-tdep.
>> - More sanity checks to make sure the note format is correct.
>> - Documented aarch64_linux_decode_memtag_note a little more.
>>
>> ---
>>
>> Teach GDB how to dump memory tags when using the gcore command and how
>> to read them back from a core file generated via gcore or the kernel.
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own NT_MEMTAG note. A section named ".memtag" is created for each of those
>> when reading the core file back.
>>
>> Dumping memory tags
>> -
>>
>> When using the gcore command to dump a core file, GDB will go through the maps
>> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
>> passed to an arch-specific gdbarch hook that generates a vector of blobs of
>> memory tag data that are blindly put into a NT_MEMTAG note.
>>
>> The vector is used because we may have, in the future,  multiple tag types for
>> a particular memory range.
>>
>> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
>> like so:
>>
>> struct tag_dump_header
>> {
>>   uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>>   uint64_t start_vma;
>>   uint64_t end_vma;
>> };
>>
>> struct tag_dump_mte
>> {
>>   uint16_t granule_byte_size;
>>   uint16_t tag_bit_size;
>>   uint16_t __unused;
>> };
>>
>> The only bits meant to be generic are the tag_dump_format, start_vma and
>> end_vma fields.
>>
>> The format-specific data is supposed to be opaque and only useful for the
>> arch-specific code.
>>
>> We can extend the format in the future to make room for other memory tag
>> layouts.
>>
>> Reading memory tags
>> -
>>
>> When reading a core file that contains NT_MEMTAG entries, GDB will use
>> a different approach to check for tagged memory range. Rather than looking
>> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
>> memory range.
>>
>> When reading tags, GDB will now use the core target's implementation of
>> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
>> is fed into an arch-specific hook that will decode the memory tag format and
>> return a vector of tags.
>>
>> I've added a test to exercise writing and reading of memory tags in core
>> files.
>>
>> gdb/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
>> 	* NEWS: Mention core file support for memory tagging.
>> 	* aarch64-linux-tdep.c: Include elf/common.h.
>> 	Include gdbsupport/memtag.h.
>> 	(MAX_TAGS_TO_TRANSFER): New constant.
>> 	(aarch64_linux_create_memtag_notes_from_range): New function.
>> 	(aarch64_linux_decode_memtag_note): Likewise.
>> 	(aarch64_linux_init_abi): Register new core file hooks.
>> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
>> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
>> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
>> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
>> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
>> 	method overrides.
>> 	* gdbarch.c: Regenerate.
>> 	* gdbarch.h: Likewise.
>> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
>> 	(decode_memtag_note): Likewise.
>> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
>> 	(linux_address_in_memtag_page): Renamed to...
>> 	(linux_process_address_in_memtag_page): ... this.
>> 	(linux_core_file_address_in_memtag_page): New function.
>> 	(linux_address_in_memtag_page): Likewise.
>> 	(linux_make_memtag_corefile_notes): Likewise.
>> 	(linux_make_corefile_notes): Handle memory tag notes.
>> 	* memtag.c: New file.
>> 	* memtag.h: New file.
>>
>> gdb/doc/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
>> 	for memory tagging in core files.
>>
>> gdb/testsuite/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* gdb.arch/aarch64-mte-gcore.c: New file.
>> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
>>
>> gdbsupport/ChangeLog:
>>
>> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
>>
>> 	* memtag.h: New file.
>> ---
>> gdb/Makefile.in                              |   1 +
>> gdb/NEWS                                     |   4 +
>> gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
>> gdb/arch/aarch64-mte-linux.h                 |  17 ++
>> gdb/corelow.c                                |  63 ++++++
>> gdb/doc/gdb.texinfo                          |   4 +
>> gdb/gdbarch.c                                |  64 ++++++
>> gdb/gdbarch.h                                |  16 ++
>> gdb/gdbarch.sh                               |   6 +
>> gdb/linux-tdep.c                             |  97 +++++++-
>> gdb/memtag.c                                 |  88 ++++++++
>> gdb/memtag.h                                 |  46 ++++
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
>> gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
>> gdbsupport/memtag.h                          |  39 ++++
>> 15 files changed, 867 insertions(+), 3 deletions(-)
>> create mode 100644 gdb/memtag.c
>> create mode 100644 gdb/memtag.h
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> create mode 100644 gdbsupport/memtag.h
>>
> 
> Note there were a few minor merge conflicts, nothing to worry about though.

I'll check again and will refresh the patch.

> 
> 
>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>> index f664d964536..12fb3b390b1 100644
>> --- a/gdb/Makefile.in
>> +++ b/gdb/Makefile.in
>> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
>> 	memattr.c \
>> 	memory-map.c \
>> 	memrange.c \
>> +	memtag.c \
>> 	minidebug.c \
>> 	minsyms.c \
>> 	mipsread.c \
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index ab678acec8b..58b9f739d4f 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,10 @@
>>
>> *** Changes since GDB 10
>>
>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>> +  reading memory tag data for AArch64 MTE from core files generated by
>> +  the gcore command or the Linux kernel.
>> +
>> * GDB now supports general memory tagging functionality if the underlying
>>    architecture supports the proper primitives and hooks.  Currently this is
>>    enabled only for AArch64 MTE.
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index e9761ed2189..04498f3b6c0 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -52,6 +52,9 @@
>> #include "value.h"
>>
>> #include "gdbsupport/selftest.h"
>> +#include "gdbsupport/memtag.h"
>> +
>> +#include "elf/common.h"
>>
>> /* Signal frame handling.
>>
>> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>      }
>> }
>>
>> +/* Memory tag note header size.  Includes both the generic and the
>> +   arch-specific parts.  */
>> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
>> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
>> +
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
>> +   gdbarch hook.  Create core file notes for memory tags.  */
>> +
>> +static std::vector<gdb::byte_vector>
>> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
>> +					      CORE_ADDR start_address,
>> +					      CORE_ADDR end_address)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
>> +						  end_address - start_address,
>> +						  AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
>> +     at the moment).  */
>> +  std::vector<gdb::byte_vector> notes (1);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return notes;
>> +
>> +  /* Adjust the MTE note size to hold the header + tags.  */
>> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> 
> Should this be NT_MEMTAG_TOTAL_HEADER_SIZE + (granules * AARCH64_MTE_GRANULE_SIZE)
> Get_granules has already divided by the granule size.
> 

We are storing 1 tag per byte. So the number of granules is the number 
of tags. If we multiply by AARCH64_MTE_GRANULE_SIZE, we will have 16 
times more tags.

>> +
>> +  CORE_ADDR address = start_address;
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
>> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +	  granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +				 static_cast<int> (memtag_type::allocation)))
>> +	{
>> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
>> +		     phex_nz (start_address, sizeof (start_address)),
>> +		     phex_nz (end_address, sizeof (end_address)));
>> +	  notes.resize (0);
> 
> If you do the original resize after the while loop, then there would be no need to resize here.
> 

I'm not sure I understand. Where do you suggest the resizing to be placed?

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH,v5][AArch64] MTE corefile support
  2021-06-24 14:37     ` Luis Machado
@ 2021-06-24 15:18       ` Alan Hayward
  0 siblings, 0 replies; 47+ messages in thread
From: Alan Hayward @ 2021-06-24 15:18 UTC (permalink / raw)
  To: Luis Machado
  Cc: gdb-patches\@sourceware.org, Simon Marchi, jhb@freebsd.org,
	david.spickett, Catalin Marinas



On 24 Jun 2021, at 15:37, Luis Machado <luis.machado@linaro.org<mailto:luis.machado@linaro.org>> wrote:

On 6/24/21 11:00 AM, Alan Hayward wrote:
On 1 Jun 2021, at 18:45, Luis Machado <luis.machado@linaro.org<mailto:luis.machado@linaro.org>> wrote:

Updates on v5:

- Fixed format warning output.

Updates on v4:

- Calculate sizes based on individual struct field sizes.

Updates on v3:

- Addressed review comments.
- New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
- Updated code documentation.
- Removed code duplication.

Updates on v2:

- Reworked core_target::fetch_memtags to handle cases where address + len runs
 over the NT_MEMTAG note.
- Turned a few magic numbers into constants. There is an unfortunate duplication
 of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
 no file generic enough that gets included by both corelow and linux-tdep.
- More sanity checks to make sure the note format is correct.
- Documented aarch64_linux_decode_memtag_note a little more.

---

Teach GDB how to dump memory tags when using the gcore command and how
to read them back from a core file generated via gcore or the kernel.

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own NT_MEMTAG note. A section named ".memtag" is created for each of those
when reading the core file back.

Dumping memory tags
-

When using the gcore command to dump a core file, GDB will go through the maps
in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
passed to an arch-specific gdbarch hook that generates a vector of blobs of
memory tag data that are blindly put into a NT_MEMTAG note.

The vector is used because we may have, in the future,  multiple tag types for
a particular memory range.

Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
like so:

struct tag_dump_header
{
 uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
 uint64_t start_vma;
 uint64_t end_vma;
};

struct tag_dump_mte
{
 uint16_t granule_byte_size;
 uint16_t tag_bit_size;
 uint16_t __unused;
};

The only bits meant to be generic are the tag_dump_format, start_vma and
end_vma fields.

The format-specific data is supposed to be opaque and only useful for the
arch-specific code.

We can extend the format in the future to make room for other memory tag
layouts.

Reading memory tags
-

When reading a core file that contains NT_MEMTAG entries, GDB will use
a different approach to check for tagged memory range. Rather than looking
at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
memory range.

When reading tags, GDB will now use the core target's implementation of
fetch_memtags (store_memtags doesn't exist for core targets). Then the data
is fed into an arch-specific hook that will decode the memory tag format and
return a vector of tags.

I've added a test to exercise writing and reading of memory tags in core
files.

gdb/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* Makefile.in (COMMON_SFILES): Add memtag.c.
* NEWS: Mention core file support for memory tagging.
* aarch64-linux-tdep.c: Include elf/common.h.
Include gdbsupport/memtag.h.
(MAX_TAGS_TO_TRANSFER): New constant.
(aarch64_linux_create_memtag_notes_from_range): New function.
(aarch64_linux_decode_memtag_note): Likewise.
(aarch64_linux_init_abi): Register new core file hooks.
(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
(AARCH64_MTE_TAG_BIT_SIZE): New constant.
* corelow.c: Include gdbsupport/memtag.h and memtag.h.
(core_target) <supports_memory_tagging, fetch_memtags>: New
method overrides.
* gdbarch.c: Regenerate.
* gdbarch.h: Likewise.
* gdbarch.sh (create_memtag_notes_from_range): New hook.
(decode_memtag_note): Likewise.
* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
(linux_address_in_memtag_page): Renamed to...
(linux_process_address_in_memtag_page): ... this.
(linux_core_file_address_in_memtag_page): New function.
(linux_address_in_memtag_page): Likewise.
(linux_make_memtag_corefile_notes): Likewise.
(linux_make_corefile_notes): Handle memory tag notes.
* memtag.c: New file.
* memtag.h: New file.

gdb/doc/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
for memory tagging in core files.

gdb/testsuite/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* gdb.arch/aarch64-mte-gcore.c: New file.
* gdb.arch/aarch64-mte-gcore.exp: New file.

gdbsupport/ChangeLog:

YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org<mailto:luis.machado@linaro.org>>

* memtag.h: New file.
---
gdb/Makefile.in                              |   1 +
gdb/NEWS                                     |   4 +
gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
gdb/arch/aarch64-mte-linux.h                 |  17 ++
gdb/corelow.c                                |  63 ++++++
gdb/doc/gdb.texinfo                          |   4 +
gdb/gdbarch.c                                |  64 ++++++
gdb/gdbarch.h                                |  16 ++
gdb/gdbarch.sh                               |   6 +
gdb/linux-tdep.c                             |  97 +++++++-
gdb/memtag.c                                 |  88 ++++++++
gdb/memtag.h                                 |  46 ++++
gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
gdbsupport/memtag.h                          |  39 ++++
15 files changed, 867 insertions(+), 3 deletions(-)
create mode 100644 gdb/memtag.c
create mode 100644 gdb/memtag.h
create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
create mode 100644 gdbsupport/memtag.h

Note there were a few minor merge conflicts, nothing to worry about though.

I'll check again and will refresh the patch.

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index f664d964536..12fb3b390b1 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1100,6 +1100,7 @@ COMMON_SFILES = \
memattr.c \
memory-map.c \
memrange.c \
+ memtag.c \
minidebug.c \
minsyms.c \
mipsread.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index ab678acec8b..58b9f739d4f 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@

*** Changes since GDB 10

+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
* GDB now supports general memory tagging functionality if the underlying
  architecture supports the proper primitives and hooks.  Currently this is
  enabled only for AArch64 MTE.
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index e9761ed2189..04498f3b6c0 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -52,6 +52,9 @@
#include "value.h"

#include "gdbsupport/selftest.h"
+#include "gdbsupport/memtag.h"
+
+#include "elf/common.h"

/* Signal frame handling.

@@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
    }
}

+/* Memory tag note header size.  Includes both the generic and the
+   arch-specific parts.  */
+#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
+      + NT_MEMTAG_MTE_HEADER_SIZE)
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
+   gdbarch hook.  Create core file notes for memory tags.  */
+
+static std::vector<gdb::byte_vector>
+aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
+       CORE_ADDR start_address,
+       CORE_ADDR end_address)
+{
+  /* We only handle MTE tags for now.  */
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (start_address,
+   end_address - start_address,
+   AARCH64_MTE_GRANULE_SIZE);
+
+  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
+     at the moment).  */
+  std::vector<gdb::byte_vector> notes (1);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return notes;
+
+  /* Adjust the MTE note size to hold the header + tags.  */
+  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
Should this be NT_MEMTAG_TOTAL_HEADER_SIZE + (granules * AARCH64_MTE_GRANULE_SIZE)
Get_granules has already divided by the granule size.

We are storing 1 tag per byte. So the number of granules is the number of tags. If we multiply by AARCH64_MTE_GRANULE_SIZE, we will have 16 times more tags.

Ahh, right.
(Of course - this sort of buffer overrun is exactly what mte prevents :) )


+
+  CORE_ADDR address = start_address;
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+ = (granules >= MAX_TAGS_TO_TRANSFER)?
+   MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+   granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+  static_cast<int> (memtag_type::allocation)))
+ {
+   warning (_("Failed to read MTE tags from memory range [%s,%s]."),
+      phex_nz (start_address, sizeof (start_address)),
+      phex_nz (end_address, sizeof (end_address)));
+   notes.resize (0);
If you do the original resize after the while loop, then there would be no need to resize here.

I'm not sure I understand. Where do you suggest the resizing to be placed?

So something like:

std::vector<gdb::byte_vector> notes (1);

while (granules > 0)
{
...
  If (!fetch())
   return notes
...
}

notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + all_granules);

Now I write it out, you’ll need to keep a copy of the original number of granules.
Given this is only going to happen on a rare failure, then maybe its not worth the change.


Ok, happy with all those then.
Rest of the patch looks good to me too.


Alan.



^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PING][PATCH,v5][AArch64] MTE corefile support
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
  2021-06-15 14:10   ` [Ping][PATCH,v5][AArch64] " Luis Machado
  2021-06-24 14:00   ` [PATCH,v5][AArch64] " Alan Hayward
@ 2021-07-01 13:50   ` Luis Machado
  2021-07-11 14:22     ` Joel Brobecker
  2021-07-19 19:05   ` Luis Machado
  2021-07-27 16:10   ` Luis Machado
  4 siblings, 1 reply; 47+ messages in thread
From: Luis Machado @ 2021-07-01 13:50 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas



On 6/1/21 2:45 PM, Luis Machado wrote:
> Updates on v5:
> 
> - Fixed format warning output.
> 
> Updates on v4:
> 
> - Calculate sizes based on individual struct field sizes.
> 
> Updates on v3:
> 
> - Addressed review comments.
> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> - Updated code documentation.
> - Removed code duplication.
> 
> Updates on v2:
> 
> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>    over the NT_MEMTAG note.
> - Turned a few magic numbers into constants. There is an unfortunate duplication
>    of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>    no file generic enough that gets included by both corelow and linux-tdep.
> - More sanity checks to make sure the note format is correct.
> - Documented aarch64_linux_decode_memtag_note a little more.
> 
> ---
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>    uint64_t start_vma;
>    uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>    uint16_t granule_byte_size;
>    uint16_t tag_bit_size;
>    uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 
> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> 	* NEWS: Mention core file support for memory tagging.
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	Include gdbsupport/memtag.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> 	(linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* memtag.c: New file.
> 	* memtag.h: New file.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> 
> gdbsupport/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* memtag.h: New file.
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |   4 +
>   gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.h                 |  17 ++
>   gdb/corelow.c                                |  63 ++++++
>   gdb/doc/gdb.texinfo                          |   4 +
>   gdb/gdbarch.c                                |  64 ++++++
>   gdb/gdbarch.h                                |  16 ++
>   gdb/gdbarch.sh                               |   6 +
>   gdb/linux-tdep.c                             |  97 +++++++-
>   gdb/memtag.c                                 |  88 ++++++++
>   gdb/memtag.h                                 |  46 ++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
>   gdbsupport/memtag.h                          |  39 ++++
>   15 files changed, 867 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>   create mode 100644 gdbsupport/memtag.h
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index f664d964536..12fb3b390b1 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>   
>   *** Changes since GDB 10
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
>   * GDB now supports general memory tagging functionality if the underlying
>     architecture supports the proper primitives and hooks.  Currently this is
>     enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..04498f3b6c0 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -52,6 +52,9 @@
>   #include "value.h"
>   
>   #include "gdbsupport/selftest.h"
> +#include "gdbsupport/memtag.h"
> +
> +#include "elf/common.h"
>   
>   /* Signal frame handling.
>   
> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> +						  end_address - start_address,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);
> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +			  byte_order, start_address);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +			  byte_order, end_address);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (tag_dump_mte::granule_byte_size);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (tag_dump_mte::tag_bit_size);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> +    {
> +      warning (_("Malformed core note - too short for MTE header.\n"
> +		 "Expected %s bytes but got %s bytes."),
> +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> +	       pulongest (note.size ()));
> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  size_t granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  const gdb_byte *buf = note.data ();
> +
> +  unsigned int format
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  CORE_ADDR start_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  CORE_ADDR end_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> +     range of addresses.  */
> +  gdb_assert (address + length < end_vma);
> +
> +  /* Is the tag header format correct for this note?  */
> +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format.\n"
> +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> +	       format);
> +      return tags;
> +    }
> +
> +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> +
> +  /* Does the number of tag bytes in this note match the expected number
> +     of tag bytes the note says it has?  */
> +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> +    {
> +      warning (_("Unexpected tag data size.\n"
> +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  size_t skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb::array_view<const gdb_byte> tags_data
> +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> +
> +  /* Read the tag granules.  */
> +  for (size_t i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range
> +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..7da9de4aefb 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* NT_MEMTAG header for MTE tags.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> +				   + sizeof (tag_dump_mte::tag_bit_size) \
> +				   + sizeof (tag_dump_mte::__unused))
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..24c5bf29a11 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -51,6 +51,8 @@
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
>   #include "observable.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>     return true;
>   }
>   
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +
> +  return (bfd_get_section_by_name (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    error (_("gdbarch_decode_memtag_note not implemented for this "
> +	     "architecture."));
> +
> +  memtag_note_info info;
> +  info.memtag_section = nullptr;
> +
> +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> +       note.  Check if we need to fetch tags from a different section.  */
> +    if (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 90d827a50e7..1b001e6cacb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                         host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: decr_pc_after_break = %s\n",
>                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>   # Find core file memory regions
>   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>   
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
>   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>   # core file into buffer READBUF with length LEN.  Return the number of bytes read
>   # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..6192cc4421b 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -41,6 +41,8 @@
>   #include "gdbsupport/gdb_optional.h"
>   #include "gcore.h"
>   #include "gcore-elf.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   
> @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_note_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  std::vector<struct smaps_data> smaps
> +    = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (const gdb::byte_vector &note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>   	return NULL;
>       }
>   
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>     /* File mappings.  */
>     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>   
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..4d92ecde84a
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,88 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 2021 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 "memtag.h"
> +#include "gdbsupport/memtag.h"
> +#include "bfd.h"
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> +   of none are found.
> +
> +   If SECTION is provided, search from that section onwards.  */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_note_info &info)
> +{
> +  /* If SECTION is nullptr, start a fresh lookup.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> +	{
> +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> +		     "header.\n"
> +		     "Expected %s bytes but got %s bytes."),
> +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> +		   pulongest (note_size));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (abfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  It contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> +      uint64_t end_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> +			    + sizeof (tag_dump_header::start_vma));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.note = note;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..43c9efb39a3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,46 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_note_info
> +{
> +  CORE_ADDR start_address;
> +  CORE_ADDR end_address;
> +  gdb::byte_vector note;
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a .memtag section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address, memtag_note_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..d0bcd036972
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,111 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> new file mode 100644
> index 00000000000..bb47eed220b
> --- /dev/null
> +++ b/gdbsupport/memtag.h
> @@ -0,0 +1,39 @@
> +/* Generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef GDBSUPPORT_MEMTAG_H
> +#define GDBSUPPORT_MEMTAG_H
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS-specific and arch-specific code.  */
> +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> +				       + sizeof (tag_dump_header::start_vma) \
> +				       + sizeof (tag_dump_header::end_vma))
> +
> +#endif /* GDBSUPPORT_MEMTAG_H */
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-01 13:50   ` [PING][PATCH,v5][AArch64] " Luis Machado
@ 2021-07-11 14:22     ` Joel Brobecker
  2021-07-14 13:07       ` Catalin Marinas
  0 siblings, 1 reply; 47+ messages in thread
From: Joel Brobecker @ 2021-07-11 14:22 UTC (permalink / raw)
  To: Luis Machado via Gdb-patches
  Cc: simon.marchi, david.spickett, catalin.marinas, Joel Brobecker

Hi everyone,

Luis is trying to have this new feature ready for the GDB 11.1
release. As I understand it, the AArch64-specific part has already
been approved, so he's waiting for someone to review the rest of
the changes. Is there a Global Maintainer who would feel comfortable
doing so and who would have the time?

Thank you!

On Thu, Jul 01, 2021 at 10:50:44AM -0300, Luis Machado via Gdb-patches wrote:
> 
> 
> On 6/1/21 2:45 PM, Luis Machado wrote:
> > Updates on v5:
> > 
> > - Fixed format warning output.
> > 
> > Updates on v4:
> > 
> > - Calculate sizes based on individual struct field sizes.
> > 
> > Updates on v3:
> > 
> > - Addressed review comments.
> > - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> > - Updated code documentation.
> > - Removed code duplication.
> > 
> > Updates on v2:
> > 
> > - Reworked core_target::fetch_memtags to handle cases where address + len runs
> >    over the NT_MEMTAG note.
> > - Turned a few magic numbers into constants. There is an unfortunate duplication
> >    of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
> >    no file generic enough that gets included by both corelow and linux-tdep.
> > - More sanity checks to make sure the note format is correct.
> > - Documented aarch64_linux_decode_memtag_note a little more.
> > 
> > ---
> > 
> > Teach GDB how to dump memory tags when using the gcore command and how
> > to read them back from a core file generated via gcore or the kernel.
> > 
> > Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> > own NT_MEMTAG note. A section named ".memtag" is created for each of those
> > when reading the core file back.
> > 
> > Dumping memory tags
> > -
> > 
> > When using the gcore command to dump a core file, GDB will go through the maps
> > in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> > passed to an arch-specific gdbarch hook that generates a vector of blobs of
> > memory tag data that are blindly put into a NT_MEMTAG note.
> > 
> > The vector is used because we may have, in the future,  multiple tag types for
> > a particular memory range.
> > 
> > Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> > like so:
> > 
> > struct tag_dump_header
> > {
> >    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
> >    uint64_t start_vma;
> >    uint64_t end_vma;
> > };
> > 
> > struct tag_dump_mte
> > {
> >    uint16_t granule_byte_size;
> >    uint16_t tag_bit_size;
> >    uint16_t __unused;
> > };
> > 
> > The only bits meant to be generic are the tag_dump_format, start_vma and
> > end_vma fields.
> > 
> > The format-specific data is supposed to be opaque and only useful for the
> > arch-specific code.
> > 
> > We can extend the format in the future to make room for other memory tag
> > layouts.
> > 
> > Reading memory tags
> > -
> > 
> > When reading a core file that contains NT_MEMTAG entries, GDB will use
> > a different approach to check for tagged memory range. Rather than looking
> > at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> > memory range.
> > 
> > When reading tags, GDB will now use the core target's implementation of
> > fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> > is fed into an arch-specific hook that will decode the memory tag format and
> > return a vector of tags.
> > 
> > I've added a test to exercise writing and reading of memory tags in core
> > files.
> > 
> > gdb/ChangeLog:
> > 
> > YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> > 
> > 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> > 	* NEWS: Mention core file support for memory tagging.
> > 	* aarch64-linux-tdep.c: Include elf/common.h.
> > 	Include gdbsupport/memtag.h.
> > 	(MAX_TAGS_TO_TRANSFER): New constant.
> > 	(aarch64_linux_create_memtag_notes_from_range): New function.
> > 	(aarch64_linux_decode_memtag_note): Likewise.
> > 	(aarch64_linux_init_abi): Register new core file hooks.
> > 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> > 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> > 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> > 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> > 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> > 	method overrides.
> > 	* gdbarch.c: Regenerate.
> > 	* gdbarch.h: Likewise.
> > 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> > 	(decode_memtag_note): Likewise.
> > 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> > 	(linux_address_in_memtag_page): Renamed to...
> > 	(linux_process_address_in_memtag_page): ... this.
> > 	(linux_core_file_address_in_memtag_page): New function.
> > 	(linux_address_in_memtag_page): Likewise.
> > 	(linux_make_memtag_corefile_notes): Likewise.
> > 	(linux_make_corefile_notes): Handle memory tag notes.
> > 	* memtag.c: New file.
> > 	* memtag.h: New file.
> > 
> > gdb/doc/ChangeLog:
> > 
> > YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> > 
> > 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> > 	for memory tagging in core files.
> > 
> > gdb/testsuite/ChangeLog:
> > 
> > YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> > 
> > 	* gdb.arch/aarch64-mte-gcore.c: New file.
> > 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> > 
> > gdbsupport/ChangeLog:
> > 
> > YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> > 
> > 	* memtag.h: New file.
> > ---
> >   gdb/Makefile.in                              |   1 +
> >   gdb/NEWS                                     |   4 +
> >   gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
> >   gdb/arch/aarch64-mte-linux.h                 |  17 ++
> >   gdb/corelow.c                                |  63 ++++++
> >   gdb/doc/gdb.texinfo                          |   4 +
> >   gdb/gdbarch.c                                |  64 ++++++
> >   gdb/gdbarch.h                                |  16 ++
> >   gdb/gdbarch.sh                               |   6 +
> >   gdb/linux-tdep.c                             |  97 +++++++-
> >   gdb/memtag.c                                 |  88 ++++++++
> >   gdb/memtag.h                                 |  46 ++++
> >   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
> >   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
> >   gdbsupport/memtag.h                          |  39 ++++
> >   15 files changed, 867 insertions(+), 3 deletions(-)
> >   create mode 100644 gdb/memtag.c
> >   create mode 100644 gdb/memtag.h
> >   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> >   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> >   create mode 100644 gdbsupport/memtag.h
> > 
> > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> > index f664d964536..12fb3b390b1 100644
> > --- a/gdb/Makefile.in
> > +++ b/gdb/Makefile.in
> > @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
> >   	memattr.c \
> >   	memory-map.c \
> >   	memrange.c \
> > +	memtag.c \
> >   	minidebug.c \
> >   	minsyms.c \
> >   	mipsread.c \
> > diff --git a/gdb/NEWS b/gdb/NEWS
> > index ab678acec8b..58b9f739d4f 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -3,6 +3,10 @@
> >   *** Changes since GDB 10
> > +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> > +  reading memory tag data for AArch64 MTE from core files generated by
> > +  the gcore command or the Linux kernel.
> > +
> >   * GDB now supports general memory tagging functionality if the underlying
> >     architecture supports the proper primitives and hooks.  Currently this is
> >     enabled only for AArch64 MTE.
> > diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> > index e9761ed2189..04498f3b6c0 100644
> > --- a/gdb/aarch64-linux-tdep.c
> > +++ b/gdb/aarch64-linux-tdep.c
> > @@ -52,6 +52,9 @@
> >   #include "value.h"
> >   #include "gdbsupport/selftest.h"
> > +#include "gdbsupport/memtag.h"
> > +
> > +#include "elf/common.h"
> >   /* Signal frame handling.
> > @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
> >       }
> >   }
> > +/* Memory tag note header size.  Includes both the generic and the
> > +   arch-specific parts.  */
> > +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> > +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> > +
> > +/* Maximum number of tags to request.  */
> > +#define MAX_TAGS_TO_TRANSFER 1024
> > +
> > +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> > +   gdbarch hook.  Create core file notes for memory tags.  */
> > +
> > +static std::vector<gdb::byte_vector>
> > +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> > +					      CORE_ADDR start_address,
> > +					      CORE_ADDR end_address)
> > +{
> > +  /* We only handle MTE tags for now.  */
> > +
> > +  /* Figure out how many tags we need to store in this memory range.  */
> > +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> > +						  end_address - start_address,
> > +						  AARCH64_MTE_GRANULE_SIZE);
> > +
> > +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> > +     at the moment).  */
> > +  std::vector<gdb::byte_vector> notes (1);
> > +
> > +  /* If there are no tag granules to fetch, just return.  */
> > +  if (granules == 0)
> > +    return notes;
> > +
> > +  /* Adjust the MTE note size to hold the header + tags.  */
> > +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> > +
> > +  CORE_ADDR address = start_address;
> > +  /* Vector of tags.  */
> > +  gdb::byte_vector tags;
> > +
> > +  while (granules > 0)
> > +    {
> > +      /* Transfer tags in chunks.  */
> > +      gdb::byte_vector tags_read;
> > +      size_t xfer_len
> > +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> > +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> > +	  granules * AARCH64_MTE_GRANULE_SIZE;
> > +
> > +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> > +				 static_cast<int> (memtag_type::allocation)))
> > +	{
> > +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> > +		     phex_nz (start_address, sizeof (start_address)),
> > +		     phex_nz (end_address, sizeof (end_address)));
> > +	  notes.resize (0);
> > +	  return notes;
> > +	}
> > +
> > +      /* Transfer over the tags that have been read.  */
> > +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> > +
> > +      /* Adjust the remaining granules and starting address.  */
> > +      granules -= tags_read.size ();
> > +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> > +    }
> > +
> > +  /* Create the header.  */
> > +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> > +  gdb_byte *buf = notes[0].data ();
> > +
> > +  /* Generic header.  */
> > +  /* Tag dump format */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> > +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> > +  buf += sizeof (tag_dump_header::format);
> > +
> > +  /* Start address */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> > +			  byte_order, start_address);
> > +  buf += sizeof (tag_dump_header::start_vma);
> > +
> > +  /* End address */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> > +			  byte_order, end_address);
> > +  buf += sizeof (tag_dump_header::end_vma);
> > +
> > +  /* MTE-specific header.  */
> > +  /* Granule byte size */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> > +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> > +  buf += sizeof (tag_dump_mte::granule_byte_size);
> > +
> > +  /* Tag bit size */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> > +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> > +  buf += sizeof (tag_dump_mte::tag_bit_size);
> > +
> > +  /* Unused value */
> > +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> > +
> > +  /* Store the tags.  */
> > +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> > +	  tags.size ());
> > +
> > +  return notes;
> > +}
> > +
> > +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> > +   hook.  Decode a memory tag note and return the requested tags.
> > +
> > +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> > +   range.  */
> > +
> > +static gdb::byte_vector
> > +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> > +				  gdb::array_view <const gdb_byte> note,
> > +				  CORE_ADDR address, size_t length)
> > +{
> > +  gdb::byte_vector tags;
> > +
> > +  /* Sanity check.  */
> > +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> > +    {
> > +      warning (_("Malformed core note - too short for MTE header.\n"
> > +		 "Expected %s bytes but got %s bytes."),
> > +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> > +	       pulongest (note.size ()));
> > +      return tags;
> > +    }
> > +
> > +  /* The amount of memory tag granules we need to fetch.  */
> > +  size_t granules
> > +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> > +
> > +  /* If there are no tag granules to decode, just return.  */
> > +  if (granules == 0)
> > +    return tags;
> > +
> > +  /* Read the generic header.  */
> > +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> > +  const gdb_byte *buf = note.data ();
> > +
> > +  unsigned int format
> > +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> > +				byte_order);
> > +  buf += sizeof (tag_dump_header::format);
> > +
> > +  CORE_ADDR start_vma
> > +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> > +				byte_order);
> > +  buf += sizeof (tag_dump_header::start_vma);
> > +
> > +  CORE_ADDR end_vma
> > +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> > +				byte_order);
> > +  buf += sizeof (tag_dump_header::end_vma);
> > +
> > +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> > +     range of addresses.  */
> > +  gdb_assert (address + length < end_vma);
> > +
> > +  /* Is the tag header format correct for this note?  */
> > +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> > +    {
> > +      warning (_("Unexpected memory tag note format.\n"
> > +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> > +	       format);
> > +      return tags;
> > +    }
> > +
> > +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> > +
> > +  /* Does the number of tag bytes in this note match the expected number
> > +     of tag bytes the note says it has?  */
> > +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> > +    {
> > +      warning (_("Unexpected tag data size.\n"
> > +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> > +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> > +      return tags;
> > +    }
> > +
> > +  /* Calculate how many granules we need to skip to get to the granule of
> > +     ADDRESS.  Align both the start address and the requested address
> > +     so it is easier to get the number of granules to skip.  This way we
> > +     don't need to consider cases where ADDRESS falls in the middle of a
> > +     tag granule range.  */
> > +  CORE_ADDR aligned_start_address
> > +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> > +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> > +
> > +  size_t skipped_granules
> > +    = aarch64_mte_get_tag_granules (aligned_start_address,
> > +				    aligned_address - aligned_start_address,
> > +				    AARCH64_MTE_GRANULE_SIZE);
> > +
> > +  /* Point to the block of data that contains the first granule we are
> > +     interested in.  */
> > +  const gdb::array_view<const gdb_byte> tags_data
> > +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> > +
> > +  /* Read the tag granules.  */
> > +  for (size_t i = 0; i < granules; i++)
> > +      tags.push_back (tags_data[i]);
> > +
> > +  return tags;
> > +}
> > +
> >   static void
> >   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> >   {
> > @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
> >         set_gdbarch_report_signal_info (gdbarch,
> >   				      aarch64_linux_report_signal_info);
> > +
> > +      /* Core file helpers.  */
> > +
> > +      /* Core file helper to create memory tag notes for a particular range of
> > +	 addresses.  */
> > +      set_gdbarch_create_memtag_notes_from_range
> > +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> > +
> > +      /* Core file helper to decode a memory tag note.  */
> > +      set_gdbarch_decode_memtag_note (gdbarch,
> > +				      aarch64_linux_decode_memtag_note);
> >       }
> >     /* Initialize the aarch64_linux_record_tdep.  */
> > diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> > index 2aa97eb861a..7da9de4aefb 100644
> > --- a/gdb/arch/aarch64-mte-linux.h
> > +++ b/gdb/arch/aarch64-mte-linux.h
> > @@ -32,6 +32,7 @@
> >   /* We have one tag per 16 bytes of memory.  */
> >   #define AARCH64_MTE_GRANULE_SIZE 16
> > +#define AARCH64_MTE_TAG_BIT_SIZE 4
> >   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
> >   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
> > @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
> >      It is always possible to get the logical tag.  */
> >   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
> > +/* NT_MEMTAG header for MTE tags.  */
> > +struct tag_dump_mte
> > +{
> > +  /* Size of the tag granule in bytes.  */
> > +  uint16_t granule_byte_size;
> > +  /* Size of the tag in bits.  */
> > +  uint16_t tag_bit_size;
> > +  /* Reserved field for the future.  */
> > +  uint16_t __unused;
> > +};
> > +
> > +/* Size of the MTE header for a NT_MEMTAG note.  */
> > +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> > +				   + sizeof (tag_dump_mte::tag_bit_size) \
> > +				   + sizeof (tag_dump_mte::__unused))
> > +
> >   #endif /* ARCH_AARCH64_LINUX_H */
> > diff --git a/gdb/corelow.c b/gdb/corelow.c
> > index 452b4dd4f9a..24c5bf29a11 100644
> > --- a/gdb/corelow.c
> > +++ b/gdb/corelow.c
> > @@ -51,6 +51,8 @@
> >   #include "gdbcmd.h"
> >   #include "xml-tdesc.h"
> >   #include "observable.h"
> > +#include "gdbsupport/memtag.h"
> > +#include "memtag.h"
> >   #ifndef O_LARGEFILE
> >   #define O_LARGEFILE 0
> > @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
> >     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;
> > +
> >     /* A few helpers.  */
> >     /* Getter, see variable definition.  */
> > @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
> >     return true;
> >   }
> > +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> > +
> > +bool
> > +core_target::supports_memory_tagging ()
> > +{
> > +  /* Look for memory tag notes.  If they exist, that means this core file
> > +     supports memory tagging.  */
> > +
> > +  return (bfd_get_section_by_name (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)
> > +{
> > +  struct gdbarch *gdbarch = target_gdbarch ();
> > +
> > +  /* Make sure we have a way to decode the memory tag notes.  */
> > +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> > +    error (_("gdbarch_decode_memtag_note not implemented for this "
> > +	     "architecture."));
> > +
> > +  memtag_note_info info;
> > +  info.memtag_section = nullptr;
> > +
> > +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> > +       note.  Check if we need to fetch tags from a different section.  */
> > +    if (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;
> > +}
> > +
> >   /* Get a pointer to the current core target.  If not connected to a
> >      core target, return NULL.  */
> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> > index 90d827a50e7..1b001e6cacb 100644
> > --- a/gdb/doc/gdb.texinfo
> > +++ b/gdb/doc/gdb.texinfo
> > @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
> >   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
> >   documentation in the Linux kernel.
> > +@value{GDBN} supports dumping memory tag data to core files through the
> > +@command{gcore} command and reading memory tag data from core files generated
> > +by the @command{gcore} command or the Linux kernel.
> > +
> >   @node i386
> >   @subsection x86 Architecture-specific Issues
> > diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> > index 208cf4b5aaa..de384da2e9a 100644
> > --- a/gdb/gdbarch.c
> > +++ b/gdb/gdbarch.c
> > @@ -283,6 +283,8 @@ struct gdbarch
> >     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
> >     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
> >     gdbarch_find_memory_regions_ftype *find_memory_regions;
> > +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> > +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
> >     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
> >     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
> >     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> > @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
> >     /* Skip verify of iterate_over_regset_sections, has predicate.  */
> >     /* Skip verify of make_corefile_notes, has predicate.  */
> >     /* Skip verify of find_memory_regions, has predicate.  */
> > +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> > +  /* Skip verify of decode_memtag_note, has predicate.  */
> >     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
> >     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
> >     /* Skip verify of core_pid_to_str, has predicate.  */
> > @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
> >     fprintf_unfiltered (file,
> >                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
> >                         host_address_to_string (gdbarch->core_xfer_siginfo));
> > +  fprintf_unfiltered (file,
> > +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> > +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> > +  fprintf_unfiltered (file,
> > +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> > +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> > +  fprintf_unfiltered (file,
> > +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> > +                      gdbarch_decode_memtag_note_p (gdbarch));
> > +  fprintf_unfiltered (file,
> > +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> > +                      host_address_to_string (gdbarch->decode_memtag_note));
> >     fprintf_unfiltered (file,
> >                         "gdbarch_dump: decr_pc_after_break = %s\n",
> >                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> > @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
> >     gdbarch->find_memory_regions = find_memory_regions;
> >   }
> > +bool
> > +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  return gdbarch->create_memtag_notes_from_range != NULL;
> > +}
> > +
> > +std::vector<gdb::byte_vector>
> > +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> > +  if (gdbarch_debug >= 2)
> > +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> > +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> > +}
> > +
> > +void
> > +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> > +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> > +{
> > +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> > +}
> > +
> > +bool
> > +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  return gdbarch->decode_memtag_note != NULL;
> > +}
> > +
> > +gdb::byte_vector
> > +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> > +  if (gdbarch_debug >= 2)
> > +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> > +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> > +}
> > +
> > +void
> > +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> > +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> > +{
> > +  gdbarch->decode_memtag_note = decode_memtag_note;
> > +}
> > +
> >   bool
> >   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
> >   {
> > diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> > index 7157e5596fd..80e244624de 100644
> > --- a/gdb/gdbarch.h
> > +++ b/gdb/gdbarch.h
> > @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
> >   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
> >   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
> > +/* Create memory tag core file notes given a range of addresses. */
> > +
> > +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> > +
> > +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> > +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> > +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> > +
> > +/* Decode a memory tag note and return the tags that it contains. */
> > +
> > +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> > +
> > +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> > +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> > +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> > +
> >   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> >      core file into buffer READBUF with length LEN.  Return the number of bytes read
> >      (zero indicates failure).
> > diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> > index 43e51341f97..7c5eed0780c 100755
> > --- a/gdb/gdbarch.sh
> > +++ b/gdb/gdbarch.sh
> > @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
> >   # Find core file memory regions
> >   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
> > +# Create memory tag core file notes given a range of addresses.
> > +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> > +
> > +# Decode a memory tag note and return the tags that it contains.
> > +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> > +
> >   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> >   # core file into buffer READBUF with length LEN.  Return the number of bytes read
> >   # (zero indicates failure).
> > diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> > index 927e69bf1e1..6192cc4421b 100644
> > --- a/gdb/linux-tdep.c
> > +++ b/gdb/linux-tdep.c
> > @@ -41,6 +41,8 @@
> >   #include "gdbsupport/gdb_optional.h"
> >   #include "gcore.h"
> >   #include "gcore-elf.h"
> > +#include "gdbsupport/memtag.h"
> > +#include "memtag.h"
> >   #include <ctype.h>
> > @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
> >     return smaps;
> >   }
> > -/* See linux-tdep.h.  */
> > +/* Helper that checks if an address is in a memory tag page for a live
> > +   process.  */
> > -bool
> > -linux_address_in_memtag_page (CORE_ADDR address)
> > +static bool
> > +linux_process_address_in_memtag_page (CORE_ADDR address)
> >   {
> >     if (current_inferior ()->fake_pid_p)
> >       return false;
> > @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
> >     return false;
> >   }
> > +/* Helper that checks if an address is in a memory tag page for a core file
> > +   process.  */
> > +
> > +static bool
> > +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> > +{
> > +  if (core_bfd == nullptr)
> > +    return false;
> > +
> > +  memtag_note_info info;
> > +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> > +}
> > +
> > +/* See linux-tdep.h.  */
> > +
> > +bool
> > +linux_address_in_memtag_page (CORE_ADDR address)
> > +{
> > +  if (!target_has_execution ())
> > +    return linux_core_file_address_in_memtag_page (address);
> > +
> > +  return linux_process_address_in_memtag_page (address);
> > +}
> > +
> > +/* For each memory map entry that has memory tagging enabled, create a new
> > +   core file note that contains all of its memory tags.  Save the data to
> > +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> > +
> > +static void
> > +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> > +				  gdb::unique_xmalloc_ptr<char> &note_data,
> > +				  int *note_size)
> > +{
> > +  if (current_inferior ()->fake_pid_p)
> > +    return;
> > +
> > +  /* If the architecture doesn't have a hook to return memory tag notes,
> > +     there is nothing left to do.  */
> > +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> > +    return;
> > +
> > +  pid_t pid = current_inferior ()->pid;
> > +
> > +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> > +
> > +  gdb::unique_xmalloc_ptr<char> data
> > +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> > +
> > +  if (data == nullptr)
> > +    return;
> > +
> > +  /* Parse the contents of smaps into a vector.  */
> > +  std::vector<struct smaps_data> smaps
> > +    = parse_smaps_data (data.get (), smaps_file);
> > +
> > +  for (const smaps_data &map : smaps)
> > +    {
> > +      /* Does this mapping have memory tagging enabled? If so, save the
> > +	 memory tags to the core file note.  */
> > +      if (map.vmflags.memory_tagging == 0)
> > +	continue;
> > +
> > +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> > +	 this particular memory range, including the header.
> > +
> > +	 If the notes are too big, we may need to break up the transfer
> > +	 into smaller chunks.
> > +
> > +	 If the architecture returns an empty vector, that means there are
> > +	 no memory tag notes to write.  */
> > +      std::vector<gdb::byte_vector> memory_tag_notes
> > +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> > +						  map.start_address,
> > +						  map.end_address);
> > +      /* Write notes to the core file.  */
> > +      for (const gdb::byte_vector &note : memory_tag_notes)
> > +	{
> > +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> > +					       note_size, "CORE",
> > +					       NT_MEMTAG, note.data (),
> > +					       note.size ()));
> > +	}
> > +    }
> > +}
> > +
> >   /* List memory regions in the inferior for a corefile.  */
> >   static int
> > @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
> >   	return NULL;
> >       }
> > +  /* Dump the memory tags, if any.  */
> > +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> > +
> >     /* File mappings.  */
> >     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
> > diff --git a/gdb/memtag.c b/gdb/memtag.c
> > new file mode 100644
> > index 00000000000..4d92ecde84a
> > --- /dev/null
> > +++ b/gdb/memtag.c
> > @@ -0,0 +1,88 @@
> > +/* GDB generic memory tagging functions.
> > +
> > +   Copyright (C) 2021 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 "memtag.h"
> > +#include "gdbsupport/memtag.h"
> > +#include "bfd.h"
> > +
> > +/* Helper function to walk through NT_MEMTAG notes in a core file.
> > +
> > +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> > +   of none are found.
> > +
> > +   If SECTION is provided, search from that section onwards.  */
> > +
> > +bool
> > +get_next_core_memtag_section (bfd *abfd, asection *section,
> > +			      CORE_ADDR address, memtag_note_info &info)
> > +{
> > +  /* If SECTION is nullptr, start a fresh lookup.  */
> > +  if (section == nullptr)
> > +    section = bfd_get_section_by_name (abfd, ".memtag");
> > +
> > +  /* Go through all the memtag sections and figure out if ADDRESS
> > +     falls within one of the memory ranges that contain tags.  */
> > +  while (section != nullptr)
> > +    {
> > +      size_t note_size = bfd_section_size (section);
> > +
> > +      /* If the note is smaller than the size of the header, this core note
> > +	 is malformed.  */
> > +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> > +	{
> > +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> > +		     "header.\n"
> > +		     "Expected %s bytes but got %s bytes."),
> > +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> > +		   pulongest (note_size));
> > +	  return false;
> > +	}
> > +
> > +      gdb::byte_vector note (note_size);
> > +
> > +      /* Fetch the contents of this particular memtag note.  */
> > +      if (!bfd_get_section_contents (abfd, section,
> > +				     note.data (), 0, note_size))
> > +	{
> > +	  warning (_("could not get core note contents."));
> > +	  return false;
> > +	}
> > +
> > +      /* Read the generic header of the note.  It contains the format,
> > +	 start address and end address.  */
> > +      uint64_t start_address
> > +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> > +      uint64_t end_address
> > +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> > +			    + sizeof (tag_dump_header::start_vma));
> > +
> > +      /* Is the address within [start_address, end_address)?  */
> > +      if (address >= start_address
> > +	  && address < end_address)
> > +	{
> > +	  info.start_address = start_address;
> > +	  info.end_address = end_address;
> > +	  info.note = note;
> > +	  info.memtag_section = section;
> > +	  return true;
> > +	}
> > +    }
> > +  return false;
> > +}
> > diff --git a/gdb/memtag.h b/gdb/memtag.h
> > new file mode 100644
> > index 00000000000..43c9efb39a3
> > --- /dev/null
> > +++ b/gdb/memtag.h
> > @@ -0,0 +1,46 @@
> > +/* GDB generic memory tagging definitions.
> > +   Copyright (C) 2021 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/>.  */
> > +
> > +#ifndef MEMTAG_H
> > +#define MEMTAG_H
> > +
> > +#include "bfd.h"
> > +
> > +struct memtag_note_info
> > +{
> > +  CORE_ADDR start_address;
> > +  CORE_ADDR end_address;
> > +  gdb::byte_vector note;
> > +  asection *memtag_section;
> > +};
> > +
> > +/* Helper function to walk through NT_MEMTAG notes in a core file.
> > +
> > +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> > +   otherwise.
> > +
> > +   If SECTION is provided, search from that section onwards. If SECTION is
> > +   nullptr, then start a new search.
> > +
> > +   If a .memtag section containing ADDRESS is found, fill INFO with data
> > +   about such section.  Otherwise leave it unchanged.  */
> > +
> > +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> > +				   CORE_ADDR address, memtag_note_info &info);
> > +
> > +#endif /* MEMTAG_H */
> > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > new file mode 100644
> > index 00000000000..b20ebcff424
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > @@ -0,0 +1,93 @@
> > +/* This test program is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2021 Free Software Foundation, Inc.
> > +
> > +   This program is free software; you can redistribute it and/or modify
> > +   it under the terms of the GNU General Public License as published by
> > +   the Free Software Foundation; either version 3 of the License, or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public License
> > +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> > +
> > +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> > +
> > +/* This test was based on the documentation for the AArch64 Memory Tagging
> > +   Extension from the Linux Kernel, found in the sources in
> > +   Documentation/arm64/memory-tagging-extension.rst.  */
> > +
> > +#include <errno.h>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <unistd.h>
> > +#include <sys/auxv.h>
> > +#include <sys/mman.h>
> > +#include <sys/prctl.h>
> > +
> > +/* From arch/arm64/include/uapi/asm/hwcap.h */
> > +#define HWCAP2_MTE              (1 << 18)
> > +
> > +/* From arch/arm64/include/uapi/asm/mman.h */
> > +#define PROT_MTE  0x20
> > +
> > +/* From include/uapi/linux/prctl.h */
> > +#define PR_SET_TAGGED_ADDR_CTRL 55
> > +#define PR_GET_TAGGED_ADDR_CTRL 56
> > +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> > +#define PR_MTE_TCF_SHIFT	1
> > +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> > +#define PR_MTE_TAG_SHIFT	3
> > +
> > +void
> > +access_memory (unsigned char *tagged_ptr)
> > +{
> > +  tagged_ptr[0] = 'a';
> > +}
> > +
> > +int
> > +main (int argc, char **argv)
> > +{
> > +  unsigned char *tagged_ptr;
> > +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> > +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> > +
> > +  /* Bail out if MTE is not supported.  */
> > +  if (!(hwcap2 & HWCAP2_MTE))
> > +    return 1;
> > +
> > +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> > +     allow all non-zero tags in the randomly generated set.  */
> > +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> > +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> > +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> > +	     0, 0, 0))
> > +    {
> > +      perror ("prctl () failed");
> > +      return 1;
> > +    }
> > +
> > +  /* Create a mapping that will have PROT_MTE set.  */
> > +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> > +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> > +  if (tagged_ptr == MAP_FAILED)
> > +    {
> > +      perror ("mmap () failed");
> > +      return 1;
> > +    }
> > +
> > +  /* Enable MTE on the above anonymous mmap.  */
> > +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> > +    {
> > +      perror ("mprotect () failed");
> > +      return 1;
> > +    }
> > +
> > +  access_memory (tagged_ptr);
> > +
> > +  return 0;
> > +}
> > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > new file mode 100644
> > index 00000000000..d0bcd036972
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > @@ -0,0 +1,111 @@
> > +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> > +#
> > +# This program is free software; you can redistribute it and/or modify
> > +# it under the terms of the GNU General Public License as published by
> > +# the Free Software Foundation; either version 3 of the License, or
> > +# (at your option) any later version.
> > +#
> > +# This program is distributed in the hope that it will be useful,
> > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +# GNU General Public License for more details.
> > +#
> > +# You should have received a copy of the GNU General Public License
> > +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +
> > +# This file is part of the gdb testsuite.
> > +
> > +# Test generating and reading a core file with MTE memory tags.
> > +
> > +if {![is_aarch64_target]} {
> > +    verbose "Skipping ${gdb_test_file_name}."
> > +    return
> > +}
> > +
> > +standard_testfile
> > +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> > +    return -1
> > +}
> > +
> > +if ![runto_main] {
> > +    untested "could not run to main"
> > +    return -1
> > +}
> > +
> > +# Targets that don't support memory tagging should not execute the
> > +# runtime memory tagging tests.
> > +if {![supports_memtag]} {
> > +    unsupported "memory tagging unsupported"
> > +    return -1
> > +}
> > +
> > +gdb_breakpoint "access_memory"
> > +
> > +if [gdb_continue "access_memory"] {
> > +    return -1
> > +}
> > +
> > +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> > +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> > +for {set i 15} {$i >= 0} {incr i -1} {
> > +    set index [expr [expr 15 - $i] * 16]
> > +    set tag [format "%02x" $i]
> > +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> > +	     $atag_msg \
> > +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> > +}
> > +
> > +# Run until a crash and confirm GDB displays memory tag violation
> > +# information.
> > +gdb_test "continue" \
> > +    [multi_line \
> > +	"Program received signal SIGSEGV, Segmentation fault" \
> > +	"Memory tag violation while accessing address $hex" \
> > +	"Allocation tag $hex" \
> > +	"Logical tag $hex\." \
> > +	"$hex in access_memory \\(.*\\) at .*" \
> > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > +	 "display tag violation information for live process"
> > +
> > +# Generate the core file.
> > +set core_filename [standard_output_file "$testfile.core"]
> > +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> > +
> > +if { !$core_generated } {
> > +    return -1
> > +}
> > +
> > +clean_restart $binfile
> > +
> > +if { $program_loaded } {
> > +    return -1
> > +}
> > +
> > +# Load the core file and make sure we see the tag violation fault
> > +# information.
> > +gdb_test "core $core_filename" \
> > +    [multi_line \
> > +	"Core was generated by.*\." \
> > +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> > +	"Memory tag violation while accessing address $hex" \
> > +	"Allocation tag 0xf" \
> > +	"Logical tag 0x0\." \
> > +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > +	 "core file shows tag violation information"
> > +
> > +# Make sure we have the tag_ctl register.
> > +gdb_test "info register tag_ctl" \
> > +	 "tag_ctl.*$hex.*${::decimal}" \
> > +	 "tag_ctl is available"
> > +
> > +# Check if the tag granules have the expected values.  If they do, that
> > +# means the core file saved the tags properly and GDB has read them
> > +# correctly.
> > +for {set i 15} {$i >= 0} {incr i -1} {
> > +    set index [expr [expr 15 - $i] * 16]
> > +    set tag [format "%x" $i]
> > +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> > +	     "= 0x$tag" \
> > +	     "memory tag of &tagged_ptr\[$index\] is correct"
> > +}
> > diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> > new file mode 100644
> > index 00000000000..bb47eed220b
> > --- /dev/null
> > +++ b/gdbsupport/memtag.h
> > @@ -0,0 +1,39 @@
> > +/* Generic memory tagging definitions.
> > +   Copyright (C) 2021 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/>.  */
> > +
> > +#ifndef GDBSUPPORT_MEMTAG_H
> > +#define GDBSUPPORT_MEMTAG_H
> > +
> > +/* Generic NT_MEMTAG header.  */
> > +struct tag_dump_header
> > +{
> > +  /* Tag format.  */
> > +  uint16_t format;
> > +  /* Start address of the tagged range.  */
> > +  uint64_t start_vma;
> > +  /* End address of the tagged range.  */
> > +  uint64_t end_vma;
> > +};
> > +
> > +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> > +   and should be shared with OS-specific and arch-specific code.  */
> > +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> > +				       + sizeof (tag_dump_header::start_vma) \
> > +				       + sizeof (tag_dump_header::end_vma))
> > +
> > +#endif /* GDBSUPPORT_MEMTAG_H */
> > 

-- 
Joel

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-11 14:22     ` Joel Brobecker
@ 2021-07-14 13:07       ` Catalin Marinas
  2021-07-29  2:26         ` Simon Marchi
  0 siblings, 1 reply; 47+ messages in thread
From: Catalin Marinas @ 2021-07-14 13:07 UTC (permalink / raw)
  To: Joel Brobecker; +Cc: Luis Machado via Gdb-patches, simon.marchi, david.spickett

On Sun, Jul 11, 2021 at 07:22:00AM -0700, Joel Brobecker wrote:
> Luis is trying to have this new feature ready for the GDB 11.1
> release. As I understand it, the AArch64-specific part has already
> been approved, so he's waiting for someone to review the rest of
> the changes. Is there a Global Maintainer who would feel comfortable
> doing so and who would have the time?

I'm a Linux kernel maintainer, so not much for me to review on the gdb
front. But feedback from the gdb maintainers would be appreciated.

W.r.t. upstreaming the feature in gdb, I'd wait until the Linux
counterpart is merged. We agreed in principle on the ABI but I wouldn't
consider it stable until the feature hits a kernel release.

-- 
Catalin

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PING][PATCH,v5][AArch64] MTE corefile support
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
                     ` (2 preceding siblings ...)
  2021-07-01 13:50   ` [PING][PATCH,v5][AArch64] " Luis Machado
@ 2021-07-19 19:05   ` Luis Machado
  2021-07-27 16:10   ` Luis Machado
  4 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-07-19 19:05 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas



On 6/1/21 2:45 PM, Luis Machado wrote:
> Updates on v5:
> 
> - Fixed format warning output.
> 
> Updates on v4:
> 
> - Calculate sizes based on individual struct field sizes.
> 
> Updates on v3:
> 
> - Addressed review comments.
> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> - Updated code documentation.
> - Removed code duplication.
> 
> Updates on v2:
> 
> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>    over the NT_MEMTAG note.
> - Turned a few magic numbers into constants. There is an unfortunate duplication
>    of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>    no file generic enough that gets included by both corelow and linux-tdep.
> - More sanity checks to make sure the note format is correct.
> - Documented aarch64_linux_decode_memtag_note a little more.
> 
> ---
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>    uint64_t start_vma;
>    uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>    uint16_t granule_byte_size;
>    uint16_t tag_bit_size;
>    uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 
> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> 	* NEWS: Mention core file support for memory tagging.
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	Include gdbsupport/memtag.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> 	(linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* memtag.c: New file.
> 	* memtag.h: New file.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> 
> gdbsupport/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* memtag.h: New file.
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |   4 +
>   gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.h                 |  17 ++
>   gdb/corelow.c                                |  63 ++++++
>   gdb/doc/gdb.texinfo                          |   4 +
>   gdb/gdbarch.c                                |  64 ++++++
>   gdb/gdbarch.h                                |  16 ++
>   gdb/gdbarch.sh                               |   6 +
>   gdb/linux-tdep.c                             |  97 +++++++-
>   gdb/memtag.c                                 |  88 ++++++++
>   gdb/memtag.h                                 |  46 ++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
>   gdbsupport/memtag.h                          |  39 ++++
>   15 files changed, 867 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>   create mode 100644 gdbsupport/memtag.h
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index f664d964536..12fb3b390b1 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>   
>   *** Changes since GDB 10
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
>   * GDB now supports general memory tagging functionality if the underlying
>     architecture supports the proper primitives and hooks.  Currently this is
>     enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..04498f3b6c0 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -52,6 +52,9 @@
>   #include "value.h"
>   
>   #include "gdbsupport/selftest.h"
> +#include "gdbsupport/memtag.h"
> +
> +#include "elf/common.h"
>   
>   /* Signal frame handling.
>   
> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> +						  end_address - start_address,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);
> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +			  byte_order, start_address);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +			  byte_order, end_address);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (tag_dump_mte::granule_byte_size);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (tag_dump_mte::tag_bit_size);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> +    {
> +      warning (_("Malformed core note - too short for MTE header.\n"
> +		 "Expected %s bytes but got %s bytes."),
> +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> +	       pulongest (note.size ()));
> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  size_t granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  const gdb_byte *buf = note.data ();
> +
> +  unsigned int format
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  CORE_ADDR start_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  CORE_ADDR end_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> +     range of addresses.  */
> +  gdb_assert (address + length < end_vma);
> +
> +  /* Is the tag header format correct for this note?  */
> +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format.\n"
> +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> +	       format);
> +      return tags;
> +    }
> +
> +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> +
> +  /* Does the number of tag bytes in this note match the expected number
> +     of tag bytes the note says it has?  */
> +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> +    {
> +      warning (_("Unexpected tag data size.\n"
> +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  size_t skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb::array_view<const gdb_byte> tags_data
> +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> +
> +  /* Read the tag granules.  */
> +  for (size_t i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range
> +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..7da9de4aefb 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* NT_MEMTAG header for MTE tags.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> +				   + sizeof (tag_dump_mte::tag_bit_size) \
> +				   + sizeof (tag_dump_mte::__unused))
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..24c5bf29a11 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -51,6 +51,8 @@
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
>   #include "observable.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>     return true;
>   }
>   
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +
> +  return (bfd_get_section_by_name (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    error (_("gdbarch_decode_memtag_note not implemented for this "
> +	     "architecture."));
> +
> +  memtag_note_info info;
> +  info.memtag_section = nullptr;
> +
> +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> +       note.  Check if we need to fetch tags from a different section.  */
> +    if (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 90d827a50e7..1b001e6cacb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                         host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: decr_pc_after_break = %s\n",
>                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>   # Find core file memory regions
>   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>   
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
>   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>   # core file into buffer READBUF with length LEN.  Return the number of bytes read
>   # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..6192cc4421b 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -41,6 +41,8 @@
>   #include "gdbsupport/gdb_optional.h"
>   #include "gcore.h"
>   #include "gcore-elf.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   
> @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_note_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  std::vector<struct smaps_data> smaps
> +    = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (const gdb::byte_vector &note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>   	return NULL;
>       }
>   
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>     /* File mappings.  */
>     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>   
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..4d92ecde84a
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,88 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 2021 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 "memtag.h"
> +#include "gdbsupport/memtag.h"
> +#include "bfd.h"
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> +   of none are found.
> +
> +   If SECTION is provided, search from that section onwards.  */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_note_info &info)
> +{
> +  /* If SECTION is nullptr, start a fresh lookup.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> +	{
> +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> +		     "header.\n"
> +		     "Expected %s bytes but got %s bytes."),
> +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> +		   pulongest (note_size));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (abfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  It contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> +      uint64_t end_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> +			    + sizeof (tag_dump_header::start_vma));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.note = note;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..43c9efb39a3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,46 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_note_info
> +{
> +  CORE_ADDR start_address;
> +  CORE_ADDR end_address;
> +  gdb::byte_vector note;
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a .memtag section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address, memtag_note_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..d0bcd036972
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,111 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> new file mode 100644
> index 00000000000..bb47eed220b
> --- /dev/null
> +++ b/gdbsupport/memtag.h
> @@ -0,0 +1,39 @@
> +/* Generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef GDBSUPPORT_MEMTAG_H
> +#define GDBSUPPORT_MEMTAG_H
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS-specific and arch-specific code.  */
> +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> +				       + sizeof (tag_dump_header::start_vma) \
> +				       + sizeof (tag_dump_header::end_vma))
> +
> +#endif /* GDBSUPPORT_MEMTAG_H */
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PING][PATCH,v5][AArch64] MTE corefile support
  2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
                     ` (3 preceding siblings ...)
  2021-07-19 19:05   ` Luis Machado
@ 2021-07-27 16:10   ` Luis Machado
  4 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-07-27 16:10 UTC (permalink / raw)
  To: gdb-patches, simon.marchi
  Cc: alan.hayward, jhb, david.spickett, catalin.marinas



On 6/1/21 2:45 PM, Luis Machado wrote:
> Updates on v5:
> 
> - Fixed format warning output.
> 
> Updates on v4:
> 
> - Calculate sizes based on individual struct field sizes.
> 
> Updates on v3:
> 
> - Addressed review comments.
> - New files gdbsupport/memtag.h, gdb/memtag.h and gdb/memtag.c.
> - Updated code documentation.
> - Removed code duplication.
> 
> Updates on v2:
> 
> - Reworked core_target::fetch_memtags to handle cases where address + len runs
>    over the NT_MEMTAG note.
> - Turned a few magic numbers into constants. There is an unfortunate duplication
>    of a constant (NT_MEMTAG_HEADER_SIZE). It is a generic constant, but there is
>    no file generic enough that gets included by both corelow and linux-tdep.
> - More sanity checks to make sure the note format is correct.
> - Documented aarch64_linux_decode_memtag_note a little more.
> 
> ---
> 
> Teach GDB how to dump memory tags when using the gcore command and how
> to read them back from a core file generated via gcore or the kernel.
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own NT_MEMTAG note. A section named ".memtag" is created for each of those
> when reading the core file back.
> 
> Dumping memory tags
> -
> 
> When using the gcore command to dump a core file, GDB will go through the maps
> in /proc/<pid>/smaps looking for tagged ranges. Each of those entries gets
> passed to an arch-specific gdbarch hook that generates a vector of blobs of
> memory tag data that are blindly put into a NT_MEMTAG note.
> 
> The vector is used because we may have, in the future,  multiple tag types for
> a particular memory range.
> 
> Each of the NT_MEMTAG notes have a generic header and a arch-specific header,
> like so:
> 
> struct tag_dump_header
> {
>    uint16_t format; // Only NT_MEMTAG_TYPE_AARCH_MTE at present
>    uint64_t start_vma;
>    uint64_t end_vma;
> };
> 
> struct tag_dump_mte
> {
>    uint16_t granule_byte_size;
>    uint16_t tag_bit_size;
>    uint16_t __unused;
> };
> 
> The only bits meant to be generic are the tag_dump_format, start_vma and
> end_vma fields.
> 
> The format-specific data is supposed to be opaque and only useful for the
> arch-specific code.
> 
> We can extend the format in the future to make room for other memory tag
> layouts.
> 
> Reading memory tags
> -
> 
> When reading a core file that contains NT_MEMTAG entries, GDB will use
> a different approach to check for tagged memory range. Rather than looking
> at /proc/<pid>/smaps, it will now look for ".memtag" sections with the right
> memory range.
> 
> When reading tags, GDB will now use the core target's implementation of
> fetch_memtags (store_memtags doesn't exist for core targets). Then the data
> is fed into an arch-specific hook that will decode the memory tag format and
> return a vector of tags.
> 
> I've added a test to exercise writing and reading of memory tags in core
> files.
> 
> gdb/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* Makefile.in (COMMON_SFILES): Add memtag.c.
> 	* NEWS: Mention core file support for memory tagging.
> 	* aarch64-linux-tdep.c: Include elf/common.h.
> 	Include gdbsupport/memtag.h.
> 	(MAX_TAGS_TO_TRANSFER): New constant.
> 	(aarch64_linux_create_memtag_notes_from_range): New function.
> 	(aarch64_linux_decode_memtag_note): Likewise.
> 	(aarch64_linux_init_abi): Register new core file hooks.
> 	(NT_MEMTAG_TOTAL_HEADER_SIZE): New constant.
> 	* arch/aarch64-mte-linux.h (tag_dump_mte): New struct.
> 	(AARCH64_MTE_TAG_BIT_SIZE): New constant.
> 	* corelow.c: Include gdbsupport/memtag.h and memtag.h.
> 	(core_target) <supports_memory_tagging, fetch_memtags>: New
> 	method overrides.
> 	* gdbarch.c: Regenerate.
> 	* gdbarch.h: Likewise.
> 	* gdbarch.sh (create_memtag_notes_from_range): New hook.
> 	(decode_memtag_note): Likewise.
> 	* linux-tdep.c: Include gdbsupport/memtag.h and memtag.h.
> 	(linux_address_in_memtag_page): Renamed to...
> 	(linux_process_address_in_memtag_page): ... this.
> 	(linux_core_file_address_in_memtag_page): New function.
> 	(linux_address_in_memtag_page): Likewise.
> 	(linux_make_memtag_corefile_notes): Likewise.
> 	(linux_make_corefile_notes): Handle memory tag notes.
> 	* memtag.c: New file.
> 	* memtag.h: New file.
> 
> gdb/doc/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.texinfo (AArch64 Memory Tagging Extension): Mention support
> 	for memory tagging in core files.
> 
> gdb/testsuite/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* gdb.arch/aarch64-mte-gcore.c: New file.
> 	* gdb.arch/aarch64-mte-gcore.exp: New file.
> 
> gdbsupport/ChangeLog:
> 
> YYYY-MM-DD  Luis Machado  <luis.machado@linaro.org>
> 
> 	* memtag.h: New file.
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |   4 +
>   gdb/aarch64-linux-tdep.c                     | 221 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.h                 |  17 ++
>   gdb/corelow.c                                |  63 ++++++
>   gdb/doc/gdb.texinfo                          |   4 +
>   gdb/gdbarch.c                                |  64 ++++++
>   gdb/gdbarch.h                                |  16 ++
>   gdb/gdbarch.sh                               |   6 +
>   gdb/linux-tdep.c                             |  97 +++++++-
>   gdb/memtag.c                                 |  88 ++++++++
>   gdb/memtag.h                                 |  46 ++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 ++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 111 ++++++++++
>   gdbsupport/memtag.h                          |  39 ++++
>   15 files changed, 867 insertions(+), 3 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>   create mode 100644 gdbsupport/memtag.h
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index f664d964536..12fb3b390b1 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1100,6 +1100,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index ab678acec8b..58b9f739d4f 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>   
>   *** Changes since GDB 10
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
>   * GDB now supports general memory tagging functionality if the underlying
>     architecture supports the proper primitives and hooks.  Currently this is
>     enabled only for AArch64 MTE.
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index e9761ed2189..04498f3b6c0 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -52,6 +52,9 @@
>   #include "value.h"
>   
>   #include "gdbsupport/selftest.h"
> +#include "gdbsupport/memtag.h"
> +
> +#include "elf/common.h"
>   
>   /* Signal frame handling.
>   
> @@ -1779,6 +1782,213 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* Memory tag note header size.  Includes both the generic and the
> +   arch-specific parts.  */
> +#define NT_MEMTAG_TOTAL_HEADER_SIZE (NT_MEMTAG_GENERIC_HEADER_SIZE \
> +				     + NT_MEMTAG_MTE_HEADER_SIZE)
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the aarch64_create_memtag_notes_from_range
> +   gdbarch hook.  Create core file notes for memory tags.  */
> +
> +static std::vector<gdb::byte_vector>
> +aarch64_linux_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +					      CORE_ADDR start_address,
> +					      CORE_ADDR end_address)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> +						  end_address - start_address,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Vector of memory tag notes. Add the MTE note (we only have MTE tags
> +     at the moment).  */
> +  std::vector<gdb::byte_vector> notes (1);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return notes;
> +
> +  /* Adjust the MTE note size to hold the header + tags.  */
> +  notes[0].resize (NT_MEMTAG_TOTAL_HEADER_SIZE + granules);
> +
> +  CORE_ADDR address = start_address;
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s]."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  notes.resize (0);
> +	  return notes;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Create the header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  gdb_byte *buf = notes[0].data ();
> +
> +  /* Generic header.  */
> +  /* Tag dump format */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +			  byte_order, NT_MEMTAG_TYPE_AARCH_MTE);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  /* Start address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +			  byte_order, start_address);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  /* End address */
> +  store_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +			  byte_order, end_address);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* MTE-specific header.  */
> +  /* Granule byte size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::granule_byte_size),
> +			  byte_order, AARCH64_MTE_GRANULE_SIZE);
> +  buf += sizeof (tag_dump_mte::granule_byte_size);
> +
> +  /* Tag bit size */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::tag_bit_size),
> +			  byte_order, AARCH64_MTE_TAG_BIT_SIZE);
> +  buf += sizeof (tag_dump_mte::tag_bit_size);
> +
> +  /* Unused value */
> +  store_unsigned_integer (buf, sizeof (tag_dump_mte::__unused), byte_order, 0);
> +
> +  /* Store the tags.  */
> +  memcpy (notes[0].data () + NT_MEMTAG_TOTAL_HEADER_SIZE, tags.data (),
> +	  tags.size ());
> +
> +  return notes;
> +}
> +
> +/* AArch64 Linux implementation of the decode_memtag_note gdbarch
> +   hook.  Decode a memory tag note and return the requested tags.
> +
> +   The note is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_note (struct gdbarch *gdbarch,
> +				  gdb::array_view <const gdb_byte> note,
> +				  CORE_ADDR address, size_t length)
> +{
> +  gdb::byte_vector tags;
> +
> +  /* Sanity check.  */
> +  if (note.size () < NT_MEMTAG_TOTAL_HEADER_SIZE)
> +    {
> +      warning (_("Malformed core note - too short for MTE header.\n"
> +		 "Expected %s bytes but got %s bytes."),
> +	       pulongest (NT_MEMTAG_TOTAL_HEADER_SIZE),
> +	       pulongest (note.size ()));
> +      return tags;
> +    }
> +
> +  /* The amount of memory tag granules we need to fetch.  */
> +  size_t granules
> +    = aarch64_mte_get_tag_granules (address, length, AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to decode, just return.  */
> +  if (granules == 0)
> +    return tags;
> +
> +  /* Read the generic header.  */
> +  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
> +  const gdb_byte *buf = note.data ();
> +
> +  unsigned int format
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::format),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::format);
> +
> +  CORE_ADDR start_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::start_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::start_vma);
> +
> +  CORE_ADDR end_vma
> +    = extract_unsigned_integer (buf, sizeof (tag_dump_header::end_vma),
> +				byte_order);
> +  buf += sizeof (tag_dump_header::end_vma);
> +
> +  /* Validate that ADDRESS + LENGTH doesn't fall outside of this note's
> +     range of addresses.  */
> +  gdb_assert (address + length < end_vma);
> +
> +  /* Is the tag header format correct for this note?  */
> +  if (format != NT_MEMTAG_TYPE_AARCH_MTE)
> +    {
> +      warning (_("Unexpected memory tag note format.\n"
> +		 "Expected %x but got %x."), NT_MEMTAG_TYPE_AARCH_MTE,
> +	       format);
> +      return tags;
> +    }
> +
> +  size_t expected_tag_bytes = (end_vma - start_vma)/AARCH64_MTE_GRANULE_SIZE;
> +
> +  /* Does the number of tag bytes in this note match the expected number
> +     of tag bytes the note says it has?  */
> +  if (note.size () < (NT_MEMTAG_TOTAL_HEADER_SIZE + expected_tag_bytes))
> +    {
> +      warning (_("Unexpected tag data size.\n"
> +		 "Expected %s but got %s."), pulongest (expected_tag_bytes),
> +	       pulongest (note.size () - NT_MEMTAG_TOTAL_HEADER_SIZE));
> +      return tags;
> +    }
> +
> +  /* Calculate how many granules we need to skip to get to the granule of
> +     ADDRESS.  Align both the start address and the requested address
> +     so it is easier to get the number of granules to skip.  This way we
> +     don't need to consider cases where ADDRESS falls in the middle of a
> +     tag granule range.  */
> +  CORE_ADDR aligned_start_address
> +    = align_down (start_vma, AARCH64_MTE_GRANULE_SIZE);
> +  CORE_ADDR aligned_address = align_down (address, AARCH64_MTE_GRANULE_SIZE);
> +
> +  size_t skipped_granules
> +    = aarch64_mte_get_tag_granules (aligned_start_address,
> +				    aligned_address - aligned_start_address,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Point to the block of data that contains the first granule we are
> +     interested in.  */
> +  const gdb::array_view<const gdb_byte> tags_data
> +    = note.slice (NT_MEMTAG_TOTAL_HEADER_SIZE + skipped_granules, granules);
> +
> +  /* Read the tag granules.  */
> +  for (size_t i = 0; i < granules; i++)
> +      tags.push_back (tags_data[i]);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1862,6 +2072,17 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create memory tag notes for a particular range of
> +	 addresses.  */
> +      set_gdbarch_create_memtag_notes_from_range
> +	(gdbarch, aarch64_linux_create_memtag_notes_from_range);
> +
> +      /* Core file helper to decode a memory tag note.  */
> +      set_gdbarch_decode_memtag_note (gdbarch,
> +				      aarch64_linux_decode_memtag_note);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index 2aa97eb861a..7da9de4aefb 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,20 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* NT_MEMTAG header for MTE tags.  */
> +struct tag_dump_mte
> +{
> +  /* Size of the tag granule in bytes.  */
> +  uint16_t granule_byte_size;
> +  /* Size of the tag in bits.  */
> +  uint16_t tag_bit_size;
> +  /* Reserved field for the future.  */
> +  uint16_t __unused;
> +};
> +
> +/* Size of the MTE header for a NT_MEMTAG note.  */
> +#define NT_MEMTAG_MTE_HEADER_SIZE (sizeof (tag_dump_mte::granule_byte_size) \
> +				   + sizeof (tag_dump_mte::tag_bit_size) \
> +				   + sizeof (tag_dump_mte::__unused))
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 452b4dd4f9a..24c5bf29a11 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -51,6 +51,8 @@
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
>   #include "observable.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -100,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1115,6 +1124,60 @@ core_target::info_proc (const char *args, enum info_proc_what request)
>     return true;
>   }
>   
> +/* Implementation of the "supports_memory_tagging" target_ops method.  */
> +
> +bool
> +core_target::supports_memory_tagging ()
> +{
> +  /* Look for memory tag notes.  If they exist, that means this core file
> +     supports memory tagging.  */
> +
> +  return (bfd_get_section_by_name (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* Make sure we have a way to decode the memory tag notes.  */
> +  if (!gdbarch_decode_memtag_note_p (gdbarch))
> +    error (_("gdbarch_decode_memtag_note not implemented for this "
> +	     "architecture."));
> +
> +  memtag_note_info info;
> +  info.memtag_section = nullptr;
> +
> +  while (get_next_core_memtag_section (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_note (gdbarch, info.note, 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 NT_MEMTAG
> +       note.  Check if we need to fetch tags from a different section.  */
> +    if (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 90d827a50e7..1b001e6cacb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25257,6 +25257,10 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index 208cf4b5aaa..de384da2e9a 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -283,6 +283,8 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range;
> +  gdbarch_decode_memtag_note_ftype *decode_memtag_note;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -667,6 +669,8 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_notes_from_range, has predicate.  */
> +  /* Skip verify of decode_memtag_note, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -925,6 +929,18 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: core_xfer_siginfo = <%s>\n",
>                         host_address_to_string (gdbarch->core_xfer_siginfo));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_notes_from_range_p() = %d\n",
> +                      gdbarch_create_memtag_notes_from_range_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: create_memtag_notes_from_range = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_notes_from_range));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_note_p() = %d\n",
> +                      gdbarch_decode_memtag_note_p (gdbarch));
> +  fprintf_unfiltered (file,
> +                      "gdbarch_dump: decode_memtag_note = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_note));
>     fprintf_unfiltered (file,
>                         "gdbarch_dump: decr_pc_after_break = %s\n",
>                         core_addr_to_string_nz (gdbarch->decr_pc_after_break));
> @@ -3898,6 +3914,54 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_notes_from_range != NULL;
> +}
> +
> +std::vector<gdb::byte_vector>
> +gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_notes_from_range != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_create_memtag_notes_from_range called\n");
> +  return gdbarch->create_memtag_notes_from_range (gdbarch, start_address, end_address);
> +}
> +
> +void
> +set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch,
> +                                            gdbarch_create_memtag_notes_from_range_ftype create_memtag_notes_from_range)
> +{
> +  gdbarch->create_memtag_notes_from_range = create_memtag_notes_from_range;
> +}
> +
> +bool
> +gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_note != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_note != NULL);
> +  if (gdbarch_debug >= 2)
> +    fprintf_unfiltered (gdb_stdlog, "gdbarch_decode_memtag_note called\n");
> +  return gdbarch->decode_memtag_note (gdbarch, note, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch,
> +                                gdbarch_decode_memtag_note_ftype decode_memtag_note)
> +{
> +  gdbarch->decode_memtag_note = decode_memtag_note;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
> index 7157e5596fd..80e244624de 100644
> --- a/gdb/gdbarch.h
> +++ b/gdb/gdbarch.h
> @@ -980,6 +980,22 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Create memory tag core file notes given a range of addresses. */
> +
> +extern bool gdbarch_create_memtag_notes_from_range_p (struct gdbarch *gdbarch);
> +
> +typedef std::vector<gdb::byte_vector> (gdbarch_create_memtag_notes_from_range_ftype) (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern std::vector<gdb::byte_vector> gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, CORE_ADDR start_address, CORE_ADDR end_address);
> +extern void set_gdbarch_create_memtag_notes_from_range (struct gdbarch *gdbarch, gdbarch_create_memtag_notes_from_range_ftype *create_memtag_notes_from_range);
> +
> +/* Decode a memory tag note and return the tags that it contains. */
> +
> +extern bool gdbarch_decode_memtag_note_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_note_ftype) (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_note (struct gdbarch *gdbarch, gdbarch_decode_memtag_note_ftype *decode_memtag_note);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
> index 43e51341f97..7c5eed0780c 100755
> --- a/gdb/gdbarch.sh
> +++ b/gdb/gdbarch.sh
> @@ -745,6 +745,12 @@ M;gdb::unique_xmalloc_ptr<char>;make_corefile_notes;bfd *obfd, int *note_size;ob
>   # Find core file memory regions
>   M;int;find_memory_regions;find_memory_region_ftype func, void *data;func, data
>   
> +# Create memory tag core file notes given a range of addresses.
> +M;std::vector<gdb::byte_vector>;create_memtag_notes_from_range;CORE_ADDR start_address, CORE_ADDR end_address;start_address, end_address
> +
> +# Decode a memory tag note and return the tags that it contains.
> +M;gdb::byte_vector;decode_memtag_note;gdb::array_view<const gdb_byte> note, CORE_ADDR address, size_t length;note, address, length
> +
>   # Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>   # core file into buffer READBUF with length LEN.  Return the number of bytes read
>   # (zero indicates failure).
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 927e69bf1e1..6192cc4421b 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -41,6 +41,8 @@
>   #include "gdbsupport/gdb_optional.h"
>   #include "gcore.h"
>   #include "gcore-elf.h"
> +#include "gdbsupport/memtag.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   
> @@ -1438,10 +1440,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1473,6 +1476,91 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_note_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
> +/* For each memory map entry that has memory tagging enabled, create a new
> +   core file note that contains all of its memory tags.  Save the data to
> +   NOTE_DATA and update NOTE_SIZE accordingly.  */
> +
> +static void
> +linux_make_memtag_corefile_notes (struct gdbarch *gdbarch, bfd *obfd,
> +				  gdb::unique_xmalloc_ptr<char> &note_data,
> +				  int *note_size)
> +{
> +  if (current_inferior ()->fake_pid_p)
> +    return;
> +
> +  /* If the architecture doesn't have a hook to return memory tag notes,
> +     there is nothing left to do.  */
> +  if (!gdbarch_create_memtag_notes_from_range_p (gdbarch))
> +    return;
> +
> +  pid_t pid = current_inferior ()->pid;
> +
> +  std::string smaps_file = string_printf ("/proc/%d/smaps", pid);
> +
> +  gdb::unique_xmalloc_ptr<char> data
> +    = target_fileio_read_stralloc (NULL, smaps_file.c_str ());
> +
> +  if (data == nullptr)
> +    return;
> +
> +  /* Parse the contents of smaps into a vector.  */
> +  std::vector<struct smaps_data> smaps
> +    = parse_smaps_data (data.get (), smaps_file);
> +
> +  for (const smaps_data &map : smaps)
> +    {
> +      /* Does this mapping have memory tagging enabled? If so, save the
> +	 memory tags to the core file note.  */
> +      if (map.vmflags.memory_tagging == 0)
> +	continue;
> +
> +      /* Ask the architecture to create (one or more) NT_MEMTAG notes for
> +	 this particular memory range, including the header.
> +
> +	 If the notes are too big, we may need to break up the transfer
> +	 into smaller chunks.
> +
> +	 If the architecture returns an empty vector, that means there are
> +	 no memory tag notes to write.  */
> +      std::vector<gdb::byte_vector> memory_tag_notes
> +	= gdbarch_create_memtag_notes_from_range (gdbarch,
> +						  map.start_address,
> +						  map.end_address);
> +      /* Write notes to the core file.  */
> +      for (const gdb::byte_vector &note : memory_tag_notes)
> +	{
> +	  note_data.reset (elfcore_write_note (obfd, note_data.release (),
> +					       note_size, "CORE",
> +					       NT_MEMTAG, note.data (),
> +					       note.size ()));
> +	}
> +    }
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -2051,6 +2139,9 @@ linux_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
>   	return NULL;
>       }
>   
> +  /* Dump the memory tags, if any.  */
> +  linux_make_memtag_corefile_notes (gdbarch, obfd, note_data, note_size);
> +
>     /* File mappings.  */
>     linux_make_mappings_corefile_notes (gdbarch, obfd, note_data, note_size);
>   
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..4d92ecde84a
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,88 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 2021 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 "memtag.h"
> +#include "gdbsupport/memtag.h"
> +#include "bfd.h"
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return a pointer to a .memtag section containing ADDRESS or nullptr
> +   of none are found.
> +
> +   If SECTION is provided, search from that section onwards.  */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_note_info &info)
> +{
> +  /* If SECTION is nullptr, start a fresh lookup.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, ".memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t note_size = bfd_section_size (section);
> +
> +      /* If the note is smaller than the size of the header, this core note
> +	 is malformed.  */
> +      if (note_size < NT_MEMTAG_GENERIC_HEADER_SIZE)
> +	{
> +	  warning (_("Malformed core note - too short for NT_MEMTAG generic "
> +		     "header.\n"
> +		     "Expected %s bytes but got %s bytes."),
> +		   pulongest (NT_MEMTAG_GENERIC_HEADER_SIZE),
> +		   pulongest (note_size));
> +	  return false;
> +	}
> +
> +      gdb::byte_vector note (note_size);
> +
> +      /* Fetch the contents of this particular memtag note.  */
> +      if (!bfd_get_section_contents (abfd, section,
> +				     note.data (), 0, note_size))
> +	{
> +	  warning (_("could not get core note contents."));
> +	  return false;
> +	}
> +
> +      /* Read the generic header of the note.  It contains the format,
> +	 start address and end address.  */
> +      uint64_t start_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format));
> +      uint64_t end_address
> +	= bfd_get_64 (abfd, note.data () + sizeof (tag_dump_header::format)
> +			    + sizeof (tag_dump_header::start_vma));
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.note = note;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..43c9efb39a3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,46 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_note_info
> +{
> +  CORE_ADDR start_address;
> +  CORE_ADDR end_address;
> +  gdb::byte_vector note;
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through NT_MEMTAG notes in a core file.
> +
> +   Return TRUE if there is a .memtag section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a .memtag section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address, memtag_note_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..d0bcd036972
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,111 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +if { $program_loaded } {
> +    return -1
> +}
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}
> diff --git a/gdbsupport/memtag.h b/gdbsupport/memtag.h
> new file mode 100644
> index 00000000000..bb47eed220b
> --- /dev/null
> +++ b/gdbsupport/memtag.h
> @@ -0,0 +1,39 @@
> +/* Generic memory tagging definitions.
> +   Copyright (C) 2021 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/>.  */
> +
> +#ifndef GDBSUPPORT_MEMTAG_H
> +#define GDBSUPPORT_MEMTAG_H
> +
> +/* Generic NT_MEMTAG header.  */
> +struct tag_dump_header
> +{
> +  /* Tag format.  */
> +  uint16_t format;
> +  /* Start address of the tagged range.  */
> +  uint64_t start_vma;
> +  /* End address of the tagged range.  */
> +  uint64_t end_vma;
> +};
> +
> +/* Size of the generic header for the NT_MEMTAG note.  This is OS-independent
> +   and should be shared with OS-specific and arch-specific code.  */
> +#define NT_MEMTAG_GENERIC_HEADER_SIZE (sizeof (tag_dump_header::format) \
> +				       + sizeof (tag_dump_header::start_vma) \
> +				       + sizeof (tag_dump_header::end_vma))
> +
> +#endif /* GDBSUPPORT_MEMTAG_H */
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-14 13:07       ` Catalin Marinas
@ 2021-07-29  2:26         ` Simon Marchi
  2021-07-29 16:03           ` John Baldwin
  2021-07-29 18:10           ` Catalin Marinas
  0 siblings, 2 replies; 47+ messages in thread
From: Simon Marchi @ 2021-07-29  2:26 UTC (permalink / raw)
  To: Catalin Marinas, Joel Brobecker
  Cc: Luis Machado via Gdb-patches, david.spickett

On 2021-07-14 9:07 a.m., Catalin Marinas wrote:
> On Sun, Jul 11, 2021 at 07:22:00AM -0700, Joel Brobecker wrote:
>> Luis is trying to have this new feature ready for the GDB 11.1
>> release. As I understand it, the AArch64-specific part has already
>> been approved, so he's waiting for someone to review the rest of
>> the changes. Is there a Global Maintainer who would feel comfortable
>> doing so and who would have the time?
> 
> I'm a Linux kernel maintainer, so not much for me to review on the gdb
> front. But feedback from the gdb maintainers would be appreciated.
> 
> W.r.t. upstreaming the feature in gdb, I'd wait until the Linux
> counterpart is merged. We agreed in principle on the ABI but I wouldn't
> consider it stable until the feature hits a kernel release.

I reviewed a few of the previous iterations, and there were only really
some nits remaining, so I am confident that this version is OK, from a
GDB point of view.

ABI-wise, I tend to agree with Catalin: as long as it's not in a
released kernel, the format *could* change.  But is it a problem in
practice?  Let's say this happens:

 - We merge support for MTE core files in GDB 11
 - The format gets changed, merged and released in a kernel
 - We fix GDB 12 based on the new format

Then GDB 11 users end up with a GDB that can't read MTE notes.  But had
we not merged the support for it, they would also end up with a GDB that
can't read MTE notes.  In both cases, we'll tell them "upgrade to GDB
12" (once it's released).

If we merge support for MTE in core files now and if the format
doesn't change, everybody is happy.  I guess it depends on how likely it
is that the format is going to change.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-29  2:26         ` Simon Marchi
@ 2021-07-29 16:03           ` John Baldwin
  2021-07-29 18:10           ` Catalin Marinas
  1 sibling, 0 replies; 47+ messages in thread
From: John Baldwin @ 2021-07-29 16:03 UTC (permalink / raw)
  To: Simon Marchi, Catalin Marinas, Joel Brobecker
  Cc: david.spickett, Luis Machado via Gdb-patches

On 7/28/21 7:26 PM, Simon Marchi via Gdb-patches wrote:
> On 2021-07-14 9:07 a.m., Catalin Marinas wrote:
>> On Sun, Jul 11, 2021 at 07:22:00AM -0700, Joel Brobecker wrote:
>>> Luis is trying to have this new feature ready for the GDB 11.1
>>> release. As I understand it, the AArch64-specific part has already
>>> been approved, so he's waiting for someone to review the rest of
>>> the changes. Is there a Global Maintainer who would feel comfortable
>>> doing so and who would have the time?
>>
>> I'm a Linux kernel maintainer, so not much for me to review on the gdb
>> front. But feedback from the gdb maintainers would be appreciated.
>>
>> W.r.t. upstreaming the feature in gdb, I'd wait until the Linux
>> counterpart is merged. We agreed in principle on the ABI but I wouldn't
>> consider it stable until the feature hits a kernel release.
> 
> I reviewed a few of the previous iterations, and there were only really
> some nits remaining, so I am confident that this version is OK, from a
> GDB point of view.
> 
> ABI-wise, I tend to agree with Catalin: as long as it's not in a
> released kernel, the format *could* change.  But is it a problem in
> practice?  Let's say this happens:
> 
>   - We merge support for MTE core files in GDB 11
>   - The format gets changed, merged and released in a kernel
>   - We fix GDB 12 based on the new format
> 
> Then GDB 11 users end up with a GDB that can't read MTE notes.  But had
> we not merged the support for it, they would also end up with a GDB that
> can't read MTE notes.  In both cases, we'll tell them "upgrade to GDB
> 12" (once it's released).
> 
> If we merge support for MTE in core files now and if the format
> doesn't change, everybody is happy.  I guess it depends on how likely it
> is that the format is going to change.

I think the format is suitable and probably won't need to change.  I have an
interest in using it for another type of memory tags (CHERI & Morello) and
this format will support those (and in fact it would even support an
architecture with multiple memory tags).

-- 
John Baldwin

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-29  2:26         ` Simon Marchi
  2021-07-29 16:03           ` John Baldwin
@ 2021-07-29 18:10           ` Catalin Marinas
  2021-07-29 18:20             ` Simon Marchi
  1 sibling, 1 reply; 47+ messages in thread
From: Catalin Marinas @ 2021-07-29 18:10 UTC (permalink / raw)
  To: Simon Marchi; +Cc: Joel Brobecker, Luis Machado via Gdb-patches, david.spickett

On Wed, Jul 28, 2021 at 10:26:58PM -0400, Simon Marchi wrote:
> On 2021-07-14 9:07 a.m., Catalin Marinas wrote:
> > On Sun, Jul 11, 2021 at 07:22:00AM -0700, Joel Brobecker wrote:
> >> Luis is trying to have this new feature ready for the GDB 11.1
> >> release. As I understand it, the AArch64-specific part has already
> >> been approved, so he's waiting for someone to review the rest of
> >> the changes. Is there a Global Maintainer who would feel comfortable
> >> doing so and who would have the time?
> > 
> > I'm a Linux kernel maintainer, so not much for me to review on the gdb
> > front. But feedback from the gdb maintainers would be appreciated.
> > 
> > W.r.t. upstreaming the feature in gdb, I'd wait until the Linux
> > counterpart is merged. We agreed in principle on the ABI but I wouldn't
> > consider it stable until the feature hits a kernel release.
> 
> I reviewed a few of the previous iterations, and there were only really
> some nits remaining, so I am confident that this version is OK, from a
> GDB point of view.
> 
> ABI-wise, I tend to agree with Catalin: as long as it's not in a
> released kernel, the format *could* change.  But is it a problem in
> practice?  Let's say this happens:
> 
>  - We merge support for MTE core files in GDB 11
>  - The format gets changed, merged and released in a kernel
>  - We fix GDB 12 based on the new format
> 
> Then GDB 11 users end up with a GDB that can't read MTE notes. But had
> we not merged the support for it, they would also end up with a GDB that
> can't read MTE notes.  In both cases, we'll tell them "upgrade to GDB
> 12" (once it's released).

I'm more worried about GDB 11 reading the MTE notes incorrectly rather
than not reading them at all.

> If we merge support for MTE in core files now and if the format
> doesn't change, everybody is happy.  I guess it depends on how likely it
> is that the format is going to change.

That's hard to say before we go through at least one Linux kernel
mailing review.

-- 
Catalin

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-29 18:10           ` Catalin Marinas
@ 2021-07-29 18:20             ` Simon Marchi
  2021-08-01 15:44               ` Joel Brobecker
  0 siblings, 1 reply; 47+ messages in thread
From: Simon Marchi @ 2021-07-29 18:20 UTC (permalink / raw)
  To: Catalin Marinas
  Cc: Joel Brobecker, Luis Machado via Gdb-patches, david.spickett

On 2021-07-29 2:10 p.m., Catalin Marinas wrote:
> I'm more worried about GDB 11 reading the MTE notes incorrectly rather
> than not reading them at all.

Yeah, if a GDB 11 that knows how to read the pre-standardized notes does
not fail gracefully when encountering the standardized notes, it would
be bad.  If it just skipped them and said "I don't recognize them", it
would be ok.

>> If we merge support for MTE in core files now and if the format
>> doesn't change, everybody is happy.  I guess it depends on how likely it
>> is that the format is going to change.
> That's hard to say before we go through at least one Linux kernel
> mailing review.

Oh, ok, I thought it had already gone through some review cycles.  Then
I'll leave it to you all to decide, those who have some stakes in this.

Simon

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-07-29 18:20             ` Simon Marchi
@ 2021-08-01 15:44               ` Joel Brobecker
  2021-08-02 12:06                 ` Luis Machado
  0 siblings, 1 reply; 47+ messages in thread
From: Joel Brobecker @ 2021-08-01 15:44 UTC (permalink / raw)
  To: Simon Marchi
  Cc: Catalin Marinas, Joel Brobecker, Luis Machado via Gdb-patches,
	david.spickett

> On 2021-07-29 2:10 p.m., Catalin Marinas wrote:
> > I'm more worried about GDB 11 reading the MTE notes incorrectly rather
> > than not reading them at all.
> 
> Yeah, if a GDB 11 that knows how to read the pre-standardized notes does
> not fail gracefully when encountering the standardized notes, it would
> be bad.  If it just skipped them and said "I don't recognize them", it
> would be ok.

Agreed.

Is there a way we could check the notes? I looked at the patch,
and it doesn't seem like there is any kind of versioning or
identifier we could use to determine which version of the notes
we get. But perhaps we can at least do some kind of consistency
check, such as for instance comparing the size of the section
with the contents of the section header, and generate an error
if that doesn't match. The error might not be as informative,
but perhaps still good enough?

> Oh, ok, I thought it had already gone through some review cycles.  Then
> I'll leave it to you all to decide, those who have some stakes in this.

-- 
Joel

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PING][PATCH,v5][AArch64] MTE corefile support
  2021-08-01 15:44               ` Joel Brobecker
@ 2021-08-02 12:06                 ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2021-08-02 12:06 UTC (permalink / raw)
  To: Joel Brobecker, Simon Marchi
  Cc: Catalin Marinas, Luis Machado via Gdb-patches, david.spickett

On 8/1/21 12:44 PM, Joel Brobecker wrote:
>> On 2021-07-29 2:10 p.m., Catalin Marinas wrote:
>>> I'm more worried about GDB 11 reading the MTE notes incorrectly rather
>>> than not reading them at all.
>>
>> Yeah, if a GDB 11 that knows how to read the pre-standardized notes does
>> not fail gracefully when encountering the standardized notes, it would
>> be bad.  If it just skipped them and said "I don't recognize them", it
>> would be ok.
> 
> Agreed.
> 
> Is there a way we could check the notes? I looked at the patch,
> and it doesn't seem like there is any kind of versioning or
> identifier we could use to determine which version of the notes
> we get. But perhaps we can at least do some kind of consistency

We have a generic header containing NT_MEMTAG and an arch-specific 
header identifier containing NT_MEMTAG_TYPE_AARCH_MTE. The arch-specific 
identifier can be versioned as needed. We could, for example, have 
NT_MEMTAG_TYPE_AARCH_MTE_V2.

> check, such as for instance comparing the size of the section
> with the contents of the section header, and generate an error
> if that doesn't match. The error might not be as informative,
> but perhaps still good enough?

On further discussion with the Linux Kernel developers, we're going to 
take the safe path and will wait for this part to be under review on the 
Linux Kernel ML before adopting it in GDB.

> 
>> Oh, ok, I thought it had already gone through some review cycles.  Then
>> I'll leave it to you all to decide, those who have some stakes in this.
> 

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-06-27 14:51   ` Pedro Alves
@ 2022-07-11 10:13     ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2022-07-11 10:13 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Hi,

On 6/27/22 15:51, Pedro Alves wrote:
> Hi Luis,
> 
> On 2022-05-03 22:56, Luis Machado via Gdb-patches wrote:
>> v4:
>>
>> - Updated documentation (added cross-references).
>> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>>    PT_AARCH64_MEMTAG_MTE.
>>
>> v3:
>>
>> - Updated NEWS and documentation to be more thorough.
>>
>> v2:
>>
>> - Rework memory tag section handling to use generic section fields.
>>
>> --
>>
>> Teach GDB how to dump memory tags for AArch64 when using the gcore command
>> and how to read memory tag data back from a core file generated by GDB
>> (via gcore) or by the Linux kernel.
>>
>> The format is documented in the Linux Kernel documentation [1].
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own PT_AARCH64_MEMTAG_MTE segment.
> 
> ...
> 
>> A section named ".memtag" is created for each
>> of those segments when reading the core file back.
> 
> It's "memtag", not ".memtag", AFAICT.
> 

Yep. I had fixed this already in a local version for an upcoming v5.

>> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
>> +
>> +static asection *
>> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
>> +				     CORE_ADDR address, size_t size)
>> +{
>> +  gdb_assert (obfd != nullptr);
>> +  gdb_assert (size > 0);
>> +
>> +  /* Create the section and associated program header.  */
>> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
>> +
>> +  if (mte_section == nullptr)
>> +    return nullptr;
>> +
>> +  bfd_set_section_vma (mte_section, address);
>> +  /* The size of the memory range covered by the memory tags.  We reuse the
>> +     section's rawsize field for this purpose.  */
>> +  mte_section->rawsize = size;
>> +  /* Tags are stored packed as 2 tags per byte.  */
>> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
> 
> Shouldn't the /2 division round up, rather than down, to account for odd number
> of tags?  I.e., this:
> 
>     bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE + 1) / 2);
> 

Indeed. I think we just got away with it because we can only allocate maps sized in multiples of the page size.

So that will guarantee we will also have an exact division here. It is not quite correct for an odd number
of tag granules. I'll fix this.

>> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
>> +     refuse to write data to this section.  */
>> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> 
> How about using bfd_make_section_anyway_with_flags further above?
> 

Should work fine. Done now.

>> +
>> +  /* Store program header information.  */
>> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
>> +		   &mte_section);
>> +
>> +  return mte_section;
>> +}
>> +
> 
> 
>> +
>> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
>> +
>> +static bool
>> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  size_t segment_size = osec->rawsize;
>> +  CORE_ADDR start_address = bfd_section_vma (osec);
>> +  CORE_ADDR end_address = start_address + segment_size;
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
>> +						  AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return true;
>> +
>> +  CORE_ADDR address = start_address;
>> +
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
>> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> 
> '?' and ':' go on the next line, like other operators.  Wrap expression in
> parens for proper alignment, per GNU standards.  So:
> 
>    = ((granules >= MAX_TAGS_TO_TRANSFER)
>       ? MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE
>       : granules * AARCH64_MTE_GRANULE_SIZE);
> 
> (note how you can read "?" as "then" and ":" as "else", this way.)
> 

Fixed the formatting now. Thanks.

>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +				 static_cast<int> (memtag_type::allocation)))
>> +	{
>> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
>> +		     phex_nz (start_address, sizeof (start_address)),
>> +		     phex_nz (end_address, sizeof (end_address)));
>> +	  return false;
>> +	}
>> +
>> +      /* Transfer over the tags that have been read.  */
>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> 
> (Just a passing comment, feel free to ignore: If target_fetch_memtags were adjusted to
> append to the buffer instead of clearing it, you could pass "tags" directly to
> target_fetch_memtags and get rid of this copying.)
> 

I think it is a reasonable idea. I'm not keen on chaging it up at this point, but that would be a good
improvement. I just need to make sure the callers all cleanup the tags vector before passing it onwards
to the functions.

>> +
>> +      /* Adjust the remaining granules and starting address.  */
>> +      granules -= tags_read.size ();
>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> +    }
>> +
>> +  /* Pack the MTE tag bits.  */
>> +  aarch64_mte_pack_tags (tags);
>> +
>> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
>> +				 0, tags.size ()))
>> +    {
>> +      warning (_("Failed to write %s bytes of corefile memory "
>> +		 "tag content (%s)."),
>> +	       pulongest (tags.size ()),
>> +	       bfd_errmsg (bfd_get_error ()));
>> +    }
>> +  return true;
>> +}
>> +
> 
>> +
>> +/* See arch/aarch64-mte-linux.h */
>> +
>>   size_t
>>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>>   {
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index d158926feff..8a145b447aa 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>   
>>   /* We have one tag per 16 bytes of memory.  */
>>   #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>   
>> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>      It is always possible to get the logical tag.  */
>>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>   
>> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
>> +   2 tags per byte and resize the vector.  */
>> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
>> +
>> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
>> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
>> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
>> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
> 
> All other declarations in the header have explicit "extern".
> 

Done now. Thanks.

>> +
>>   #endif /* ARCH_AARCH64_LINUX_H */
> 
> 
>> +/* 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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* 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 (core_bfd, info.memtag_section,
>> +				       address, info))
>> +  {
>> +    size_t adjusted_length
>> +      = (address + len < info.end_address)? len : (info.end_address - address);
> 
> Space before "?".
> 

Fixed.

>> +
>> +    /* 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
>> +get_next_core_memtag_section (bfd *abfd, asection *section,
>> +			      CORE_ADDR address, memtag_section_info &info)
>> +{
>> +  /* If the caller provided no SECTION to start from, search from the
>> +     beginning.  */
>> +  if (section == nullptr)
>> +    section = bfd_get_section_by_name (abfd, "memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t memtag_range_size = section->rawsize;
>> +      size_t tags_size = bfd_section_size (section);
>> +
>> +      /* Empty memory range and empty tag dump should not happen.  */
>> +      gdb_assert (memtag_range_size != 0);
>> +      gdb_assert (tags_size != 0);
> 
> Seems like this could happen with corrupted input?  GDB should not assert
> on bad input.
> 

I turned this into a warning and then we skip to the next section.

>> +
>> +      CORE_ADDR start_address = bfd_section_vma (section);
>> +      CORE_ADDR end_address = start_address + memtag_range_size;
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +	  && address < end_address)
>> +	{
>> +	  info.start_address = start_address;
>> +	  info.end_address = end_address;
>> +	  info.memtag_section = section;
>> +	  return true;
>> +	}
>> +      section = bfd_get_next_section_by_name (abfd, section);
>> +    }
>> +  return false;
>> +}
> 
> 
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2021 Free Software Foundation, Inc.
> 
> 2022 missing.
> 
> 

Fixed now.

>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..8a19c4b449e
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,107 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> 
> 2021 -> 2022.
> 
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +	"Program received signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag $hex" \
>> +	"Logical tag $hex\." \
>> +	"$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> 
> I'd suggest also making sure gdb is able to read kernel-generated core properly.
> You can use core_find to produce one.

I spent a bit of time doing this. I wasn't aware of core_find. The testcase now
exercises both gcore and native core files.

v5 of this patch is on its way.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
                     ` (3 preceding siblings ...)
  2022-06-22  9:04   ` Luis Machado
@ 2022-06-27 14:51   ` Pedro Alves
  2022-07-11 10:13     ` Luis Machado
  4 siblings, 1 reply; 47+ messages in thread
From: Pedro Alves @ 2022-06-27 14:51 UTC (permalink / raw)
  To: Luis Machado, gdb-patches

Hi Luis,

On 2022-05-03 22:56, Luis Machado via Gdb-patches wrote:
> v4:
> 
> - Updated documentation (added cross-references).
> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>   PT_AARCH64_MEMTAG_MTE.
> 
> v3:
> 
> - Updated NEWS and documentation to be more thorough.
> 
> v2:
> 
> - Rework memory tag section handling to use generic section fields.
> 
> --
> 
> Teach GDB how to dump memory tags for AArch64 when using the gcore command
> and how to read memory tag data back from a core file generated by GDB
> (via gcore) or by the Linux kernel.
> 
> The format is documented in the Linux Kernel documentation [1].
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own PT_AARCH64_MEMTAG_MTE segment. 

...

> A section named ".memtag" is created for each
> of those segments when reading the core file back.

It's "memtag", not ".memtag", AFAICT.

> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
> +
> +static asection *
> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> +				     CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (obfd != nullptr);
> +  gdb_assert (size > 0);
> +
> +  /* Create the section and associated program header.  */
> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
> +
> +  if (mte_section == nullptr)
> +    return nullptr;
> +
> +  bfd_set_section_vma (mte_section, address);
> +  /* The size of the memory range covered by the memory tags.  We reuse the
> +     section's rawsize field for this purpose.  */
> +  mte_section->rawsize = size;
> +  /* Tags are stored packed as 2 tags per byte.  */
> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);

Shouldn't the /2 division round up, rather than down, to account for odd number
of tags?  I.e., this:

   bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE + 1) / 2);

> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
> +     refuse to write data to this section.  */
> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);

How about using bfd_make_section_anyway_with_flags further above?

> +
> +  /* Store program header information.  */
> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
> +		   &mte_section);
> +
> +  return mte_section;
> +}
> +


> +
> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
> +
> +static bool
> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  size_t segment_size = osec->rawsize;
> +  CORE_ADDR start_address = bfd_section_vma (osec);
> +  CORE_ADDR end_address = start_address + segment_size;
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return true;
> +
> +  CORE_ADDR address = start_address;
> +
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;

'?' and ':' go on the next line, like other operators.  Wrap expression in
parens for proper alignment, per GNU standards.  So:

  = ((granules >= MAX_TAGS_TO_TRANSFER)
     ? MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE
     : granules * AARCH64_MTE_GRANULE_SIZE);

(note how you can read "?" as "then" and ":" as "else", this way.)

> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  return false;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());

(Just a passing comment, feel free to ignore: If target_fetch_memtags were adjusted to
append to the buffer instead of clearing it, you could pass "tags" directly to
target_fetch_memtags and get rid of this copying.)

> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Pack the MTE tag bits.  */
> +  aarch64_mte_pack_tags (tags);
> +
> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> +				 0, tags.size ()))
> +    {
> +      warning (_("Failed to write %s bytes of corefile memory "
> +		 "tag content (%s)."),
> +	       pulongest (tags.size ()),
> +	       bfd_errmsg (bfd_get_error ()));
> +    }
> +  return true;
> +}
> +

> +
> +/* See arch/aarch64-mte-linux.h */
> +
>  size_t
>  aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>  {
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index d158926feff..8a145b447aa 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>  
>  /* We have one tag per 16 bytes of memory.  */
>  #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>  #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>  #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>  
> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>     It is always possible to get the logical tag.  */
>  extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>  
> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
> +   2 tags per byte and resize the vector.  */
> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> +
> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);

All other declarations in the header have explicit "extern".

> +
>  #endif /* ARCH_AARCH64_LINUX_H */


> +/* 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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* 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 (core_bfd, info.memtag_section,
> +				       address, info))
> +  {
> +    size_t adjusted_length
> +      = (address + len < info.end_address)? len : (info.end_address - address);

Space before "?".

> +
> +    /* 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
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_section_info &info)
> +{
> +  /* If the caller provided no SECTION to start from, search from the
> +     beginning.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, "memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t memtag_range_size = section->rawsize;
> +      size_t tags_size = bfd_section_size (section);
> +
> +      /* Empty memory range and empty tag dump should not happen.  */
> +      gdb_assert (memtag_range_size != 0);
> +      gdb_assert (tags_size != 0);

Seems like this could happen with corrupted input?  GDB should not assert
on bad input.

> +
> +      CORE_ADDR start_address = bfd_section_vma (section);
> +      CORE_ADDR end_address = start_address + memtag_range_size;
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +      section = bfd_get_next_section_by_name (abfd, section);
> +    }
> +  return false;
> +}


> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.

2022 missing.


> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..8a19c4b449e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,107 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.

2021 -> 2022.

> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]

I'd suggest also making sure gdb is able to read kernel-generated core properly.
You can use core_find to produce one.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
                     ` (2 preceding siblings ...)
  2022-06-06  9:28   ` Luis Machado
@ 2022-06-22  9:04   ` Luis Machado
  2022-06-27 14:51   ` Pedro Alves
  4 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2022-06-22  9:04 UTC (permalink / raw)
  To: gdb-patches

Ping? Could I please get a code review on the following?

Eli has approved the documentation changes.

On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> v4:
> 
> - Updated documentation (added cross-references).
> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>    PT_AARCH64_MEMTAG_MTE.
> 
> v3:
> 
> - Updated NEWS and documentation to be more thorough.
> 
> v2:
> 
> - Rework memory tag section handling to use generic section fields.
> 
> --
> 
> Teach GDB how to dump memory tags for AArch64 when using the gcore command
> and how to read memory tag data back from a core file generated by GDB
> (via gcore) or by the Linux kernel.
> 
> The format is documented in the Linux Kernel documentation [1].
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
> of those segments when reading the core file back.
> 
> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
> are stored packed as 2 tags per byte.
> 
> When reading the data back, the tags are unpacked.
> 
> I've added a new testcase to exercise the feature.
> 
> Build-tested with --enable-targets=all and regression tested on aarch64-linux
> Ubuntu 20.04.
> 
> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |  10 ++
>   gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>   gdb/arch/aarch64-mte-linux.h                 |  10 ++
>   gdb/corelow.c                                |  62 +++++++
>   gdb/defs.h                                   |   3 +-
>   gdb/doc/gdb.texinfo                          |  19 +++
>   gdb/gcore.c                                  |  83 ++++++++-
>   gdb/gdbarch-components.py                    |  35 ++++
>   gdb/gdbarch-gen.h                            |  26 +++
>   gdb/gdbarch.c                                |  96 +++++++++++
>   gdb/linux-tdep.c                             |  39 ++++-
>   gdb/memtag.c                                 |  61 +++++++
>   gdb/memtag.h                                 |  50 ++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>   17 files changed, 910 insertions(+), 8 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 418094775a5..fac9364bea4 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 982f4a1a18c..3d925dc3663 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,16 @@
>   
>   *** Changes since GDB 12
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> +  When a process uses memory-mapped pages protected by memory tags (for
> +  example, AArch64 MTE), this additional information will be recorded in
> +  the core file in the event of a crash or if GDB generates a core file
> +  from the current process state.  GDB will show this additional information
> +  automatically, or through one of the memory-tag subcommands.
> +
>   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>   
>   * Remove support for building against Python 2, it is now only possible to
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index 55094b3d88b..12d98e71796 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,9 @@
>   
>   #include "gdbsupport/selftest.h"
>   
> +#include "elf/common.h"
> +#include "elf/aarch64.h"
> +
>   /* Signal frame handling.
>   
>         +------------+  ^
> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
> +
> +static asection *
> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> +				     CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (obfd != nullptr);
> +  gdb_assert (size > 0);
> +
> +  /* Create the section and associated program header.  */
> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
> +
> +  if (mte_section == nullptr)
> +    return nullptr;
> +
> +  bfd_set_section_vma (mte_section, address);
> +  /* The size of the memory range covered by the memory tags.  We reuse the
> +     section's rawsize field for this purpose.  */
> +  mte_section->rawsize = size;
> +  /* Tags are stored packed as 2 tags per byte.  */
> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
> +     refuse to write data to this section.  */
> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> +
> +  /* Store program header information.  */
> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
> +		   &mte_section);
> +
> +  return mte_section;
> +}
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
> +
> +static bool
> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  size_t segment_size = osec->rawsize;
> +  CORE_ADDR start_address = bfd_section_vma (osec);
> +  CORE_ADDR end_address = start_address + segment_size;
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return true;
> +
> +  CORE_ADDR address = start_address;
> +
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  return false;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Pack the MTE tag bits.  */
> +  aarch64_mte_pack_tags (tags);
> +
> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> +				 0, tags.size ()))
> +    {
> +      warning (_("Failed to write %s bytes of corefile memory "
> +		 "tag content (%s)."),
> +	       pulongest (tags.size ()),
> +	       bfd_errmsg (bfd_get_error ()));
> +    }
> +  return true;
> +}
> +
> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
> +   hook.  Decode a memory tag section and return the requested tags.
> +
> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> +				     bfd_section *section,
> +				     int type,
> +				     CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (section != nullptr);
> +
> +  /* The requested address must not be less than section->vma.  */
> +  gdb_assert (section->vma <= address);
> +
> +  /* Figure out how many tags we need to fetch in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +  /* Sanity check.  */
> +  gdb_assert (granules > 0);
> +
> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
> +  size_t granules_from_vma
> +    = aarch64_mte_get_tag_granules (section->vma,
> +				    address - section->vma + length,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> +
> +  /* Figure out the starting offset into the packed tags data.  */
> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> +
> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
> +				 offset, tags.size ()))
> +    error (_("Couldn't read contents from memtag section."));
> +
> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
> +     returning.  */
> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> +  aarch64_mte_unpack_tags (tags, skip_first);
> +
> +  /* Resize to the exact number of tags that was requested.  */
> +  tags.resize (granules);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create a memory tag section for a particular
> +	 PT_LOAD segment.  */
> +      set_gdbarch_create_memtag_section
> +	(gdbarch, aarch64_linux_create_memtag_section);
> +
> +      /* Core file helper to fill a memory tag section with tag data.  */
> +      set_gdbarch_fill_memtag_section
> +	(gdbarch, aarch64_linux_fill_memtag_section);
> +
> +      /* Core file helper to decode a memory tag section.  */
> +      set_gdbarch_decode_memtag_section (gdbarch,
> +					 aarch64_linux_decode_memtag_section);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
> index fc7a8cc00f7..3af6f364e91 100644
> --- a/gdb/arch/aarch64-mte-linux.c
> +++ b/gdb/arch/aarch64-mte-linux.c
> @@ -21,6 +21,62 @@
>   
>   /* See arch/aarch64-mte-linux.h */
>   
> +void
> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> +{
> +  /* Nothing to pack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* If the tags vector has an odd number of elements, add another
> +     zeroed-out element to make it even.  This facilitates packing.  */
> +  if ((tags.size () % 2) != 0)
> +    tags.emplace_back (0);
> +
> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> +       unpacked += 2, packed++)
> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> +
> +  /* Now we have half the size.  */
> +  tags.resize (tags.size () / 2);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
> +void
> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
> +{
> +  /* Nothing to unpack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* An unpacked MTE tags vector will have twice the number of elements
> +     compared to an unpacked one.  */
> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> +
> +  int unpacked = 0, packed = 0;
> +
> +  if (skip_first)
> +    {
> +      /* We are not interested in the first unpacked element, just discard
> +	 it.  */
> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> +      unpacked++;
> +      packed++;
> +    }
> +
> +  for (; packed < tags.size (); unpacked += 2, packed++)
> +    {
> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> +    }
> +
> +  /* Update the original tags vector.  */
> +  tags = std::move (unpacked_tags);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
>   size_t
>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>   {
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index d158926feff..8a145b447aa 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
> +   2 tags per byte and resize the vector.  */
> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> +
> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 8c33fb7ebb2..8b8994f80db 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -52,6 +52,7 @@
>   #include <unordered_set>
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* 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 (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/defs.h b/gdb/defs.h
> index 99bfdd526ff..51a7576a56a 100644
> --- a/gdb/defs.h
> +++ b/gdb/defs.h
> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>   
>   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>   					 int read, int write, int exec,
> -					 int modified, void *data);
> +					 int modified, bool memory_tagged,
> +					 void *data);
>   
>   /* * Possible lvalue types.  Like enum language, this should be in
>      value.h, but needs to be here for the same reason.  */
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 38ad2ac32b0..36f10f20cfb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> +When a process uses memory-mapped pages protected by memory tags (for
> +example, AArch64 MTE), this additional information will be recorded in
> +the core file in the event of a crash or if @value{GDBN} generates a core file
> +from the current process state.
> +
> +The memory tag data will be used so developers can display the memory
> +tags from a particular memory region (using the @samp{m} modifier to the
> +@command{x} command, using the @command{print} command or using the various
> +@command{memory-tag} subcommands.
> +
> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
> +information automatically from the core file, and will show one of the above
> +messages depending on whether the synchronous or asynchronous mode is selected.
> +@xref{Memory Tagging}. @xref{Memory}.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gcore.c b/gdb/gcore.c
> index fdb22b72a07..b81ef81ab84 100644
> --- a/gdb/gcore.c
> +++ b/gdb/gcore.c
> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>     int p_flags = 0;
>     int p_type = 0;
>   
> +  /* Memory tag segments have already been handled by the architecture, as
> +     those contain arch-specific information.  If we have one of those, just
> +     return.  */
> +  if (startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
>     /* FIXME: these constants may only be applicable for ELF.  */
>     if (startswith (bfd_section_name (osec), "load"))
>       p_type = PT_LOAD;
> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>   
>   static int
>   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
> -		       int write, int exec, int modified, void *data)
> +		       int write, int exec, int modified, bool memory_tagged,
> +		       void *data)
>   {
>     bfd *obfd = (bfd *) data;
>     asection *osec;
> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>     return 0;
>   }
>   
> +/* gdbarch_find_memory_region callback for creating a memory tag section.
> +   DATA is 'bfd *' for the core file GDB is creating.  */
> +
> +static int
> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
> +				      int read, int write, int exec,
> +				      int modified, bool memory_tagged,
> +				      void *data)
> +{
> +  /* Are there memory tags in this particular memory map entry?  */
> +  if (!memory_tagged)
> +    return 0;
> +
> +  bfd *obfd = (bfd *) data;
> +
> +  /* Ask the architecture to create a memory tag section for this particular
> +     memory map entry.  It will be populated with contents later, as we can't
> +     start writing the contents before we have all the sections sorted out.  */
> +  asection *memtag_section
> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
> +
> +  if (memtag_section == nullptr)
> +    {
> +      warning (_("Couldn't make gcore memory tag segment: %s"),
> +	       bfd_errmsg (bfd_get_error ()));
> +      return 1;
> +    }
> +
> +  if (info_verbose)
> +    {
> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
> +			      "at %s\n",
> +		  plongest (bfd_section_size (memtag_section)),
> +		  paddress (target_gdbarch (), vaddr));
> +    }
> +
> +  return 0;
> +}
> +
>   int
>   objfile_find_memory_regions (struct target_ops *self,
>   			     find_memory_region_ftype func, void *obfd)
> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   			   (flags & SEC_READONLY) == 0, /* Writable.  */
>   			   (flags & SEC_CODE) != 0, /* Executable.  */
>   			   1, /* MODIFIED is unknown, pass it as true.  */
> +			   false, /* No memory tags in the object file.  */
>   			   obfd);
>   	    if (ret != 0)
>   	      return ret;
> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Stack section will be writable.  */
>   	     0, /* Stack section will not be executable.  */
>   	     1, /* Stack section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     /* Make a heap segment.  */
> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Heap section will be writable.  */
>   	     0, /* Heap section will not be executable.  */
>   	     1, /* Heap section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     return 0;
> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>       }
>   }
>   
> +/* Callback to copy contents to a particular memory tag section.  */
> +
> +static void
> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> +{
> +  /* We are only interested in "memtag" sections.  */
> +  if (!startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
> +  /* Fill the section with memory tag contents.  */
> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> +    error (_("Failed to fill memory tag section for core file."));
> +}
> +
>   static int
>   gcore_memory_sections (bfd *obfd)
>   {
> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>   	return 0;			/* FIXME: error return/msg?  */
>       }
>   
> +  /* Take care of dumping memory tags, if there are any.  */
> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> +      || gdbarch_find_memory_regions (target_gdbarch (),
> +				      gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +    {
> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +	return 0;
> +    }
> +
>     /* Record phdrs for section-to-segment mapping.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
>       make_output_phdrs (obfd, sect);
>   
> -  /* Copy memory region contents.  */
> +  /* Copy memory region and memory tag contents.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
> -    gcore_copy_callback (obfd, sect);
> +    {
> +      gcore_copy_callback (obfd, sect);
> +      gcore_copy_memtag_section_callback (obfd, sect);
> +    }
>   
>     return 1;
>   }
> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
> index e8f20c83ff0..6fa1b7591db 100644
> --- a/gdb/gdbarch-components.py
> +++ b/gdb/gdbarch-components.py
> @@ -1522,6 +1522,41 @@ Find core file memory regions
>       invalid=True,
>   )
>   
> +Method(
> +    comment="""
> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
> +""",
> +    type="asection *",
> +    name="create_memtag_section",
> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
> +""",
> +    type="bool",
> +    name="fill_memtag_section",
> +    params=[("asection *", "osec")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Decode a memory tag SECTION and return the tags of type TYPE contained in
> +the memory range [ADDRESS, ADDRESS + LENGTH).
> +If no tags were found, return an empty vector.
> +""",
> +    type="gdb::byte_vector",
> +    name="decode_memtag_section",
> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
>   Method(
>       comment="""
>   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index 882b9057b1a..1d19f51f21d 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
> +
> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
> +
> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
> +
> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
> +
> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
> +   the memory range [ADDRESS, ADDRESS + LENGTH).
> +   If no tags were found, return an empty vector. */
> +
> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index a588bdef61a..f5dbacb14e7 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -171,6 +171,9 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_section, has predicate.  */
> +  /* Skip verify of fill_memtag_section, has predicate.  */
> +  /* Skip verify of decode_memtag_section, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     gdb_printf (file,
>                         "gdbarch_dump: find_memory_regions = <%s>\n",
>                         host_address_to_string (gdbarch->find_memory_regions));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
> +                      gdbarch_create_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
> +                      gdbarch_fill_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->fill_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
> +                      gdbarch_decode_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_section));
>     gdb_printf (file,
>                         "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>                         gdbarch_core_xfer_shared_libraries_p (gdbarch));
> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_section != NULL;
> +}
> +
> +asection *
> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
> +}
> +
> +void
> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
> +{
> +  gdbarch->create_memtag_section = create_memtag_section;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->fill_memtag_section != NULL;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
> +  return gdbarch->fill_memtag_section (gdbarch, osec);
> +}
> +
> +void
> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
> +{
> +  gdbarch->fill_memtag_section = fill_memtag_section;
> +}
> +
> +bool
> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_section != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
> +{
> +  gdbarch->decode_memtag_section = decode_memtag_section;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 4e728a06e7e..8a83ed320cf 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -42,6 +42,7 @@
>   #include "gcore.h"
>   #include "gcore-elf.h"
>   #include "solib-svr4.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   #include <unordered_map>
> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>   					    ULONGEST offset, ULONGEST inode,
>   					    int read, int write,
>   					    int exec, int modified,
> +					    bool memory_tagged,
>   					    const char *filename,
>   					    void *data);
>   
> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_section_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>   		map.offset, map.inode, map.read, map.write, map.exec,
>   		1, /* MODIFIED is true because we want to dump
>   		      the mapping.  */
> +		map.vmflags.memory_tagging != 0,
>   		map.filename.c_str (), obfd);
>   	}
>       }
> @@ -1621,12 +1649,14 @@ static int
>   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>   				 ULONGEST offset, ULONGEST inode,
>   				 int read, int write, int exec, int modified,
> +				 bool memory_tagged,
>   				 const char *filename, void *arg)
>   {
>     struct linux_find_memory_regions_data *data
>       = (struct linux_find_memory_regions_data *) arg;
>   
> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
> +		     data->obfd);
>   }
>   
>   /* A variant of linux_find_memory_regions_full that is suitable as the
> @@ -1675,6 +1705,7 @@ static int
>   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>   			      ULONGEST offset, ULONGEST inode,
>   			      int read, int write, int exec, int modified,
> +			      bool memory_tagged,
>   			      const char *filename, void *data)
>   {
>     struct linux_make_mappings_data *map_data
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..af86137c49d
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,61 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 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 "memtag.h"
> +#include "bfd.h"
> +
> +/* See memtag.h */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_section_info &info)
> +{
> +  /* If the caller provided no SECTION to start from, search from the
> +     beginning.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, "memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t memtag_range_size = section->rawsize;
> +      size_t tags_size = bfd_section_size (section);
> +
> +      /* Empty memory range and empty tag dump should not happen.  */
> +      gdb_assert (memtag_range_size != 0);
> +      gdb_assert (tags_size != 0);
> +
> +      CORE_ADDR start_address = bfd_section_vma (section);
> +      CORE_ADDR end_address = start_address + memtag_range_size;
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +      section = bfd_get_next_section_by_name (abfd, section);
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..fe908c1e5e3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,50 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_section_info
> +{
> +  /* The start address of the tagged memory range.  */
> +  CORE_ADDR start_address;
> +  /* The final address of the tagged memory range.  */
> +  CORE_ADDR end_address;
> +  /* The section containing tags for the memory range
> +     [start_address, end_address).  */
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through memory tag sections in a core file.
> +
> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address,
> +				   memtag_section_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..8a19c4b449e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,107 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-06-06  9:28   ` Luis Machado
  2022-06-06  9:42     ` Kuan-Ying Lee
@ 2022-06-06 10:49     ` Eli Zaretskii
  1 sibling, 0 replies; 47+ messages in thread
From: Eli Zaretskii @ 2022-06-06 10:49 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches

> Date: Mon, 6 Jun 2022 10:28:13 +0100
> From: Luis Machado via Gdb-patches <gdb-patches@sourceware.org>
> 
> Ping?

The documentation parts are okay, thanks.

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-06-06  9:47       ` Luis Machado
@ 2022-06-06  9:54         ` Kuan-Ying Lee
  0 siblings, 0 replies; 47+ messages in thread
From: Kuan-Ying Lee @ 2022-06-06  9:54 UTC (permalink / raw)
  To: Luis Machado, gdb-patches

On Mon, 2022-06-06 at 17:47 +0800, Luis Machado wrote:
> On 6/6/22 10:42, Kuan-Ying Lee wrote:
> > On Mon, 2022-06-06 at 17:28 +0800, Luis Machado via Gdb-patches
> > wrote:
> > > Ping?
> > > 
> > 
> > Hi Luis,
> > 
> > I don't see PT_AARCH64_MEMTAG_MTE in include/elf/common.h.
> > 
> > I think we need to add below in include/elf/common.h.
> 
> Based on binutils@ feedback, this constant was moved to
> include/elf/aarch64.h, and is
> part of the binutils patch.

Got it.
Thanks. :)

> 
> > 
> > #define PT_AARCH64_MEMTAG_MTE (PT_LOPROC + 0x2)
> > 
> > Or do I miss something?
> > 
> > Best regards,
> > Kuan-Ying Lee
> > 
> > > On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> > > > v4:
> > > > 
> > > > - Updated documentation (added cross-references).
> > > > - Updated the segment name from PT_ARM_MEMTAG_MTE to
> > > >     PT_AARCH64_MEMTAG_MTE.
> > > > 
> > > > v3:
> > > > 
> > > > - Updated NEWS and documentation to be more thorough.
> > > > 
> > > > v2:
> > > > 
> > > > - Rework memory tag section handling to use generic section
> > > > fields.
> > > > 
> > > > --
> > > > 
> > > > Teach GDB how to dump memory tags for AArch64 when using the
> > > > gcore
> > > > command
> > > > and how to read memory tag data back from a core file generated
> > > > by
> > > > GDB
> > > > (via gcore) or by the Linux kernel.
> > > > 
> > > > The format is documented in the Linux Kernel documentation [1].
> > > > 
> > > > Each tagged memory range (listed in /proc/<pid>/smaps) gets
> > > > dumped
> > > > to its
> > > > own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is
> > > > created for each
> > > > of those segments when reading the core file back.
> > > > 
> > > > To save a little bit of space, given MTE tags only take 4 bits,
> > > > the
> > > > memory tags
> > > > are stored packed as 2 tags per byte.
> > > > 
> > > > When reading the data back, the tags are unpacked.
> > > > 
> > > > I've added a new testcase to exercise the feature.
> > > > 
> > > > Build-tested with --enable-targets=all and regression tested on
> > > > aarch64-linux
> > > > Ubuntu 20.04.
> > > > 
> > > > [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump
> > > > Support)
> > > > ---
> > > >    gdb/Makefile.in                              |   1 +
> > > >    gdb/NEWS                                     |  10 ++
> > > >    gdb/aarch64-linux-tdep.c                     | 167
> > > > +++++++++++++++++++
> > > >    gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
> > > >    gdb/arch/aarch64-mte-linux.h                 |  10 ++
> > > >    gdb/corelow.c                                |  62 +++++++
> > > >    gdb/defs.h                                   |   3 +-
> > > >    gdb/doc/gdb.texinfo                          |  19 +++
> > > >    gdb/gcore.c                                  |  83 ++++++++-
> > > >    gdb/gdbarch-components.py                    |  35 ++++
> > > >    gdb/gdbarch-gen.h                            |  26 +++
> > > >    gdb/gdbarch.c                                |  96
> > > > +++++++++++
> > > >    gdb/linux-tdep.c                             |  39 ++++-
> > > >    gdb/memtag.c                                 |  61 +++++++
> > > >    gdb/memtag.h                                 |  50 ++++++
> > > >    gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93
> > > > +++++++++++
> > > >    gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107
> > > > ++++++++++++
> > > >    17 files changed, 910 insertions(+), 8 deletions(-)
> > > >    create mode 100644 gdb/memtag.c
> > > >    create mode 100644 gdb/memtag.h
> > > >    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-
> > > > gcore.c
> > > >    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-
> > > > gcore.exp
> > > > 
> > > > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> > > > index 418094775a5..fac9364bea4 100644
> > > > --- a/gdb/Makefile.in
> > > > +++ b/gdb/Makefile.in
> > > > @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
> > > >    	memattr.c \
> > > >    	memory-map.c \
> > > >    	memrange.c \
> > > > +	memtag.c \
> > > >    	minidebug.c \
> > > >    	minsyms.c \
> > > >    	mipsread.c \
> > > > diff --git a/gdb/NEWS b/gdb/NEWS
> > > > index 982f4a1a18c..3d925dc3663 100644
> > > > --- a/gdb/NEWS
> > > > +++ b/gdb/NEWS
> > > > @@ -3,6 +3,16 @@
> > > >    
> > > >    *** Changes since GDB 12
> > > >    
> > > > +* GDB now supports dumping memory tag data for AArch64
> > > > MTE.  It
> > > > also supports
> > > > +  reading memory tag data for AArch64 MTE from core files
> > > > generated by
> > > > +  the gcore command or the Linux kernel.
> > > > +
> > > > +  When a process uses memory-mapped pages protected by memory
> > > > tags
> > > > (for
> > > > +  example, AArch64 MTE), this additional information will be
> > > > recorded in
> > > > +  the core file in the event of a crash or if GDB generates a
> > > > core
> > > > file
> > > > +  from the current process state.  GDB will show this
> > > > additional
> > > > information
> > > > +  automatically, or through one of the memory-tag subcommands.
> > > > +
> > > >    * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
> > > >    
> > > >    * Remove support for building against Python 2, it is now
> > > > only
> > > > possible to
> > > > diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-
> > > > tdep.c
> > > > index 55094b3d88b..12d98e71796 100644
> > > > --- a/gdb/aarch64-linux-tdep.c
> > > > +++ b/gdb/aarch64-linux-tdep.c
> > > > @@ -53,6 +53,9 @@
> > > >    
> > > >    #include "gdbsupport/selftest.h"
> > > >    
> > > > +#include "elf/common.h"
> > > > +#include "elf/aarch64.h"
> > > > +
> > > >    /* Signal frame handling.
> > > >    
> > > >          +------------+  ^
> > > > @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info
> > > > (struct
> > > > gdbarch *gdbarch,
> > > >        }
> > > >    }
> > > >    
> > > > +/* AArch64 Linux implementation of the
> > > > gdbarch_create_memtag_section hook.  */
> > > > +
> > > > +static asection *
> > > > +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch,
> > > > bfd
> > > > *obfd,
> > > > +				     CORE_ADDR address, size_t
> > > > size)
> > > > +{
> > > > +  gdb_assert (obfd != nullptr);
> > > > +  gdb_assert (size > 0);
> > > > +
> > > > +  /* Create the section and associated program header.  */
> > > > +  asection *mte_section = bfd_make_section_anyway (obfd,
> > > > "memtag");
> > > > +
> > > > +  if (mte_section == nullptr)
> > > > +    return nullptr;
> > > > +
> > > > +  bfd_set_section_vma (mte_section, address);
> > > > +  /* The size of the memory range covered by the memory
> > > > tags.  We
> > > > reuse the
> > > > +     section's rawsize field for this purpose.  */
> > > > +  mte_section->rawsize = size;
> > > > +  /* Tags are stored packed as 2 tags per byte.  */
> > > > +  bfd_set_section_size (mte_section, (size /
> > > > AARCH64_MTE_GRANULE_SIZE) / 2);
> > > > +  /* Make sure the section's flags has SEC_HAS_CONTENTS,
> > > > otherwise
> > > > BFD will
> > > > +     refuse to write data to this section.  */
> > > > +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> > > > +
> > > > +  /* Store program header information.  */
> > > > +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0,
> > > > 0,
> > > > 1,
> > > > +		   &mte_section);
> > > > +
> > > > +  return mte_section;
> > > > +}
> > > > +
> > > > +/* Maximum number of tags to request.  */
> > > > +#define MAX_TAGS_TO_TRANSFER 1024
> > > > +
> > > > +/* AArch64 Linux implementation of the
> > > > gdbarch_fill_memtag_section
> > > > hook.  */
> > > > +
> > > > +static bool
> > > > +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch,
> > > > asection *osec)
> > > > +{
> > > > +  /* We only handle MTE tags for now.  */
> > > > +
> > > > +  size_t segment_size = osec->rawsize;
> > > > +  CORE_ADDR start_address = bfd_section_vma (osec);
> > > > +  CORE_ADDR end_address = start_address + segment_size;
> > > > +
> > > > +  /* Figure out how many tags we need to store in this memory
> > > > range.  */
> > > > +  size_t granules = aarch64_mte_get_tag_granules
> > > > (start_address,
> > > > segment_size,
> > > > +						  AARCH64_MTE_G
> > > > RANULE_S
> > > > IZE);
> > > > +
> > > > +  /* If there are no tag granules to fetch, just return.  */
> > > > +  if (granules == 0)
> > > > +    return true;
> > > > +
> > > > +  CORE_ADDR address = start_address;
> > > > +
> > > > +  /* Vector of tags.  */
> > > > +  gdb::byte_vector tags;
> > > > +
> > > > +  while (granules > 0)
> > > > +    {
> > > > +      /* Transfer tags in chunks.  */
> > > > +      gdb::byte_vector tags_read;
> > > > +      size_t xfer_len
> > > > +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> > > > +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> > > > +	  granules * AARCH64_MTE_GRANULE_SIZE;
> > > > +
> > > > +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> > > > +				 static_cast<int>
> > > > (memtag_type::allocation)))
> > > > +	{
> > > > +	  warning (_("Failed to read MTE tags from memory range
> > > > [%s,%s)."),
> > > > +		     phex_nz (start_address, sizeof
> > > > (start_address)),
> > > > +		     phex_nz (end_address, sizeof
> > > > (end_address)));
> > > > +	  return false;
> > > > +	}
> > > > +
> > > > +      /* Transfer over the tags that have been read.  */
> > > > +      tags.insert (tags.end (), tags_read.begin (),
> > > > tags_read.end
> > > > ());
> > > > +
> > > > +      /* Adjust the remaining granules and starting
> > > > address.  */
> > > > +      granules -= tags_read.size ();
> > > > +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> > > > +    }
> > > > +
> > > > +  /* Pack the MTE tag bits.  */
> > > > +  aarch64_mte_pack_tags (tags);
> > > > +
> > > > +  if (!bfd_set_section_contents (osec->owner, osec, tags.data
> > > > (),
> > > > +				 0, tags.size ()))
> > > > +    {
> > > > +      warning (_("Failed to write %s bytes of corefile memory
> > > > "
> > > > +		 "tag content (%s)."),
> > > > +	       pulongest (tags.size ()),
> > > > +	       bfd_errmsg (bfd_get_error ()));
> > > > +    }
> > > > +  return true;
> > > > +}
> > > > +
> > > > +/* AArch64 Linux implementation of the
> > > > gdbarch_decode_memtag_section
> > > > +   hook.  Decode a memory tag section and return the requested
> > > > tags.
> > > > +
> > > > +   The section is guaranteed to cover the [ADDRESS, ADDRESS +
> > > > length)
> > > > +   range.  */
> > > > +
> > > > +static gdb::byte_vector
> > > > +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> > > > +				     bfd_section *section,
> > > > +				     int type,
> > > > +				     CORE_ADDR address, size_t
> > > > length)
> > > > +{
> > > > +  gdb_assert (section != nullptr);
> > > > +
> > > > +  /* The requested address must not be less than section-
> > > > >vma.  */
> > > > +  gdb_assert (section->vma <= address);
> > > > +
> > > > +  /* Figure out how many tags we need to fetch in this memory
> > > > range.  */
> > > > +  size_t granules = aarch64_mte_get_tag_granules (address,
> > > > length,
> > > > +						  AARCH64_MTE_G
> > > > RANULE_S
> > > > IZE);
> > > > +  /* Sanity check.  */
> > > > +  gdb_assert (granules > 0);
> > > > +
> > > > +  /* Fetch the total number of tags in the range [VMA, address
> > > > +
> > > > length).  */
> > > > +  size_t granules_from_vma
> > > > +    = aarch64_mte_get_tag_granules (section->vma,
> > > > +				    address - section->vma +
> > > > length,
> > > > +				    AARCH64_MTE_GRANULE_SIZE);
> > > > +
> > > > +  /* Adjust the tags vector to contain the exact number of
> > > > packed
> > > > bytes.  */
> > > > +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> > > > +
> > > > +  /* Figure out the starting offset into the packed tags
> > > > data.  */
> > > > +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> > > > +
> > > > +  if (!bfd_get_section_contents (section->owner, section,
> > > > tags.data (),
> > > > +				 offset, tags.size ()))
> > > > +    error (_("Couldn't read contents from memtag section."));
> > > > +
> > > > +  /* At this point, the tags are packed 2 per byte.  Unpack
> > > > them
> > > > before
> > > > +     returning.  */
> > > > +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> > > > +  aarch64_mte_unpack_tags (tags, skip_first);
> > > > +
> > > > +  /* Resize to the exact number of tags that was
> > > > requested.  */
> > > > +  tags.resize (granules);
> > > > +
> > > > +  return tags;
> > > > +}
> > > > +
> > > >    static void
> > > >    aarch64_linux_init_abi (struct gdbarch_info info, struct
> > > > gdbarch
> > > > *gdbarch)
> > > >    {
> > > > @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct
> > > > gdbarch_info
> > > > info, struct gdbarch *gdbarch)
> > > >    
> > > >          set_gdbarch_report_signal_info (gdbarch,
> > > >    				      aarch64_linux_report_sign
> > > > al_info)
> > > > ;
> > > > +
> > > > +      /* Core file helpers.  */
> > > > +
> > > > +      /* Core file helper to create a memory tag section for a
> > > > particular
> > > > +	 PT_LOAD segment.  */
> > > > +      set_gdbarch_create_memtag_section
> > > > +	(gdbarch, aarch64_linux_create_memtag_section);
> > > > +
> > > > +      /* Core file helper to fill a memory tag section with
> > > > tag
> > > > data.  */
> > > > +      set_gdbarch_fill_memtag_section
> > > > +	(gdbarch, aarch64_linux_fill_memtag_section);
> > > > +
> > > > +      /* Core file helper to decode a memory tag section.  */
> > > > +      set_gdbarch_decode_memtag_section (gdbarch,
> > > > +					 aarch64_linux_decode_m
> > > > emtag_se
> > > > ction);
> > > >        }
> > > >    
> > > >      /* Initialize the aarch64_linux_record_tdep.  */
> > > > diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-
> > > > mte-
> > > > linux.c
> > > > index fc7a8cc00f7..3af6f364e91 100644
> > > > --- a/gdb/arch/aarch64-mte-linux.c
> > > > +++ b/gdb/arch/aarch64-mte-linux.c
> > > > @@ -21,6 +21,62 @@
> > > >    
> > > >    /* See arch/aarch64-mte-linux.h */
> > > >    
> > > > +void
> > > > +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> > > > +{
> > > > +  /* Nothing to pack?  */
> > > > +  if (tags.empty ())
> > > > +    return;
> > > > +
> > > > +  /* If the tags vector has an odd number of elements, add
> > > > another
> > > > +     zeroed-out element to make it even.  This facilitates
> > > > packing.  */
> > > > +  if ((tags.size () % 2) != 0)
> > > > +    tags.emplace_back (0);
> > > > +
> > > > +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> > > > +       unpacked += 2, packed++)
> > > > +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> > > > +
> > > > +  /* Now we have half the size.  */
> > > > +  tags.resize (tags.size () / 2);
> > > > +}
> > > > +
> > > > +/* See arch/aarch64-mte-linux.h */
> > > > +
> > > > +void
> > > > +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool
> > > > skip_first)
> > > > +{
> > > > +  /* Nothing to unpack?  */
> > > > +  if (tags.empty ())
> > > > +    return;
> > > > +
> > > > +  /* An unpacked MTE tags vector will have twice the number of
> > > > elements
> > > > +     compared to an unpacked one.  */
> > > > +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> > > > +
> > > > +  int unpacked = 0, packed = 0;
> > > > +
> > > > +  if (skip_first)
> > > > +    {
> > > > +      /* We are not interested in the first unpacked element,
> > > > just
> > > > discard
> > > > +	 it.  */
> > > > +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> > > > +      unpacked++;
> > > > +      packed++;
> > > > +    }
> > > > +
> > > > +  for (; packed < tags.size (); unpacked += 2, packed++)
> > > > +    {
> > > > +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> > > > +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> > > > +    }
> > > > +
> > > > +  /* Update the original tags vector.  */
> > > > +  tags = std::move (unpacked_tags);
> > > > +}
> > > > +
> > > > +/* See arch/aarch64-mte-linux.h */
> > > > +
> > > >    size_t
> > > >    aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len,
> > > > size_t
> > > > granule_size)
> > > >    {
> > > > diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-
> > > > mte-
> > > > linux.h
> > > > index d158926feff..8a145b447aa 100644
> > > > --- a/gdb/arch/aarch64-mte-linux.h
> > > > +++ b/gdb/arch/aarch64-mte-linux.h
> > > > @@ -32,6 +32,7 @@
> > > >    
> > > >    /* We have one tag per 16 bytes of memory.  */
> > > >    #define AARCH64_MTE_GRANULE_SIZE 16
> > > > +#define AARCH64_MTE_TAG_BIT_SIZE 4
> > > >    #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
> > > >    #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
> > > >    
> > > > @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag
> > > > (CORE_ADDR
> > > > address, CORE_ADDR tag);
> > > >       It is always possible to get the logical tag.  */
> > > >    extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
> > > >    
> > > > +/* Given a TAGS vector containing 1 MTE tag per byte, pack the
> > > > data as
> > > > +   2 tags per byte and resize the vector.  */
> > > > +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> > > > +
> > > > +/* Given a TAGS vector containing 2 MTE tags per byte, unpack
> > > > the
> > > > data as
> > > > +   1 tag per byte and resize the vector.  If SKIP_FIRST is
> > > > TRUE,
> > > > skip the
> > > > +   first unpacked element.  Otherwise leave it in the unpacked
> > > > vector.  */
> > > > +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool
> > > > skip_first);
> > > > +
> > > >    #endif /* ARCH_AARCH64_LINUX_H */
> > > > diff --git a/gdb/corelow.c b/gdb/corelow.c
> > > > index 8c33fb7ebb2..8b8994f80db 100644
> > > > --- a/gdb/corelow.c
> > > > +++ b/gdb/corelow.c
> > > > @@ -52,6 +52,7 @@
> > > >    #include <unordered_set>
> > > >    #include "gdbcmd.h"
> > > >    #include "xml-tdesc.h"
> > > > +#include "memtag.h"
> > > >    
> > > >    #ifndef O_LARGEFILE
> > > >    #define O_LARGEFILE 0
> > > > @@ -101,6 +102,13 @@ class core_target final : public
> > > > process_stratum_target
> > > >    
> > > >      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;
> > > > +
> > > >      /* A few helpers.  */
> > > >    
> > > >      /* Getter, see variable definition.  */
> > > > @@ -1162,6 +1170,60 @@ core_target::info_proc (const char
> > > > *args,
> > > > enum info_proc_what 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 (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)
> > > > +{
> > > > +  struct gdbarch *gdbarch = target_gdbarch ();
> > > > +
> > > > +  /* 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 (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;
> > > > +}
> > > > +
> > > >    /* Get a pointer to the current core target.  If not
> > > > connected to
> > > > a
> > > >       core target, return NULL.  */
> > > >    
> > > > diff --git a/gdb/defs.h b/gdb/defs.h
> > > > index 99bfdd526ff..51a7576a56a 100644
> > > > --- a/gdb/defs.h
> > > > +++ b/gdb/defs.h
> > > > @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
> > > >    
> > > >    typedef int (*find_memory_region_ftype) (CORE_ADDR addr,
> > > > unsigned
> > > > long size,
> > > >    					 int read, int write,
> > > > int exec,
> > > > -					 int modified, void
> > > > *data);
> > > > +					 int modified, bool
> > > > memory_tagged,
> > > > +					 void *data);
> > > >    
> > > >    /* * Possible lvalue types.  Like enum language, this should
> > > > be
> > > > in
> > > >       value.h, but needs to be here for the same reason.  */
> > > > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> > > > index 38ad2ac32b0..36f10f20cfb 100644
> > > > --- a/gdb/doc/gdb.texinfo
> > > > +++ b/gdb/doc/gdb.texinfo
> > > > @@ -25555,6 +25555,25 @@ options that can be controlled at
> > > > runtime
> > > > and emulates the @code{prctl}
> > > >    option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further
> > > > information,
> > > > see the
> > > >    documentation in the Linux kernel.
> > > >    
> > > > +@value{GDBN} supports dumping memory tag data to core files
> > > > through the
> > > > +@command{gcore} command and reading memory tag data from core
> > > > files generated
> > > > +by the @command{gcore} command or the Linux kernel.
> > > > +
> > > > +When a process uses memory-mapped pages protected by memory
> > > > tags
> > > > (for
> > > > +example, AArch64 MTE), this additional information will be
> > > > recorded in
> > > > +the core file in the event of a crash or if @value{GDBN}
> > > > generates
> > > > a core file
> > > > +from the current process state.
> > > > +
> > > > +The memory tag data will be used so developers can display the
> > > > memory
> > > > +tags from a particular memory region (using the @samp{m}
> > > > modifier
> > > > to the
> > > > +@command{x} command, using the @command{print} command or
> > > > using
> > > > the various
> > > > +@command{memory-tag} subcommands.
> > > > +
> > > > +In the case of a crash, @value{GDBN} will attempt to retrieve
> > > > the
> > > > memory tag
> > > > +information automatically from the core file, and will show
> > > > one of
> > > > the above
> > > > +messages depending on whether the synchronous or asynchronous
> > > > mode
> > > > is selected.
> > > > +@xref{Memory Tagging}. @xref{Memory}.
> > > > +
> > > >    @node i386
> > > >    @subsection x86 Architecture-specific Issues
> > > >    
> > > > diff --git a/gdb/gcore.c b/gdb/gcore.c
> > > > index fdb22b72a07..b81ef81ab84 100644
> > > > --- a/gdb/gcore.c
> > > > +++ b/gdb/gcore.c
> > > > @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection
> > > > *osec)
> > > >      int p_flags = 0;
> > > >      int p_type = 0;
> > > >    
> > > > +  /* Memory tag segments have already been handled by the
> > > > architecture, as
> > > > +     those contain arch-specific information.  If we have one
> > > > of
> > > > those, just
> > > > +     return.  */
> > > > +  if (startswith (bfd_section_name (osec), "memtag"))
> > > > +    return;
> > > > +
> > > >      /* FIXME: these constants may only be applicable for
> > > > ELF.  */
> > > >      if (startswith (bfd_section_name (osec), "load"))
> > > >        p_type = PT_LOAD;
> > > > @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection
> > > > *osec)
> > > >    
> > > >    static int
> > > >    gcore_create_callback (CORE_ADDR vaddr, unsigned long size,
> > > > int
> > > > read,
> > > > -		       int write, int exec, int modified, void
> > > > *data)
> > > > +		       int write, int exec, int modified, bool
> > > > memory_tagged,
> > > > +		       void *data)
> > > >    {
> > > >      bfd *obfd = (bfd *) data;
> > > >      asection *osec;
> > > > @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr,
> > > > unsigned long size, int read,
> > > >      return 0;
> > > >    }
> > > >    
> > > > +/* gdbarch_find_memory_region callback for creating a memory
> > > > tag
> > > > section.
> > > > +   DATA is 'bfd *' for the core file GDB is creating.  */
> > > > +
> > > > +static int
> > > > +gcore_create_memtag_section_callback (CORE_ADDR vaddr,
> > > > unsigned
> > > > long size,
> > > > +				      int read, int write, int
> > > > exec,
> > > > +				      int modified, bool
> > > > memory_tagged,
> > > > +				      void *data)
> > > > +{
> > > > +  /* Are there memory tags in this particular memory map
> > > > entry?  */
> > > > +  if (!memory_tagged)
> > > > +    return 0;
> > > > +
> > > > +  bfd *obfd = (bfd *) data;
> > > > +
> > > > +  /* Ask the architecture to create a memory tag section for
> > > > this
> > > > particular
> > > > +     memory map entry.  It will be populated with contents
> > > > later,
> > > > as we can't
> > > > +     start writing the contents before we have all the
> > > > sections
> > > > sorted out.  */
> > > > +  asection *memtag_section
> > > > +    = gdbarch_create_memtag_section (target_gdbarch (), obfd,
> > > > vaddr, size);
> > > > +
> > > > +  if (memtag_section == nullptr)
> > > > +    {
> > > > +      warning (_("Couldn't make gcore memory tag segment:
> > > > %s"),
> > > > +	       bfd_errmsg (bfd_get_error ()));
> > > > +      return 1;
> > > > +    }
> > > > +
> > > > +  if (info_verbose)
> > > > +    {
> > > > +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s
> > > > bytes
> > > > "
> > > > +			      "at %s\n",
> > > > +		  plongest (bfd_section_size (memtag_section)),
> > > > +		  paddress (target_gdbarch (), vaddr));
> > > > +    }
> > > > +
> > > > +  return 0;
> > > > +}
> > > > +
> > > >    int
> > > >    objfile_find_memory_regions (struct target_ops *self,
> > > >    			     find_memory_region_ftype func,
> > > > void *obfd)
> > > > @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct
> > > > target_ops
> > > > *self,
> > > >    			   (flags & SEC_READONLY) == 0, /*
> > > > Writable.  */
> > > >    			   (flags & SEC_CODE) != 0, /*
> > > > Executable.  */
> > > >    			   1, /* MODIFIED is unknown, pass it
> > > > as
> > > > true.  */
> > > > +			   false, /* No memory tags in the
> > > > object
> > > > file.  */
> > > >    			   obfd);
> > > >    	    if (ret != 0)
> > > >    	      return ret;
> > > > @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct
> > > > target_ops
> > > > *self,
> > > >    	     1, /* Stack section will be writable.  */
> > > >    	     0, /* Stack section will not be executable.  */
> > > >    	     1, /* Stack section will be modified.  */
> > > > +	     false, /* No memory tags in the object file.  */
> > > >    	     obfd);
> > > >    
> > > >      /* Make a heap segment.  */
> > > > @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct
> > > > target_ops
> > > > *self,
> > > >    	     1, /* Heap section will be writable.  */
> > > >    	     0, /* Heap section will not be executable.  */
> > > >    	     1, /* Heap section will be modified.  */
> > > > +	     false, /* No memory tags in the object file.  */
> > > >    	     obfd);
> > > >    
> > > >      return 0;
> > > > @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection
> > > > *osec)
> > > >        }
> > > >    }
> > > >    
> > > > +/* Callback to copy contents to a particular memory tag
> > > > section.  */
> > > > +
> > > > +static void
> > > > +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> > > > +{
> > > > +  /* We are only interested in "memtag" sections.  */
> > > > +  if (!startswith (bfd_section_name (osec), "memtag"))
> > > > +    return;
> > > > +
> > > > +  /* Fill the section with memory tag contents.  */
> > > > +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> > > > +    error (_("Failed to fill memory tag section for core
> > > > file."));
> > > > +}
> > > > +
> > > >    static int
> > > >    gcore_memory_sections (bfd *obfd)
> > > >    {
> > > > @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
> > > >    	return 0;			/* FIXME: error
> > > > return/msg?  */
> > > >        }
> > > >    
> > > > +  /* Take care of dumping memory tags, if there are any.  */
> > > > +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> > > > +      || gdbarch_find_memory_regions (target_gdbarch (),
> > > > +				      gcore_create_memtag_secti
> > > > on_callb
> > > > ack,
> > > > +				      obfd) != 0)
> > > > +    {
> > > > +      if (target_find_memory_regions
> > > > (gcore_create_memtag_section_callback,
> > > > +				      obfd) != 0)
> > > > +	return 0;
> > > > +    }
> > > > +
> > > >      /* Record phdrs for section-to-segment mapping.  */
> > > >      for (asection *sect : gdb_bfd_sections (obfd))
> > > >        make_output_phdrs (obfd, sect);
> > > >    
> > > > -  /* Copy memory region contents.  */
> > > > +  /* Copy memory region and memory tag contents.  */
> > > >      for (asection *sect : gdb_bfd_sections (obfd))
> > > > -    gcore_copy_callback (obfd, sect);
> > > > +    {
> > > > +      gcore_copy_callback (obfd, sect);
> > > > +      gcore_copy_memtag_section_callback (obfd, sect);
> > > > +    }
> > > >    
> > > >      return 1;
> > > >    }
> > > > diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-
> > > > components.py
> > > > index e8f20c83ff0..6fa1b7591db 100644
> > > > --- a/gdb/gdbarch-components.py
> > > > +++ b/gdb/gdbarch-components.py
> > > > @@ -1522,6 +1522,41 @@ Find core file memory regions
> > > >        invalid=True,
> > > >    )
> > > >    
> > > > +Method(
> > > > +    comment="""
> > > > +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory
> > > > tag
> > > > section to be dumped to a core file
> > > > +""",
> > > > +    type="asection *",
> > > > +    name="create_memtag_section",
> > > > +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"),
> > > > ("size_t", "size")],
> > > > +    predicate=True,
> > > > +    invalid=True,
> > > > +)
> > > > +
> > > > +Method(
> > > > +    comment="""
> > > > +Given a memory tag section OSEC, fill OSEC's contents with the
> > > > appropriate tag data
> > > > +""",
> > > > +    type="bool",
> > > > +    name="fill_memtag_section",
> > > > +    params=[("asection *", "osec")],
> > > > +    predicate=True,
> > > > +    invalid=True,
> > > > +)
> > > > +
> > > > +Method(
> > > > +    comment="""
> > > > +Decode a memory tag SECTION and return the tags of type TYPE
> > > > contained in
> > > > +the memory range [ADDRESS, ADDRESS + LENGTH).
> > > > +If no tags were found, return an empty vector.
> > > > +""",
> > > > +    type="gdb::byte_vector",
> > > > +    name="decode_memtag_section",
> > > > +    params=[("bfd_section *", "section"), ("int", "type"),
> > > > ("CORE_ADDR", "address"), ("size_t", "length")],
> > > > +    predicate=True,
> > > > +    invalid=True,
> > > > +)
> > > > +
> > > >    Method(
> > > >        comment="""
> > > >    Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted
> > > > shared
> > > > libraries list from
> > > > diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> > > > index 882b9057b1a..1d19f51f21d 100644
> > > > --- a/gdb/gdbarch-gen.h
> > > > +++ b/gdb/gdbarch-gen.h
> > > > @@ -874,6 +874,32 @@ typedef int
> > > > (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch,
> > > > find_m
> > > >    extern int gdbarch_find_memory_regions (struct gdbarch
> > > > *gdbarch,
> > > > find_memory_region_ftype func, void *data);
> > > >    extern void set_gdbarch_find_memory_regions (struct gdbarch
> > > > *gdbarch, gdbarch_find_memory_regions_ftype
> > > > *find_memory_regions);
> > > >    
> > > > +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory
> > > > tag
> > > > section to be dumped to a core file */
> > > > +
> > > > +extern bool gdbarch_create_memtag_section_p (struct gdbarch
> > > > *gdbarch);
> > > > +
> > > > +typedef asection * (gdbarch_create_memtag_section_ftype)
> > > > (struct
> > > > gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> > > > +extern asection * gdbarch_create_memtag_section (struct
> > > > gdbarch
> > > > *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> > > > +extern void set_gdbarch_create_memtag_section (struct gdbarch
> > > > *gdbarch, gdbarch_create_memtag_section_ftype
> > > > *create_memtag_section);
> > > > +
> > > > +/* Given a memory tag section OSEC, fill OSEC's contents with
> > > > the
> > > > appropriate tag data */
> > > > +
> > > > +extern bool gdbarch_fill_memtag_section_p (struct gdbarch
> > > > *gdbarch);
> > > > +
> > > > +typedef bool (gdbarch_fill_memtag_section_ftype) (struct
> > > > gdbarch
> > > > *gdbarch, asection *osec);
> > > > +extern bool gdbarch_fill_memtag_section (struct gdbarch
> > > > *gdbarch,
> > > > asection *osec);
> > > > +extern void set_gdbarch_fill_memtag_section (struct gdbarch
> > > > *gdbarch, gdbarch_fill_memtag_section_ftype
> > > > *fill_memtag_section);
> > > > +
> > > > +/* Decode a memory tag SECTION and return the tags of type
> > > > TYPE
> > > > contained in
> > > > +   the memory range [ADDRESS, ADDRESS + LENGTH).
> > > > +   If no tags were found, return an empty vector. */
> > > > +
> > > > +extern bool gdbarch_decode_memtag_section_p (struct gdbarch
> > > > *gdbarch);
> > > > +
> > > > +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype)
> > > > (struct gdbarch *gdbarch, bfd_section *section, int type,
> > > > CORE_ADDR
> > > > address, size_t length);
> > > > +extern gdb::byte_vector gdbarch_decode_memtag_section (struct
> > > > gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR
> > > > address, size_t length);
> > > > +extern void set_gdbarch_decode_memtag_section (struct gdbarch
> > > > *gdbarch, gdbarch_decode_memtag_section_ftype
> > > > *decode_memtag_section);
> > > > +
> > > >    /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted
> > > > shared
> > > > libraries list from
> > > >       core file into buffer READBUF with length LEN.  Return
> > > > the
> > > > number of bytes read
> > > >       (zero indicates failure).
> > > > diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> > > > index a588bdef61a..f5dbacb14e7 100644
> > > > --- a/gdb/gdbarch.c
> > > > +++ b/gdb/gdbarch.c
> > > > @@ -171,6 +171,9 @@ struct gdbarch
> > > >      gdbarch_iterate_over_regset_sections_ftype
> > > > *iterate_over_regset_sections;
> > > >      gdbarch_make_corefile_notes_ftype *make_corefile_notes;
> > > >      gdbarch_find_memory_regions_ftype *find_memory_regions;
> > > > +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> > > > +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> > > > +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
> > > >      gdbarch_core_xfer_shared_libraries_ftype
> > > > *core_xfer_shared_libraries;
> > > >      gdbarch_core_xfer_shared_libraries_aix_ftype
> > > > *core_xfer_shared_libraries_aix;
> > > >      gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> > > > @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
> > > >      /* Skip verify of iterate_over_regset_sections, has
> > > > predicate.  */
> > > >      /* Skip verify of make_corefile_notes, has predicate.  */
> > > >      /* Skip verify of find_memory_regions, has predicate.  */
> > > > +  /* Skip verify of create_memtag_section, has predicate.  */
> > > > +  /* Skip verify of fill_memtag_section, has predicate.  */
> > > > +  /* Skip verify of decode_memtag_section, has predicate.  */
> > > >      /* Skip verify of core_xfer_shared_libraries, has
> > > > predicate.  */
> > > >      /* Skip verify of core_xfer_shared_libraries_aix, has
> > > > predicate.  */
> > > >      /* Skip verify of core_pid_to_str, has predicate.  */
> > > > @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch,
> > > > struct ui_file *file)
> > > >      gdb_printf (file,
> > > >                          "gdbarch_dump: find_memory_regions =
> > > > <%s>\n",
> > > >                          host_address_to_string (gdbarch-
> > > > > find_memory_regions));
> > > > 
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump:
> > > > gdbarch_create_memtag_section_p() = %d\n",
> > > > +                      gdbarch_create_memtag_section_p
> > > > (gdbarch));
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump: create_memtag_section =
> > > > <%s>\n",
> > > > +                      host_address_to_string (gdbarch-
> > > > > create_memtag_section));
> > > > 
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump:
> > > > gdbarch_fill_memtag_section_p() = %d\n",
> > > > +                      gdbarch_fill_memtag_section_p
> > > > (gdbarch));
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump: fill_memtag_section =
> > > > <%s>\n",
> > > > +                      host_address_to_string (gdbarch-
> > > > > fill_memtag_section));
> > > > 
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump:
> > > > gdbarch_decode_memtag_section_p() = %d\n",
> > > > +                      gdbarch_decode_memtag_section_p
> > > > (gdbarch));
> > > > +  gdb_printf (file,
> > > > +                      "gdbarch_dump: decode_memtag_section =
> > > > <%s>\n",
> > > > +                      host_address_to_string (gdbarch-
> > > > > decode_memtag_section));
> > > > 
> > > >      gdb_printf (file,
> > > >                          "gdbarch_dump:
> > > > gdbarch_core_xfer_shared_libraries_p() = %d\n",
> > > >                          gdbarch_core_xfer_shared_libraries_p
> > > > (gdbarch));
> > > > @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct
> > > > gdbarch *gdbarch,
> > > >      gdbarch->find_memory_regions = find_memory_regions;
> > > >    }
> > > >    
> > > > +bool
> > > > +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  return gdbarch->create_memtag_section != NULL;
> > > > +}
> > > > +
> > > > +asection *
> > > > +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd
> > > > *obfd,
> > > > CORE_ADDR address, size_t size)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  gdb_assert (gdbarch->create_memtag_section != NULL);
> > > > +  if (gdbarch_debug >= 2)
> > > > +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section
> > > > called\n");
> > > > +  return gdbarch->create_memtag_section (gdbarch, obfd,
> > > > address,
> > > > size);
> > > > +}
> > > > +
> > > > +void
> > > > +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> > > > +                                   gdbarch_create_memtag_secti
> > > > on_f
> > > > type create_memtag_section)
> > > > +{
> > > > +  gdbarch->create_memtag_section = create_memtag_section;
> > > > +}
> > > > +
> > > > +bool
> > > > +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  return gdbarch->fill_memtag_section != NULL;
> > > > +}
> > > > +
> > > > +bool
> > > > +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection
> > > > *osec)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> > > > +  if (gdbarch_debug >= 2)
> > > > +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section
> > > > called\n");
> > > > +  return gdbarch->fill_memtag_section (gdbarch, osec);
> > > > +}
> > > > +
> > > > +void
> > > > +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> > > > +                                 gdbarch_fill_memtag_section_f
> > > > type
> > > > fill_memtag_section)
> > > > +{
> > > > +  gdbarch->fill_memtag_section = fill_memtag_section;
> > > > +}
> > > > +
> > > > +bool
> > > > +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  return gdbarch->decode_memtag_section != NULL;
> > > > +}
> > > > +
> > > > +gdb::byte_vector
> > > > +gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> > > > bfd_section *section, int type, CORE_ADDR address, size_t
> > > > length)
> > > > +{
> > > > +  gdb_assert (gdbarch != NULL);
> > > > +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> > > > +  if (gdbarch_debug >= 2)
> > > > +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section
> > > > called\n");
> > > > +  return gdbarch->decode_memtag_section (gdbarch, section,
> > > > type,
> > > > address, length);
> > > > +}
> > > > +
> > > > +void
> > > > +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> > > > +                                   gdbarch_decode_memtag_secti
> > > > on_f
> > > > type decode_memtag_section)
> > > > +{
> > > > +  gdbarch->decode_memtag_section = decode_memtag_section;
> > > > +}
> > > > +
> > > >    bool
> > > >    gdbarch_core_xfer_shared_libraries_p (struct gdbarch
> > > > *gdbarch)
> > > >    {
> > > > diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> > > > index 4e728a06e7e..8a83ed320cf 100644
> > > > --- a/gdb/linux-tdep.c
> > > > +++ b/gdb/linux-tdep.c
> > > > @@ -42,6 +42,7 @@
> > > >    #include "gcore.h"
> > > >    #include "gcore-elf.h"
> > > >    #include "solib-svr4.h"
> > > > +#include "memtag.h"
> > > >    
> > > >    #include <ctype.h>
> > > >    #include <unordered_map>
> > > > @@ -1320,6 +1321,7 @@ typedef int
> > > > linux_find_memory_region_ftype
> > > > (ULONGEST vaddr, ULONGEST size,
> > > >    					    ULONGEST offset,
> > > > ULONGEST
> > > > inode,
> > > >    					    int read, int
> > > > write,
> > > >    					    int exec, int
> > > > modified,
> > > > +					    bool memory_tagged,
> > > >    					    const char
> > > > *filename,
> > > >    					    void *data);
> > > >    
> > > > @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
> > > >      return smaps;
> > > >    }
> > > >    
> > > > -/* See linux-tdep.h.  */
> > > > +/* Helper that checks if an address is in a memory tag page
> > > > for a
> > > > live
> > > > +   process.  */
> > > >    
> > > > -bool
> > > > -linux_address_in_memtag_page (CORE_ADDR address)
> > > > +static bool
> > > > +linux_process_address_in_memtag_page (CORE_ADDR address)
> > > >    {
> > > >      if (current_inferior ()->fake_pid_p)
> > > >        return false;
> > > > @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR
> > > > address)
> > > >      return false;
> > > >    }
> > > >    
> > > > +/* Helper that checks if an address is in a memory tag page
> > > > for a
> > > > core file
> > > > +   process.  */
> > > > +
> > > > +static bool
> > > > +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> > > > +{
> > > > +  if (core_bfd == nullptr)
> > > > +    return false;
> > > > +
> > > > +  memtag_section_info info;
> > > > +  return get_next_core_memtag_section (core_bfd, nullptr,
> > > > address,
> > > > info);
> > > > +}
> > > > +
> > > > +/* See linux-tdep.h.  */
> > > > +
> > > > +bool
> > > > +linux_address_in_memtag_page (CORE_ADDR address)
> > > > +{
> > > > +  if (!target_has_execution ())
> > > > +    return linux_core_file_address_in_memtag_page (address);
> > > > +
> > > > +  return linux_process_address_in_memtag_page (address);
> > > > +}
> > > > +
> > > >    /* List memory regions in the inferior for a corefile.  */
> > > >    
> > > >    static int
> > > > @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct
> > > > gdbarch *gdbarch,
> > > >    		map.offset, map.inode, map.read, map.write,
> > > > map.exec,
> > > >    		1, /* MODIFIED is true because we want to dump
> > > >    		      the mapping.  */
> > > > +		map.vmflags.memory_tagging != 0,
> > > >    		map.filename.c_str (), obfd);
> > > >    	}
> > > >        }
> > > > @@ -1621,12 +1649,14 @@ static int
> > > >    linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST
> > > > size,
> > > >    				 ULONGEST offset, ULONGEST
> > > > inode,
> > > >    				 int read, int write, int exec,
> > > > int
> > > > modified,
> > > > +				 bool memory_tagged,
> > > >    				 const char *filename, void
> > > > *arg)
> > > >    {
> > > >      struct linux_find_memory_regions_data *data
> > > >        = (struct linux_find_memory_regions_data *) arg;
> > > >    
> > > > -  return data->func (vaddr, size, read, write, exec, modified,
> > > > data->obfd);
> > > > +  return data->func (vaddr, size, read, write, exec, modified,
> > > > memory_tagged,
> > > > +		     data->obfd);
> > > >    }
> > > >    
> > > >    /* A variant of linux_find_memory_regions_full that is
> > > > suitable
> > > > as the
> > > > @@ -1675,6 +1705,7 @@ static int
> > > >    linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
> > > >    			      ULONGEST offset, ULONGEST inode,
> > > >    			      int read, int write, int exec,
> > > > int
> > > > modified,
> > > > +			      bool memory_tagged,
> > > >    			      const char *filename, void *data)
> > > >    {
> > > >      struct linux_make_mappings_data *map_data
> > > > diff --git a/gdb/memtag.c b/gdb/memtag.c
> > > > new file mode 100644
> > > > index 00000000000..af86137c49d
> > > > --- /dev/null
> > > > +++ b/gdb/memtag.c
> > > > @@ -0,0 +1,61 @@
> > > > +/* GDB generic memory tagging functions.
> > > > +
> > > > +   Copyright (C) 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 <
> > > > 
https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> > > >   >.  */
> > > > +
> > > > +#include "defs.h"
> > > > +#include "memtag.h"
> > > > +#include "bfd.h"
> > > > +
> > > > +/* See memtag.h */
> > > > +
> > > > +bool
> > > > +get_next_core_memtag_section (bfd *abfd, asection *section,
> > > > +			      CORE_ADDR address,
> > > > memtag_section_info
> > > > &info)
> > > > +{
> > > > +  /* If the caller provided no SECTION to start from, search
> > > > from
> > > > the
> > > > +     beginning.  */
> > > > +  if (section == nullptr)
> > > > +    section = bfd_get_section_by_name (abfd, "memtag");
> > > > +
> > > > +  /* Go through all the memtag sections and figure out if
> > > > ADDRESS
> > > > +     falls within one of the memory ranges that contain
> > > > tags.  */
> > > > +  while (section != nullptr)
> > > > +    {
> > > > +      size_t memtag_range_size = section->rawsize;
> > > > +      size_t tags_size = bfd_section_size (section);
> > > > +
> > > > +      /* Empty memory range and empty tag dump should not
> > > > happen.  */
> > > > +      gdb_assert (memtag_range_size != 0);
> > > > +      gdb_assert (tags_size != 0);
> > > > +
> > > > +      CORE_ADDR start_address = bfd_section_vma (section);
> > > > +      CORE_ADDR end_address = start_address +
> > > > memtag_range_size;
> > > > +
> > > > +      /* Is the address within [start_address,
> > > > end_address)?  */
> > > > +      if (address >= start_address
> > > > +	  && address < end_address)
> > > > +	{
> > > > +	  info.start_address = start_address;
> > > > +	  info.end_address = end_address;
> > > > +	  info.memtag_section = section;
> > > > +	  return true;
> > > > +	}
> > > > +      section = bfd_get_next_section_by_name (abfd, section);
> > > > +    }
> > > > +  return false;
> > > > +}
> > > > diff --git a/gdb/memtag.h b/gdb/memtag.h
> > > > new file mode 100644
> > > > index 00000000000..fe908c1e5e3
> > > > --- /dev/null
> > > > +++ b/gdb/memtag.h
> > > > @@ -0,0 +1,50 @@
> > > > +/* GDB generic memory tagging definitions.
> > > > +   Copyright (C) 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 <
> > > > 
https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> > > >   >.  */
> > > > +
> > > > +#ifndef MEMTAG_H
> > > > +#define MEMTAG_H
> > > > +
> > > > +#include "bfd.h"
> > > > +
> > > > +struct memtag_section_info
> > > > +{
> > > > +  /* The start address of the tagged memory range.  */
> > > > +  CORE_ADDR start_address;
> > > > +  /* The final address of the tagged memory range.  */
> > > > +  CORE_ADDR end_address;
> > > > +  /* The section containing tags for the memory range
> > > > +     [start_address, end_address).  */
> > > > +  asection *memtag_section;
> > > > +};
> > > > +
> > > > +/* Helper function to walk through memory tag sections in a
> > > > core
> > > > file.
> > > > +
> > > > +   Return TRUE if there is a "memtag" section containing
> > > > ADDRESS.  Return FALSE
> > > > +   otherwise.
> > > > +
> > > > +   If SECTION is provided, search from that section onwards.
> > > > If
> > > > SECTION is
> > > > +   nullptr, then start a new search.
> > > > +
> > > > +   If a "memtag" section containing ADDRESS is found, fill
> > > > INFO
> > > > with data
> > > > +   about such section.  Otherwise leave it unchanged.  */
> > > > +
> > > > +bool get_next_core_memtag_section (bfd *abfd, asection
> > > > *section,
> > > > +				   CORE_ADDR address,
> > > > +				   memtag_section_info &info);
> > > > +
> > > > +#endif /* MEMTAG_H */
> > > > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > > > b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > > > new file mode 100644
> > > > index 00000000000..b20ebcff424
> > > > --- /dev/null
> > > > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > > > @@ -0,0 +1,93 @@
> > > > +/* This test program is part of GDB, the GNU debugger.
> > > > +
> > > > +   Copyright 2021 Free Software Foundation, Inc.
> > > > +
> > > > +   This program is free software; you can redistribute it
> > > > and/or
> > > > modify
> > > > +   it under the terms of the GNU General Public License as
> > > > published by
> > > > +   the Free Software Foundation; either version 3 of the
> > > > License,
> > > > or
> > > > +   (at your option) any later version.
> > > > +
> > > > +   This program is distributed in the hope that it will be
> > > > useful,
> > > > +   but WITHOUT ANY WARRANTY; without even the implied warranty
> > > > of
> > > > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
> > > > the
> > > > +   GNU General Public License for more details.
> > > > +
> > > > +   You should have received a copy of the GNU General Public
> > > > License
> > > > +   along with this program.  If not, see <
> > > > 
https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> > > >   >.  */
> > > > +
> > > > +/* Exercise AArch64's Memory Tagging Extension with tagged
> > > > pointers.  */
> > > > +
> > > > +/* This test was based on the documentation for the AArch64
> > > > Memory
> > > > Tagging
> > > > +   Extension from the Linux Kernel, found in the sources in
> > > > +   Documentation/arm64/memory-tagging-extension.rst.  */
> > > > +
> > > > +#include <errno.h>
> > > > +#include <stdio.h>
> > > > +#include <stdlib.h>
> > > > +#include <unistd.h>
> > > > +#include <sys/auxv.h>
> > > > +#include <sys/mman.h>
> > > > +#include <sys/prctl.h>
> > > > +
> > > > +/* From arch/arm64/include/uapi/asm/hwcap.h */
> > > > +#define HWCAP2_MTE              (1 << 18)
> > > > +
> > > > +/* From arch/arm64/include/uapi/asm/mman.h */
> > > > +#define PROT_MTE  0x20
> > > > +
> > > > +/* From include/uapi/linux/prctl.h */
> > > > +#define PR_SET_TAGGED_ADDR_CTRL 55
> > > > +#define PR_GET_TAGGED_ADDR_CTRL 56
> > > > +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> > > > +#define PR_MTE_TCF_SHIFT	1
> > > > +#define PR_MTE_TCF_SYNC		(1UL <<
> > > > PR_MTE_TCF_SHIFT)
> > > > +#define PR_MTE_TAG_SHIFT	3
> > > > +
> > > > +void
> > > > +access_memory (unsigned char *tagged_ptr)
> > > > +{
> > > > +  tagged_ptr[0] = 'a';
> > > > +}
> > > > +
> > > > +int
> > > > +main (int argc, char **argv)
> > > > +{
> > > > +  unsigned char *tagged_ptr;
> > > > +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> > > > +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> > > > +
> > > > +  /* Bail out if MTE is not supported.  */
> > > > +  if (!(hwcap2 & HWCAP2_MTE))
> > > > +    return 1;
> > > > +
> > > > +  /* Enable the tagged address ABI, synchronous MTE tag check
> > > > faults and
> > > > +     allow all non-zero tags in the randomly generated
> > > > set.  */
> > > > +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> > > > +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> > > > +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> > > > +	     0, 0, 0))
> > > > +    {
> > > > +      perror ("prctl () failed");
> > > > +      return 1;
> > > > +    }
> > > > +
> > > > +  /* Create a mapping that will have PROT_MTE set.  */
> > > > +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> > > > +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> > > > +  if (tagged_ptr == MAP_FAILED)
> > > > +    {
> > > > +      perror ("mmap () failed");
> > > > +      return 1;
> > > > +    }
> > > > +
> > > > +  /* Enable MTE on the above anonymous mmap.  */
> > > > +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE |
> > > > PROT_MTE))
> > > > +    {
> > > > +      perror ("mprotect () failed");
> > > > +      return 1;
> > > > +    }
> > > > +
> > > > +  access_memory (tagged_ptr);
> > > > +
> > > > +  return 0;
> > > > +}
> > > > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > > > b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > > > new file mode 100644
> > > > index 00000000000..8a19c4b449e
> > > > --- /dev/null
> > > > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > > > @@ -0,0 +1,107 @@
> > > > +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> > > > +#
> > > > +# This program is free software; you can redistribute it
> > > > and/or
> > > > modify
> > > > +# it under the terms of the GNU General Public License as
> > > > published by
> > > > +# the Free Software Foundation; either version 3 of the
> > > > License,
> > > > or
> > > > +# (at your option) any later version.
> > > > +#
> > > > +# This program is distributed in the hope that it will be
> > > > useful,
> > > > +# but WITHOUT ANY WARRANTY; without even the implied warranty
> > > > of
> > > > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
> > > > the
> > > > +# GNU General Public License for more details.
> > > > +#
> > > > +# You should have received a copy of the GNU General Public
> > > > License
> > > > +# along with this program.  If not, see <
> > > > 
https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> > > >   >.
> > > > +
> > > > +# This file is part of the gdb testsuite.
> > > > +
> > > > +# Test generating and reading a core file with MTE memory
> > > > tags.
> > > > +
> > > > +if {![is_aarch64_target]} {
> > > > +    verbose "Skipping ${gdb_test_file_name}."
> > > > +    return
> > > > +}
> > > > +
> > > > +standard_testfile
> > > > +if { [prepare_for_testing "failed to prepare" ${testfile}
> > > > ${srcfile}] } {
> > > > +    return -1
> > > > +}
> > > > +
> > > > +if ![runto_main] {
> > > > +    untested "could not run to main"
> > > > +    return -1
> > > > +}
> > > > +
> > > > +# Targets that don't support memory tagging should not execute
> > > > the
> > > > +# runtime memory tagging tests.
> > > > +if {![supports_memtag]} {
> > > > +    unsupported "memory tagging unsupported"
> > > > +    return -1
> > > > +}
> > > > +
> > > > +gdb_breakpoint "access_memory"
> > > > +
> > > > +if [gdb_continue "access_memory"] {
> > > > +    return -1
> > > > +}
> > > > +
> > > > +# Set each tag granule to a different tag value, from 0x0 to
> > > > 0xf.
> > > > +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> > > > +for {set i 15} {$i >= 0} {incr i -1} {
> > > > +    set index [expr [expr 15 - $i] * 16]
> > > > +    set tag [format "%02x" $i]
> > > > +    gdb_test "memory-tag set-allocation-tag
> > > > &tagged_ptr\[$index\]
> > > > 1 $tag" \
> > > > +	     $atag_msg \
> > > > +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> > > > +}
> > > > +
> > > > +# Run until a crash and confirm GDB displays memory tag
> > > > violation
> > > > +# information.
> > > > +gdb_test "continue" \
> > > > +    [multi_line \
> > > > +	"Program received signal SIGSEGV, Segmentation fault" \
> > > > +	"Memory tag violation while accessing address $hex" \
> > > > +	"Allocation tag $hex" \
> > > > +	"Logical tag $hex\." \
> > > > +	"$hex in access_memory \\(.*\\) at .*" \
> > > > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > > > +	 "display tag violation information for live process"
> > > > +
> > > > +# Generate the core file.
> > > > +set core_filename [standard_output_file "$testfile.core"]
> > > > +set core_generated [gdb_gcore_cmd "$core_filename" "generate
> > > > core
> > > > file"]
> > > > +
> > > > +if { !$core_generated } {
> > > > +    return -1
> > > > +}
> > > > +
> > > > +clean_restart $binfile
> > > > +
> > > > +# Load the core file and make sure we see the tag violation
> > > > fault
> > > > +# information.
> > > > +gdb_test "core $core_filename" \
> > > > +    [multi_line \
> > > > +	"Core was generated by.*\." \
> > > > +	"Program terminated with signal SIGSEGV, Segmentation
> > > > fault" \
> > > > +	"Memory tag violation while accessing address $hex" \
> > > > +	"Allocation tag 0xf" \
> > > > +	"Logical tag 0x0\." \
> > > > +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> > > > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > > > +	 "core file shows tag violation information"
> > > > +
> > > > +# Make sure we have the tag_ctl register.
> > > > +gdb_test "info register tag_ctl" \
> > > > +	 "tag_ctl.*$hex.*${::decimal}" \
> > > > +	 "tag_ctl is available"
> > > > +
> > > > +# Check if the tag granules have the expected values.  If they
> > > > do,
> > > > that
> > > > +# means the core file saved the tags properly and GDB has read
> > > > them
> > > > +# correctly.
> > > > +for {set i 15} {$i >= 0} {incr i -1} {
> > > > +    set index [expr [expr 15 - $i] * 16]
> > > > +    set tag [format "%x" $i]
> > > > +    gdb_test "memory-tag print-allocation-tag
> > > > &tagged_ptr\[$index\]" \
> > > > +	     "= 0x$tag" \
> > > > +	     "memory tag of &tagged_ptr\[$index\] is correct"
> > > > +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-06-06  9:42     ` Kuan-Ying Lee
@ 2022-06-06  9:47       ` Luis Machado
  2022-06-06  9:54         ` Kuan-Ying Lee
  0 siblings, 1 reply; 47+ messages in thread
From: Luis Machado @ 2022-06-06  9:47 UTC (permalink / raw)
  To: Kuan-Ying Lee, gdb-patches

On 6/6/22 10:42, Kuan-Ying Lee wrote:
> On Mon, 2022-06-06 at 17:28 +0800, Luis Machado via Gdb-patches wrote:
>> Ping?
>>
> 
> Hi Luis,
> 
> I don't see PT_AARCH64_MEMTAG_MTE in include/elf/common.h.
> 
> I think we need to add below in include/elf/common.h.

Based on binutils@ feedback, this constant was moved to include/elf/aarch64.h, and is
part of the binutils patch.

> 
> #define PT_AARCH64_MEMTAG_MTE (PT_LOPROC + 0x2)
> 
> Or do I miss something?
> 
> Best regards,
> Kuan-Ying Lee
> 
>> On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
>>> v4:
>>>
>>> - Updated documentation (added cross-references).
>>> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>>>     PT_AARCH64_MEMTAG_MTE.
>>>
>>> v3:
>>>
>>> - Updated NEWS and documentation to be more thorough.
>>>
>>> v2:
>>>
>>> - Rework memory tag section handling to use generic section fields.
>>>
>>> --
>>>
>>> Teach GDB how to dump memory tags for AArch64 when using the gcore
>>> command
>>> and how to read memory tag data back from a core file generated by
>>> GDB
>>> (via gcore) or by the Linux kernel.
>>>
>>> The format is documented in the Linux Kernel documentation [1].
>>>
>>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped
>>> to its
>>> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is
>>> created for each
>>> of those segments when reading the core file back.
>>>
>>> To save a little bit of space, given MTE tags only take 4 bits, the
>>> memory tags
>>> are stored packed as 2 tags per byte.
>>>
>>> When reading the data back, the tags are unpacked.
>>>
>>> I've added a new testcase to exercise the feature.
>>>
>>> Build-tested with --enable-targets=all and regression tested on
>>> aarch64-linux
>>> Ubuntu 20.04.
>>>
>>> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump
>>> Support)
>>> ---
>>>    gdb/Makefile.in                              |   1 +
>>>    gdb/NEWS                                     |  10 ++
>>>    gdb/aarch64-linux-tdep.c                     | 167
>>> +++++++++++++++++++
>>>    gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>>>    gdb/arch/aarch64-mte-linux.h                 |  10 ++
>>>    gdb/corelow.c                                |  62 +++++++
>>>    gdb/defs.h                                   |   3 +-
>>>    gdb/doc/gdb.texinfo                          |  19 +++
>>>    gdb/gcore.c                                  |  83 ++++++++-
>>>    gdb/gdbarch-components.py                    |  35 ++++
>>>    gdb/gdbarch-gen.h                            |  26 +++
>>>    gdb/gdbarch.c                                |  96 +++++++++++
>>>    gdb/linux-tdep.c                             |  39 ++++-
>>>    gdb/memtag.c                                 |  61 +++++++
>>>    gdb/memtag.h                                 |  50 ++++++
>>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>>>    17 files changed, 910 insertions(+), 8 deletions(-)
>>>    create mode 100644 gdb/memtag.c
>>>    create mode 100644 gdb/memtag.h
>>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>>
>>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>>> index 418094775a5..fac9364bea4 100644
>>> --- a/gdb/Makefile.in
>>> +++ b/gdb/Makefile.in
>>> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>>>    	memattr.c \
>>>    	memory-map.c \
>>>    	memrange.c \
>>> +	memtag.c \
>>>    	minidebug.c \
>>>    	minsyms.c \
>>>    	mipsread.c \
>>> diff --git a/gdb/NEWS b/gdb/NEWS
>>> index 982f4a1a18c..3d925dc3663 100644
>>> --- a/gdb/NEWS
>>> +++ b/gdb/NEWS
>>> @@ -3,6 +3,16 @@
>>>    
>>>    *** Changes since GDB 12
>>>    
>>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It
>>> also supports
>>> +  reading memory tag data for AArch64 MTE from core files
>>> generated by
>>> +  the gcore command or the Linux kernel.
>>> +
>>> +  When a process uses memory-mapped pages protected by memory tags
>>> (for
>>> +  example, AArch64 MTE), this additional information will be
>>> recorded in
>>> +  the core file in the event of a crash or if GDB generates a core
>>> file
>>> +  from the current process state.  GDB will show this additional
>>> information
>>> +  automatically, or through one of the memory-tag subcommands.
>>> +
>>>    * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>>>    
>>>    * Remove support for building against Python 2, it is now only
>>> possible to
>>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>>> index 55094b3d88b..12d98e71796 100644
>>> --- a/gdb/aarch64-linux-tdep.c
>>> +++ b/gdb/aarch64-linux-tdep.c
>>> @@ -53,6 +53,9 @@
>>>    
>>>    #include "gdbsupport/selftest.h"
>>>    
>>> +#include "elf/common.h"
>>> +#include "elf/aarch64.h"
>>> +
>>>    /* Signal frame handling.
>>>    
>>>          +------------+  ^
>>> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct
>>> gdbarch *gdbarch,
>>>        }
>>>    }
>>>    
>>> +/* AArch64 Linux implementation of the
>>> gdbarch_create_memtag_section hook.  */
>>> +
>>> +static asection *
>>> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd
>>> *obfd,
>>> +				     CORE_ADDR address, size_t size)
>>> +{
>>> +  gdb_assert (obfd != nullptr);
>>> +  gdb_assert (size > 0);
>>> +
>>> +  /* Create the section and associated program header.  */
>>> +  asection *mte_section = bfd_make_section_anyway (obfd,
>>> "memtag");
>>> +
>>> +  if (mte_section == nullptr)
>>> +    return nullptr;
>>> +
>>> +  bfd_set_section_vma (mte_section, address);
>>> +  /* The size of the memory range covered by the memory tags.  We
>>> reuse the
>>> +     section's rawsize field for this purpose.  */
>>> +  mte_section->rawsize = size;
>>> +  /* Tags are stored packed as 2 tags per byte.  */
>>> +  bfd_set_section_size (mte_section, (size /
>>> AARCH64_MTE_GRANULE_SIZE) / 2);
>>> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise
>>> BFD will
>>> +     refuse to write data to this section.  */
>>> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
>>> +
>>> +  /* Store program header information.  */
>>> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0,
>>> 1,
>>> +		   &mte_section);
>>> +
>>> +  return mte_section;
>>> +}
>>> +
>>> +/* Maximum number of tags to request.  */
>>> +#define MAX_TAGS_TO_TRANSFER 1024
>>> +
>>> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section
>>> hook.  */
>>> +
>>> +static bool
>>> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch,
>>> asection *osec)
>>> +{
>>> +  /* We only handle MTE tags for now.  */
>>> +
>>> +  size_t segment_size = osec->rawsize;
>>> +  CORE_ADDR start_address = bfd_section_vma (osec);
>>> +  CORE_ADDR end_address = start_address + segment_size;
>>> +
>>> +  /* Figure out how many tags we need to store in this memory
>>> range.  */
>>> +  size_t granules = aarch64_mte_get_tag_granules (start_address,
>>> segment_size,
>>> +						  AARCH64_MTE_GRANULE_S
>>> IZE);
>>> +
>>> +  /* If there are no tag granules to fetch, just return.  */
>>> +  if (granules == 0)
>>> +    return true;
>>> +
>>> +  CORE_ADDR address = start_address;
>>> +
>>> +  /* Vector of tags.  */
>>> +  gdb::byte_vector tags;
>>> +
>>> +  while (granules > 0)
>>> +    {
>>> +      /* Transfer tags in chunks.  */
>>> +      gdb::byte_vector tags_read;
>>> +      size_t xfer_len
>>> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
>>> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>>> +	  granules * AARCH64_MTE_GRANULE_SIZE;
>>> +
>>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>>> +				 static_cast<int>
>>> (memtag_type::allocation)))
>>> +	{
>>> +	  warning (_("Failed to read MTE tags from memory range
>>> [%s,%s)."),
>>> +		     phex_nz (start_address, sizeof (start_address)),
>>> +		     phex_nz (end_address, sizeof (end_address)));
>>> +	  return false;
>>> +	}
>>> +
>>> +      /* Transfer over the tags that have been read.  */
>>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end
>>> ());
>>> +
>>> +      /* Adjust the remaining granules and starting address.  */
>>> +      granules -= tags_read.size ();
>>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>>> +    }
>>> +
>>> +  /* Pack the MTE tag bits.  */
>>> +  aarch64_mte_pack_tags (tags);
>>> +
>>> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
>>> +				 0, tags.size ()))
>>> +    {
>>> +      warning (_("Failed to write %s bytes of corefile memory "
>>> +		 "tag content (%s)."),
>>> +	       pulongest (tags.size ()),
>>> +	       bfd_errmsg (bfd_get_error ()));
>>> +    }
>>> +  return true;
>>> +}
>>> +
>>> +/* AArch64 Linux implementation of the
>>> gdbarch_decode_memtag_section
>>> +   hook.  Decode a memory tag section and return the requested
>>> tags.
>>> +
>>> +   The section is guaranteed to cover the [ADDRESS, ADDRESS +
>>> length)
>>> +   range.  */
>>> +
>>> +static gdb::byte_vector
>>> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
>>> +				     bfd_section *section,
>>> +				     int type,
>>> +				     CORE_ADDR address, size_t length)
>>> +{
>>> +  gdb_assert (section != nullptr);
>>> +
>>> +  /* The requested address must not be less than section->vma.  */
>>> +  gdb_assert (section->vma <= address);
>>> +
>>> +  /* Figure out how many tags we need to fetch in this memory
>>> range.  */
>>> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
>>> +						  AARCH64_MTE_GRANULE_S
>>> IZE);
>>> +  /* Sanity check.  */
>>> +  gdb_assert (granules > 0);
>>> +
>>> +  /* Fetch the total number of tags in the range [VMA, address +
>>> length).  */
>>> +  size_t granules_from_vma
>>> +    = aarch64_mte_get_tag_granules (section->vma,
>>> +				    address - section->vma + length,
>>> +				    AARCH64_MTE_GRANULE_SIZE);
>>> +
>>> +  /* Adjust the tags vector to contain the exact number of packed
>>> bytes.  */
>>> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
>>> +
>>> +  /* Figure out the starting offset into the packed tags data.  */
>>> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
>>> +
>>> +  if (!bfd_get_section_contents (section->owner, section,
>>> tags.data (),
>>> +				 offset, tags.size ()))
>>> +    error (_("Couldn't read contents from memtag section."));
>>> +
>>> +  /* At this point, the tags are packed 2 per byte.  Unpack them
>>> before
>>> +     returning.  */
>>> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
>>> +  aarch64_mte_unpack_tags (tags, skip_first);
>>> +
>>> +  /* Resize to the exact number of tags that was requested.  */
>>> +  tags.resize (granules);
>>> +
>>> +  return tags;
>>> +}
>>> +
>>>    static void
>>>    aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch
>>> *gdbarch)
>>>    {
>>> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info
>>> info, struct gdbarch *gdbarch)
>>>    
>>>          set_gdbarch_report_signal_info (gdbarch,
>>>    				      aarch64_linux_report_signal_info)
>>> ;
>>> +
>>> +      /* Core file helpers.  */
>>> +
>>> +      /* Core file helper to create a memory tag section for a
>>> particular
>>> +	 PT_LOAD segment.  */
>>> +      set_gdbarch_create_memtag_section
>>> +	(gdbarch, aarch64_linux_create_memtag_section);
>>> +
>>> +      /* Core file helper to fill a memory tag section with tag
>>> data.  */
>>> +      set_gdbarch_fill_memtag_section
>>> +	(gdbarch, aarch64_linux_fill_memtag_section);
>>> +
>>> +      /* Core file helper to decode a memory tag section.  */
>>> +      set_gdbarch_decode_memtag_section (gdbarch,
>>> +					 aarch64_linux_decode_memtag_se
>>> ction);
>>>        }
>>>    
>>>      /* Initialize the aarch64_linux_record_tdep.  */
>>> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-
>>> linux.c
>>> index fc7a8cc00f7..3af6f364e91 100644
>>> --- a/gdb/arch/aarch64-mte-linux.c
>>> +++ b/gdb/arch/aarch64-mte-linux.c
>>> @@ -21,6 +21,62 @@
>>>    
>>>    /* See arch/aarch64-mte-linux.h */
>>>    
>>> +void
>>> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
>>> +{
>>> +  /* Nothing to pack?  */
>>> +  if (tags.empty ())
>>> +    return;
>>> +
>>> +  /* If the tags vector has an odd number of elements, add another
>>> +     zeroed-out element to make it even.  This facilitates
>>> packing.  */
>>> +  if ((tags.size () % 2) != 0)
>>> +    tags.emplace_back (0);
>>> +
>>> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
>>> +       unpacked += 2, packed++)
>>> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
>>> +
>>> +  /* Now we have half the size.  */
>>> +  tags.resize (tags.size () / 2);
>>> +}
>>> +
>>> +/* See arch/aarch64-mte-linux.h */
>>> +
>>> +void
>>> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
>>> +{
>>> +  /* Nothing to unpack?  */
>>> +  if (tags.empty ())
>>> +    return;
>>> +
>>> +  /* An unpacked MTE tags vector will have twice the number of
>>> elements
>>> +     compared to an unpacked one.  */
>>> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
>>> +
>>> +  int unpacked = 0, packed = 0;
>>> +
>>> +  if (skip_first)
>>> +    {
>>> +      /* We are not interested in the first unpacked element, just
>>> discard
>>> +	 it.  */
>>> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
>>> +      unpacked++;
>>> +      packed++;
>>> +    }
>>> +
>>> +  for (; packed < tags.size (); unpacked += 2, packed++)
>>> +    {
>>> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
>>> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
>>> +    }
>>> +
>>> +  /* Update the original tags vector.  */
>>> +  tags = std::move (unpacked_tags);
>>> +}
>>> +
>>> +/* See arch/aarch64-mte-linux.h */
>>> +
>>>    size_t
>>>    aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t
>>> granule_size)
>>>    {
>>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-
>>> linux.h
>>> index d158926feff..8a145b447aa 100644
>>> --- a/gdb/arch/aarch64-mte-linux.h
>>> +++ b/gdb/arch/aarch64-mte-linux.h
>>> @@ -32,6 +32,7 @@
>>>    
>>>    /* We have one tag per 16 bytes of memory.  */
>>>    #define AARCH64_MTE_GRANULE_SIZE 16
>>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>>    #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>>    #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>>    
>>> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR
>>> address, CORE_ADDR tag);
>>>       It is always possible to get the logical tag.  */
>>>    extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>>    
>>> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the
>>> data as
>>> +   2 tags per byte and resize the vector.  */
>>> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
>>> +
>>> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the
>>> data as
>>> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE,
>>> skip the
>>> +   first unpacked element.  Otherwise leave it in the unpacked
>>> vector.  */
>>> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool
>>> skip_first);
>>> +
>>>    #endif /* ARCH_AARCH64_LINUX_H */
>>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>>> index 8c33fb7ebb2..8b8994f80db 100644
>>> --- a/gdb/corelow.c
>>> +++ b/gdb/corelow.c
>>> @@ -52,6 +52,7 @@
>>>    #include <unordered_set>
>>>    #include "gdbcmd.h"
>>>    #include "xml-tdesc.h"
>>> +#include "memtag.h"
>>>    
>>>    #ifndef O_LARGEFILE
>>>    #define O_LARGEFILE 0
>>> @@ -101,6 +102,13 @@ class core_target final : public
>>> process_stratum_target
>>>    
>>>      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;
>>> +
>>>      /* A few helpers.  */
>>>    
>>>      /* Getter, see variable definition.  */
>>> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args,
>>> enum info_proc_what 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 (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)
>>> +{
>>> +  struct gdbarch *gdbarch = target_gdbarch ();
>>> +
>>> +  /* 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 (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;
>>> +}
>>> +
>>>    /* Get a pointer to the current core target.  If not connected to
>>> a
>>>       core target, return NULL.  */
>>>    
>>> diff --git a/gdb/defs.h b/gdb/defs.h
>>> index 99bfdd526ff..51a7576a56a 100644
>>> --- a/gdb/defs.h
>>> +++ b/gdb/defs.h
>>> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>>>    
>>>    typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned
>>> long size,
>>>    					 int read, int write, int exec,
>>> -					 int modified, void *data);
>>> +					 int modified, bool
>>> memory_tagged,
>>> +					 void *data);
>>>    
>>>    /* * Possible lvalue types.  Like enum language, this should be
>>> in
>>>       value.h, but needs to be here for the same reason.  */
>>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>>> index 38ad2ac32b0..36f10f20cfb 100644
>>> --- a/gdb/doc/gdb.texinfo
>>> +++ b/gdb/doc/gdb.texinfo
>>> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime
>>> and emulates the @code{prctl}
>>>    option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information,
>>> see the
>>>    documentation in the Linux kernel.
>>>    
>>> +@value{GDBN} supports dumping memory tag data to core files
>>> through the
>>> +@command{gcore} command and reading memory tag data from core
>>> files generated
>>> +by the @command{gcore} command or the Linux kernel.
>>> +
>>> +When a process uses memory-mapped pages protected by memory tags
>>> (for
>>> +example, AArch64 MTE), this additional information will be
>>> recorded in
>>> +the core file in the event of a crash or if @value{GDBN} generates
>>> a core file
>>> +from the current process state.
>>> +
>>> +The memory tag data will be used so developers can display the
>>> memory
>>> +tags from a particular memory region (using the @samp{m} modifier
>>> to the
>>> +@command{x} command, using the @command{print} command or using
>>> the various
>>> +@command{memory-tag} subcommands.
>>> +
>>> +In the case of a crash, @value{GDBN} will attempt to retrieve the
>>> memory tag
>>> +information automatically from the core file, and will show one of
>>> the above
>>> +messages depending on whether the synchronous or asynchronous mode
>>> is selected.
>>> +@xref{Memory Tagging}. @xref{Memory}.
>>> +
>>>    @node i386
>>>    @subsection x86 Architecture-specific Issues
>>>    
>>> diff --git a/gdb/gcore.c b/gdb/gcore.c
>>> index fdb22b72a07..b81ef81ab84 100644
>>> --- a/gdb/gcore.c
>>> +++ b/gdb/gcore.c
>>> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>>      int p_flags = 0;
>>>      int p_type = 0;
>>>    
>>> +  /* Memory tag segments have already been handled by the
>>> architecture, as
>>> +     those contain arch-specific information.  If we have one of
>>> those, just
>>> +     return.  */
>>> +  if (startswith (bfd_section_name (osec), "memtag"))
>>> +    return;
>>> +
>>>      /* FIXME: these constants may only be applicable for ELF.  */
>>>      if (startswith (bfd_section_name (osec), "load"))
>>>        p_type = PT_LOAD;
>>> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>>    
>>>    static int
>>>    gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int
>>> read,
>>> -		       int write, int exec, int modified, void *data)
>>> +		       int write, int exec, int modified, bool
>>> memory_tagged,
>>> +		       void *data)
>>>    {
>>>      bfd *obfd = (bfd *) data;
>>>      asection *osec;
>>> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr,
>>> unsigned long size, int read,
>>>      return 0;
>>>    }
>>>    
>>> +/* gdbarch_find_memory_region callback for creating a memory tag
>>> section.
>>> +   DATA is 'bfd *' for the core file GDB is creating.  */
>>> +
>>> +static int
>>> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned
>>> long size,
>>> +				      int read, int write, int exec,
>>> +				      int modified, bool memory_tagged,
>>> +				      void *data)
>>> +{
>>> +  /* Are there memory tags in this particular memory map
>>> entry?  */
>>> +  if (!memory_tagged)
>>> +    return 0;
>>> +
>>> +  bfd *obfd = (bfd *) data;
>>> +
>>> +  /* Ask the architecture to create a memory tag section for this
>>> particular
>>> +     memory map entry.  It will be populated with contents later,
>>> as we can't
>>> +     start writing the contents before we have all the sections
>>> sorted out.  */
>>> +  asection *memtag_section
>>> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd,
>>> vaddr, size);
>>> +
>>> +  if (memtag_section == nullptr)
>>> +    {
>>> +      warning (_("Couldn't make gcore memory tag segment: %s"),
>>> +	       bfd_errmsg (bfd_get_error ()));
>>> +      return 1;
>>> +    }
>>> +
>>> +  if (info_verbose)
>>> +    {
>>> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes
>>> "
>>> +			      "at %s\n",
>>> +		  plongest (bfd_section_size (memtag_section)),
>>> +		  paddress (target_gdbarch (), vaddr));
>>> +    }
>>> +
>>> +  return 0;
>>> +}
>>> +
>>>    int
>>>    objfile_find_memory_regions (struct target_ops *self,
>>>    			     find_memory_region_ftype func, void *obfd)
>>> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops
>>> *self,
>>>    			   (flags & SEC_READONLY) == 0, /*
>>> Writable.  */
>>>    			   (flags & SEC_CODE) != 0, /* Executable.  */
>>>    			   1, /* MODIFIED is unknown, pass it as
>>> true.  */
>>> +			   false, /* No memory tags in the object
>>> file.  */
>>>    			   obfd);
>>>    	    if (ret != 0)
>>>    	      return ret;
>>> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops
>>> *self,
>>>    	     1, /* Stack section will be writable.  */
>>>    	     0, /* Stack section will not be executable.  */
>>>    	     1, /* Stack section will be modified.  */
>>> +	     false, /* No memory tags in the object file.  */
>>>    	     obfd);
>>>    
>>>      /* Make a heap segment.  */
>>> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops
>>> *self,
>>>    	     1, /* Heap section will be writable.  */
>>>    	     0, /* Heap section will not be executable.  */
>>>    	     1, /* Heap section will be modified.  */
>>> +	     false, /* No memory tags in the object file.  */
>>>    	     obfd);
>>>    
>>>      return 0;
>>> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection
>>> *osec)
>>>        }
>>>    }
>>>    
>>> +/* Callback to copy contents to a particular memory tag
>>> section.  */
>>> +
>>> +static void
>>> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
>>> +{
>>> +  /* We are only interested in "memtag" sections.  */
>>> +  if (!startswith (bfd_section_name (osec), "memtag"))
>>> +    return;
>>> +
>>> +  /* Fill the section with memory tag contents.  */
>>> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
>>> +    error (_("Failed to fill memory tag section for core file."));
>>> +}
>>> +
>>>    static int
>>>    gcore_memory_sections (bfd *obfd)
>>>    {
>>> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>>>    	return 0;			/* FIXME: error return/msg?  */
>>>        }
>>>    
>>> +  /* Take care of dumping memory tags, if there are any.  */
>>> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
>>> +      || gdbarch_find_memory_regions (target_gdbarch (),
>>> +				      gcore_create_memtag_section_callb
>>> ack,
>>> +				      obfd) != 0)
>>> +    {
>>> +      if (target_find_memory_regions
>>> (gcore_create_memtag_section_callback,
>>> +				      obfd) != 0)
>>> +	return 0;
>>> +    }
>>> +
>>>      /* Record phdrs for section-to-segment mapping.  */
>>>      for (asection *sect : gdb_bfd_sections (obfd))
>>>        make_output_phdrs (obfd, sect);
>>>    
>>> -  /* Copy memory region contents.  */
>>> +  /* Copy memory region and memory tag contents.  */
>>>      for (asection *sect : gdb_bfd_sections (obfd))
>>> -    gcore_copy_callback (obfd, sect);
>>> +    {
>>> +      gcore_copy_callback (obfd, sect);
>>> +      gcore_copy_memtag_section_callback (obfd, sect);
>>> +    }
>>>    
>>>      return 1;
>>>    }
>>> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
>>> index e8f20c83ff0..6fa1b7591db 100644
>>> --- a/gdb/gdbarch-components.py
>>> +++ b/gdb/gdbarch-components.py
>>> @@ -1522,6 +1522,41 @@ Find core file memory regions
>>>        invalid=True,
>>>    )
>>>    
>>> +Method(
>>> +    comment="""
>>> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag
>>> section to be dumped to a core file
>>> +""",
>>> +    type="asection *",
>>> +    name="create_memtag_section",
>>> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"),
>>> ("size_t", "size")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>> +Method(
>>> +    comment="""
>>> +Given a memory tag section OSEC, fill OSEC's contents with the
>>> appropriate tag data
>>> +""",
>>> +    type="bool",
>>> +    name="fill_memtag_section",
>>> +    params=[("asection *", "osec")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>> +Method(
>>> +    comment="""
>>> +Decode a memory tag SECTION and return the tags of type TYPE
>>> contained in
>>> +the memory range [ADDRESS, ADDRESS + LENGTH).
>>> +If no tags were found, return an empty vector.
>>> +""",
>>> +    type="gdb::byte_vector",
>>> +    name="decode_memtag_section",
>>> +    params=[("bfd_section *", "section"), ("int", "type"),
>>> ("CORE_ADDR", "address"), ("size_t", "length")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>>    Method(
>>>        comment="""
>>>    Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared
>>> libraries list from
>>> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
>>> index 882b9057b1a..1d19f51f21d 100644
>>> --- a/gdb/gdbarch-gen.h
>>> +++ b/gdb/gdbarch-gen.h
>>> @@ -874,6 +874,32 @@ typedef int
>>> (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch,
>>> find_m
>>>    extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>> find_memory_region_ftype func, void *data);
>>>    extern void set_gdbarch_find_memory_regions (struct gdbarch
>>> *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>>    
>>> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag
>>> section to be dumped to a core file */
>>> +
>>> +extern bool gdbarch_create_memtag_section_p (struct gdbarch
>>> *gdbarch);
>>> +
>>> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct
>>> gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>>> +extern asection * gdbarch_create_memtag_section (struct gdbarch
>>> *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>>> +extern void set_gdbarch_create_memtag_section (struct gdbarch
>>> *gdbarch, gdbarch_create_memtag_section_ftype
>>> *create_memtag_section);
>>> +
>>> +/* Given a memory tag section OSEC, fill OSEC's contents with the
>>> appropriate tag data */
>>> +
>>> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch
>>> *gdbarch);
>>> +
>>> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch
>>> *gdbarch, asection *osec);
>>> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
>>> asection *osec);
>>> +extern void set_gdbarch_fill_memtag_section (struct gdbarch
>>> *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
>>> +
>>> +/* Decode a memory tag SECTION and return the tags of type TYPE
>>> contained in
>>> +   the memory range [ADDRESS, ADDRESS + LENGTH).
>>> +   If no tags were found, return an empty vector. */
>>> +
>>> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch
>>> *gdbarch);
>>> +
>>> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype)
>>> (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR
>>> address, size_t length);
>>> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct
>>> gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR
>>> address, size_t length);
>>> +extern void set_gdbarch_decode_memtag_section (struct gdbarch
>>> *gdbarch, gdbarch_decode_memtag_section_ftype
>>> *decode_memtag_section);
>>> +
>>>    /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared
>>> libraries list from
>>>       core file into buffer READBUF with length LEN.  Return the
>>> number of bytes read
>>>       (zero indicates failure).
>>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>>> index a588bdef61a..f5dbacb14e7 100644
>>> --- a/gdb/gdbarch.c
>>> +++ b/gdb/gdbarch.c
>>> @@ -171,6 +171,9 @@ struct gdbarch
>>>      gdbarch_iterate_over_regset_sections_ftype
>>> *iterate_over_regset_sections;
>>>      gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>>      gdbarch_find_memory_regions_ftype *find_memory_regions;
>>> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
>>> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
>>> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>>>      gdbarch_core_xfer_shared_libraries_ftype
>>> *core_xfer_shared_libraries;
>>>      gdbarch_core_xfer_shared_libraries_aix_ftype
>>> *core_xfer_shared_libraries_aix;
>>>      gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>>> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>>      /* Skip verify of iterate_over_regset_sections, has
>>> predicate.  */
>>>      /* Skip verify of make_corefile_notes, has predicate.  */
>>>      /* Skip verify of find_memory_regions, has predicate.  */
>>> +  /* Skip verify of create_memtag_section, has predicate.  */
>>> +  /* Skip verify of fill_memtag_section, has predicate.  */
>>> +  /* Skip verify of decode_memtag_section, has predicate.  */
>>>      /* Skip verify of core_xfer_shared_libraries, has
>>> predicate.  */
>>>      /* Skip verify of core_xfer_shared_libraries_aix, has
>>> predicate.  */
>>>      /* Skip verify of core_pid_to_str, has predicate.  */
>>> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch,
>>> struct ui_file *file)
>>>      gdb_printf (file,
>>>                          "gdbarch_dump: find_memory_regions =
>>> <%s>\n",
>>>                          host_address_to_string (gdbarch-
>>>> find_memory_regions));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump:
>>> gdbarch_create_memtag_section_p() = %d\n",
>>> +                      gdbarch_create_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: create_memtag_section =
>>> <%s>\n",
>>> +                      host_address_to_string (gdbarch-
>>>> create_memtag_section));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump:
>>> gdbarch_fill_memtag_section_p() = %d\n",
>>> +                      gdbarch_fill_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: fill_memtag_section =
>>> <%s>\n",
>>> +                      host_address_to_string (gdbarch-
>>>> fill_memtag_section));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump:
>>> gdbarch_decode_memtag_section_p() = %d\n",
>>> +                      gdbarch_decode_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: decode_memtag_section =
>>> <%s>\n",
>>> +                      host_address_to_string (gdbarch-
>>>> decode_memtag_section));
>>>      gdb_printf (file,
>>>                          "gdbarch_dump:
>>> gdbarch_core_xfer_shared_libraries_p() = %d\n",
>>>                          gdbarch_core_xfer_shared_libraries_p
>>> (gdbarch));
>>> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct
>>> gdbarch *gdbarch,
>>>      gdbarch->find_memory_regions = find_memory_regions;
>>>    }
>>>    
>>> +bool
>>> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->create_memtag_section != NULL;
>>> +}
>>> +
>>> +asection *
>>> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
>>> CORE_ADDR address, size_t size)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->create_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section
>>> called\n");
>>> +  return gdbarch->create_memtag_section (gdbarch, obfd, address,
>>> size);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
>>> +                                   gdbarch_create_memtag_section_f
>>> type create_memtag_section)
>>> +{
>>> +  gdbarch->create_memtag_section = create_memtag_section;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->fill_memtag_section != NULL;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection
>>> *osec)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section
>>> called\n");
>>> +  return gdbarch->fill_memtag_section (gdbarch, osec);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
>>> +                                 gdbarch_fill_memtag_section_ftype
>>> fill_memtag_section)
>>> +{
>>> +  gdbarch->fill_memtag_section = fill_memtag_section;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->decode_memtag_section != NULL;
>>> +}
>>> +
>>> +gdb::byte_vector
>>> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
>>> bfd_section *section, int type, CORE_ADDR address, size_t length)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section
>>> called\n");
>>> +  return gdbarch->decode_memtag_section (gdbarch, section, type,
>>> address, length);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
>>> +                                   gdbarch_decode_memtag_section_f
>>> type decode_memtag_section)
>>> +{
>>> +  gdbarch->decode_memtag_section = decode_memtag_section;
>>> +}
>>> +
>>>    bool
>>>    gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>>    {
>>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>>> index 4e728a06e7e..8a83ed320cf 100644
>>> --- a/gdb/linux-tdep.c
>>> +++ b/gdb/linux-tdep.c
>>> @@ -42,6 +42,7 @@
>>>    #include "gcore.h"
>>>    #include "gcore-elf.h"
>>>    #include "solib-svr4.h"
>>> +#include "memtag.h"
>>>    
>>>    #include <ctype.h>
>>>    #include <unordered_map>
>>> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype
>>> (ULONGEST vaddr, ULONGEST size,
>>>    					    ULONGEST offset, ULONGEST
>>> inode,
>>>    					    int read, int write,
>>>    					    int exec, int modified,
>>> +					    bool memory_tagged,
>>>    					    const char *filename,
>>>    					    void *data);
>>>    
>>> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>>>      return smaps;
>>>    }
>>>    
>>> -/* See linux-tdep.h.  */
>>> +/* Helper that checks if an address is in a memory tag page for a
>>> live
>>> +   process.  */
>>>    
>>> -bool
>>> -linux_address_in_memtag_page (CORE_ADDR address)
>>> +static bool
>>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>>    {
>>>      if (current_inferior ()->fake_pid_p)
>>>        return false;
>>> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR
>>> address)
>>>      return false;
>>>    }
>>>    
>>> +/* Helper that checks if an address is in a memory tag page for a
>>> core file
>>> +   process.  */
>>> +
>>> +static bool
>>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>>> +{
>>> +  if (core_bfd == nullptr)
>>> +    return false;
>>> +
>>> +  memtag_section_info info;
>>> +  return get_next_core_memtag_section (core_bfd, nullptr, address,
>>> info);
>>> +}
>>> +
>>> +/* See linux-tdep.h.  */
>>> +
>>> +bool
>>> +linux_address_in_memtag_page (CORE_ADDR address)
>>> +{
>>> +  if (!target_has_execution ())
>>> +    return linux_core_file_address_in_memtag_page (address);
>>> +
>>> +  return linux_process_address_in_memtag_page (address);
>>> +}
>>> +
>>>    /* List memory regions in the inferior for a corefile.  */
>>>    
>>>    static int
>>> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct
>>> gdbarch *gdbarch,
>>>    		map.offset, map.inode, map.read, map.write, map.exec,
>>>    		1, /* MODIFIED is true because we want to dump
>>>    		      the mapping.  */
>>> +		map.vmflags.memory_tagging != 0,
>>>    		map.filename.c_str (), obfd);
>>>    	}
>>>        }
>>> @@ -1621,12 +1649,14 @@ static int
>>>    linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>>>    				 ULONGEST offset, ULONGEST inode,
>>>    				 int read, int write, int exec, int
>>> modified,
>>> +				 bool memory_tagged,
>>>    				 const char *filename, void *arg)
>>>    {
>>>      struct linux_find_memory_regions_data *data
>>>        = (struct linux_find_memory_regions_data *) arg;
>>>    
>>> -  return data->func (vaddr, size, read, write, exec, modified,
>>> data->obfd);
>>> +  return data->func (vaddr, size, read, write, exec, modified,
>>> memory_tagged,
>>> +		     data->obfd);
>>>    }
>>>    
>>>    /* A variant of linux_find_memory_regions_full that is suitable
>>> as the
>>> @@ -1675,6 +1705,7 @@ static int
>>>    linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>>>    			      ULONGEST offset, ULONGEST inode,
>>>    			      int read, int write, int exec, int
>>> modified,
>>> +			      bool memory_tagged,
>>>    			      const char *filename, void *data)
>>>    {
>>>      struct linux_make_mappings_data *map_data
>>> diff --git a/gdb/memtag.c b/gdb/memtag.c
>>> new file mode 100644
>>> index 00000000000..af86137c49d
>>> --- /dev/null
>>> +++ b/gdb/memtag.c
>>> @@ -0,0 +1,61 @@
>>> +/* GDB generic memory tagging functions.
>>> +
>>> +   Copyright (C) 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 <
>>> https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
>>>   >.  */
>>> +
>>> +#include "defs.h"
>>> +#include "memtag.h"
>>> +#include "bfd.h"
>>> +
>>> +/* See memtag.h */
>>> +
>>> +bool
>>> +get_next_core_memtag_section (bfd *abfd, asection *section,
>>> +			      CORE_ADDR address, memtag_section_info
>>> &info)
>>> +{
>>> +  /* If the caller provided no SECTION to start from, search from
>>> the
>>> +     beginning.  */
>>> +  if (section == nullptr)
>>> +    section = bfd_get_section_by_name (abfd, "memtag");
>>> +
>>> +  /* Go through all the memtag sections and figure out if ADDRESS
>>> +     falls within one of the memory ranges that contain tags.  */
>>> +  while (section != nullptr)
>>> +    {
>>> +      size_t memtag_range_size = section->rawsize;
>>> +      size_t tags_size = bfd_section_size (section);
>>> +
>>> +      /* Empty memory range and empty tag dump should not
>>> happen.  */
>>> +      gdb_assert (memtag_range_size != 0);
>>> +      gdb_assert (tags_size != 0);
>>> +
>>> +      CORE_ADDR start_address = bfd_section_vma (section);
>>> +      CORE_ADDR end_address = start_address + memtag_range_size;
>>> +
>>> +      /* Is the address within [start_address, end_address)?  */
>>> +      if (address >= start_address
>>> +	  && address < end_address)
>>> +	{
>>> +	  info.start_address = start_address;
>>> +	  info.end_address = end_address;
>>> +	  info.memtag_section = section;
>>> +	  return true;
>>> +	}
>>> +      section = bfd_get_next_section_by_name (abfd, section);
>>> +    }
>>> +  return false;
>>> +}
>>> diff --git a/gdb/memtag.h b/gdb/memtag.h
>>> new file mode 100644
>>> index 00000000000..fe908c1e5e3
>>> --- /dev/null
>>> +++ b/gdb/memtag.h
>>> @@ -0,0 +1,50 @@
>>> +/* GDB generic memory tagging definitions.
>>> +   Copyright (C) 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 <
>>> https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
>>>   >.  */
>>> +
>>> +#ifndef MEMTAG_H
>>> +#define MEMTAG_H
>>> +
>>> +#include "bfd.h"
>>> +
>>> +struct memtag_section_info
>>> +{
>>> +  /* The start address of the tagged memory range.  */
>>> +  CORE_ADDR start_address;
>>> +  /* The final address of the tagged memory range.  */
>>> +  CORE_ADDR end_address;
>>> +  /* The section containing tags for the memory range
>>> +     [start_address, end_address).  */
>>> +  asection *memtag_section;
>>> +};
>>> +
>>> +/* Helper function to walk through memory tag sections in a core
>>> file.
>>> +
>>> +   Return TRUE if there is a "memtag" section containing
>>> ADDRESS.  Return FALSE
>>> +   otherwise.
>>> +
>>> +   If SECTION is provided, search from that section onwards. If
>>> SECTION is
>>> +   nullptr, then start a new search.
>>> +
>>> +   If a "memtag" section containing ADDRESS is found, fill INFO
>>> with data
>>> +   about such section.  Otherwise leave it unchanged.  */
>>> +
>>> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
>>> +				   CORE_ADDR address,
>>> +				   memtag_section_info &info);
>>> +
>>> +#endif /* MEMTAG_H */
>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>> b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>> new file mode 100644
>>> index 00000000000..b20ebcff424
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>> @@ -0,0 +1,93 @@
>>> +/* This test program is part of GDB, the GNU debugger.
>>> +
>>> +   Copyright 2021 Free Software Foundation, Inc.
>>> +
>>> +   This program is free software; you can redistribute it and/or
>>> modify
>>> +   it under the terms of the GNU General Public License as
>>> published by
>>> +   the Free Software Foundation; either version 3 of the License,
>>> or
>>> +   (at your option) any later version.
>>> +
>>> +   This program is distributed in the hope that it will be useful,
>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> +   GNU General Public License for more details.
>>> +
>>> +   You should have received a copy of the GNU General Public
>>> License
>>> +   along with this program.  If not, see <
>>> https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
>>>   >.  */
>>> +
>>> +/* Exercise AArch64's Memory Tagging Extension with tagged
>>> pointers.  */
>>> +
>>> +/* This test was based on the documentation for the AArch64 Memory
>>> Tagging
>>> +   Extension from the Linux Kernel, found in the sources in
>>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>>> +
>>> +#include <errno.h>
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <unistd.h>
>>> +#include <sys/auxv.h>
>>> +#include <sys/mman.h>
>>> +#include <sys/prctl.h>
>>> +
>>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>>> +#define HWCAP2_MTE              (1 << 18)
>>> +
>>> +/* From arch/arm64/include/uapi/asm/mman.h */
>>> +#define PROT_MTE  0x20
>>> +
>>> +/* From include/uapi/linux/prctl.h */
>>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>>> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
>>> +#define PR_MTE_TCF_SHIFT	1
>>> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
>>> +#define PR_MTE_TAG_SHIFT	3
>>> +
>>> +void
>>> +access_memory (unsigned char *tagged_ptr)
>>> +{
>>> +  tagged_ptr[0] = 'a';
>>> +}
>>> +
>>> +int
>>> +main (int argc, char **argv)
>>> +{
>>> +  unsigned char *tagged_ptr;
>>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>>> +
>>> +  /* Bail out if MTE is not supported.  */
>>> +  if (!(hwcap2 & HWCAP2_MTE))
>>> +    return 1;
>>> +
>>> +  /* Enable the tagged address ABI, synchronous MTE tag check
>>> faults and
>>> +     allow all non-zero tags in the randomly generated set.  */
>>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>>> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>>> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
>>> +	     0, 0, 0))
>>> +    {
>>> +      perror ("prctl () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  /* Create a mapping that will have PROT_MTE set.  */
>>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>>> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>>> +  if (tagged_ptr == MAP_FAILED)
>>> +    {
>>> +      perror ("mmap () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  /* Enable MTE on the above anonymous mmap.  */
>>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE |
>>> PROT_MTE))
>>> +    {
>>> +      perror ("mprotect () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  access_memory (tagged_ptr);
>>> +
>>> +  return 0;
>>> +}
>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>> b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>> new file mode 100644
>>> index 00000000000..8a19c4b449e
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>> @@ -0,0 +1,107 @@
>>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>>> +#
>>> +# This program is free software; you can redistribute it and/or
>>> modify
>>> +# it under the terms of the GNU General Public License as
>>> published by
>>> +# the Free Software Foundation; either version 3 of the License,
>>> or
>>> +# (at your option) any later version.
>>> +#
>>> +# This program is distributed in the hope that it will be useful,
>>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> +# GNU General Public License for more details.
>>> +#
>>> +# You should have received a copy of the GNU General Public
>>> License
>>> +# along with this program.  If not, see <
>>> https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
>>>   >.
>>> +
>>> +# This file is part of the gdb testsuite.
>>> +
>>> +# Test generating and reading a core file with MTE memory tags.
>>> +
>>> +if {![is_aarch64_target]} {
>>> +    verbose "Skipping ${gdb_test_file_name}."
>>> +    return
>>> +}
>>> +
>>> +standard_testfile
>>> +if { [prepare_for_testing "failed to prepare" ${testfile}
>>> ${srcfile}] } {
>>> +    return -1
>>> +}
>>> +
>>> +if ![runto_main] {
>>> +    untested "could not run to main"
>>> +    return -1
>>> +}
>>> +
>>> +# Targets that don't support memory tagging should not execute the
>>> +# runtime memory tagging tests.
>>> +if {![supports_memtag]} {
>>> +    unsupported "memory tagging unsupported"
>>> +    return -1
>>> +}
>>> +
>>> +gdb_breakpoint "access_memory"
>>> +
>>> +if [gdb_continue "access_memory"] {
>>> +    return -1
>>> +}
>>> +
>>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>> +    set index [expr [expr 15 - $i] * 16]
>>> +    set tag [format "%02x" $i]
>>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\]
>>> 1 $tag" \
>>> +	     $atag_msg \
>>> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
>>> +}
>>> +
>>> +# Run until a crash and confirm GDB displays memory tag violation
>>> +# information.
>>> +gdb_test "continue" \
>>> +    [multi_line \
>>> +	"Program received signal SIGSEGV, Segmentation fault" \
>>> +	"Memory tag violation while accessing address $hex" \
>>> +	"Allocation tag $hex" \
>>> +	"Logical tag $hex\." \
>>> +	"$hex in access_memory \\(.*\\) at .*" \
>>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>>> +	 "display tag violation information for live process"
>>> +
>>> +# Generate the core file.
>>> +set core_filename [standard_output_file "$testfile.core"]
>>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core
>>> file"]
>>> +
>>> +if { !$core_generated } {
>>> +    return -1
>>> +}
>>> +
>>> +clean_restart $binfile
>>> +
>>> +# Load the core file and make sure we see the tag violation fault
>>> +# information.
>>> +gdb_test "core $core_filename" \
>>> +    [multi_line \
>>> +	"Core was generated by.*\." \
>>> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
>>> +	"Memory tag violation while accessing address $hex" \
>>> +	"Allocation tag 0xf" \
>>> +	"Logical tag 0x0\." \
>>> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
>>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>>> +	 "core file shows tag violation information"
>>> +
>>> +# Make sure we have the tag_ctl register.
>>> +gdb_test "info register tag_ctl" \
>>> +	 "tag_ctl.*$hex.*${::decimal}" \
>>> +	 "tag_ctl is available"
>>> +
>>> +# Check if the tag granules have the expected values.  If they do,
>>> that
>>> +# means the core file saved the tags properly and GDB has read
>>> them
>>> +# correctly.
>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>> +    set index [expr [expr 15 - $i] * 16]
>>> +    set tag [format "%x" $i]
>>> +    gdb_test "memory-tag print-allocation-tag
>>> &tagged_ptr\[$index\]" \
>>> +	     "= 0x$tag" \
>>> +	     "memory tag of &tagged_ptr\[$index\] is correct"
>>> +}
> 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-06-06  9:28   ` Luis Machado
@ 2022-06-06  9:42     ` Kuan-Ying Lee
  2022-06-06  9:47       ` Luis Machado
  2022-06-06 10:49     ` Eli Zaretskii
  1 sibling, 1 reply; 47+ messages in thread
From: Kuan-Ying Lee @ 2022-06-06  9:42 UTC (permalink / raw)
  To: Luis Machado, gdb-patches, kuan-ying.lee

On Mon, 2022-06-06 at 17:28 +0800, Luis Machado via Gdb-patches wrote:
> Ping?
> 

Hi Luis,

I don't see PT_AARCH64_MEMTAG_MTE in include/elf/common.h.

I think we need to add below in include/elf/common.h.

#define PT_AARCH64_MEMTAG_MTE (PT_LOPROC + 0x2)

Or do I miss something?

Best regards,
Kuan-Ying Lee

> On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> > v4:
> > 
> > - Updated documentation (added cross-references).
> > - Updated the segment name from PT_ARM_MEMTAG_MTE to
> >    PT_AARCH64_MEMTAG_MTE.
> > 
> > v3:
> > 
> > - Updated NEWS and documentation to be more thorough.
> > 
> > v2:
> > 
> > - Rework memory tag section handling to use generic section fields.
> > 
> > --
> > 
> > Teach GDB how to dump memory tags for AArch64 when using the gcore
> > command
> > and how to read memory tag data back from a core file generated by
> > GDB
> > (via gcore) or by the Linux kernel.
> > 
> > The format is documented in the Linux Kernel documentation [1].
> > 
> > Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped
> > to its
> > own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is
> > created for each
> > of those segments when reading the core file back.
> > 
> > To save a little bit of space, given MTE tags only take 4 bits, the
> > memory tags
> > are stored packed as 2 tags per byte.
> > 
> > When reading the data back, the tags are unpacked.
> > 
> > I've added a new testcase to exercise the feature.
> > 
> > Build-tested with --enable-targets=all and regression tested on
> > aarch64-linux
> > Ubuntu 20.04.
> > 
> > [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump
> > Support)
> > ---
> >   gdb/Makefile.in                              |   1 +
> >   gdb/NEWS                                     |  10 ++
> >   gdb/aarch64-linux-tdep.c                     | 167
> > +++++++++++++++++++
> >   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
> >   gdb/arch/aarch64-mte-linux.h                 |  10 ++
> >   gdb/corelow.c                                |  62 +++++++
> >   gdb/defs.h                                   |   3 +-
> >   gdb/doc/gdb.texinfo                          |  19 +++
> >   gdb/gcore.c                                  |  83 ++++++++-
> >   gdb/gdbarch-components.py                    |  35 ++++
> >   gdb/gdbarch-gen.h                            |  26 +++
> >   gdb/gdbarch.c                                |  96 +++++++++++
> >   gdb/linux-tdep.c                             |  39 ++++-
> >   gdb/memtag.c                                 |  61 +++++++
> >   gdb/memtag.h                                 |  50 ++++++
> >   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
> >   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
> >   17 files changed, 910 insertions(+), 8 deletions(-)
> >   create mode 100644 gdb/memtag.c
> >   create mode 100644 gdb/memtag.h
> >   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> >   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > 
> > diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> > index 418094775a5..fac9364bea4 100644
> > --- a/gdb/Makefile.in
> > +++ b/gdb/Makefile.in
> > @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
> >   	memattr.c \
> >   	memory-map.c \
> >   	memrange.c \
> > +	memtag.c \
> >   	minidebug.c \
> >   	minsyms.c \
> >   	mipsread.c \
> > diff --git a/gdb/NEWS b/gdb/NEWS
> > index 982f4a1a18c..3d925dc3663 100644
> > --- a/gdb/NEWS
> > +++ b/gdb/NEWS
> > @@ -3,6 +3,16 @@
> >   
> >   *** Changes since GDB 12
> >   
> > +* GDB now supports dumping memory tag data for AArch64 MTE.  It
> > also supports
> > +  reading memory tag data for AArch64 MTE from core files
> > generated by
> > +  the gcore command or the Linux kernel.
> > +
> > +  When a process uses memory-mapped pages protected by memory tags
> > (for
> > +  example, AArch64 MTE), this additional information will be
> > recorded in
> > +  the core file in the event of a crash or if GDB generates a core
> > file
> > +  from the current process state.  GDB will show this additional
> > information
> > +  automatically, or through one of the memory-tag subcommands.
> > +
> >   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
> >   
> >   * Remove support for building against Python 2, it is now only
> > possible to
> > diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> > index 55094b3d88b..12d98e71796 100644
> > --- a/gdb/aarch64-linux-tdep.c
> > +++ b/gdb/aarch64-linux-tdep.c
> > @@ -53,6 +53,9 @@
> >   
> >   #include "gdbsupport/selftest.h"
> >   
> > +#include "elf/common.h"
> > +#include "elf/aarch64.h"
> > +
> >   /* Signal frame handling.
> >   
> >         +------------+  ^
> > @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct
> > gdbarch *gdbarch,
> >       }
> >   }
> >   
> > +/* AArch64 Linux implementation of the
> > gdbarch_create_memtag_section hook.  */
> > +
> > +static asection *
> > +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd
> > *obfd,
> > +				     CORE_ADDR address, size_t size)
> > +{
> > +  gdb_assert (obfd != nullptr);
> > +  gdb_assert (size > 0);
> > +
> > +  /* Create the section and associated program header.  */
> > +  asection *mte_section = bfd_make_section_anyway (obfd,
> > "memtag");
> > +
> > +  if (mte_section == nullptr)
> > +    return nullptr;
> > +
> > +  bfd_set_section_vma (mte_section, address);
> > +  /* The size of the memory range covered by the memory tags.  We
> > reuse the
> > +     section's rawsize field for this purpose.  */
> > +  mte_section->rawsize = size;
> > +  /* Tags are stored packed as 2 tags per byte.  */
> > +  bfd_set_section_size (mte_section, (size /
> > AARCH64_MTE_GRANULE_SIZE) / 2);
> > +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise
> > BFD will
> > +     refuse to write data to this section.  */
> > +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> > +
> > +  /* Store program header information.  */
> > +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0,
> > 1,
> > +		   &mte_section);
> > +
> > +  return mte_section;
> > +}
> > +
> > +/* Maximum number of tags to request.  */
> > +#define MAX_TAGS_TO_TRANSFER 1024
> > +
> > +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section
> > hook.  */
> > +
> > +static bool
> > +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch,
> > asection *osec)
> > +{
> > +  /* We only handle MTE tags for now.  */
> > +
> > +  size_t segment_size = osec->rawsize;
> > +  CORE_ADDR start_address = bfd_section_vma (osec);
> > +  CORE_ADDR end_address = start_address + segment_size;
> > +
> > +  /* Figure out how many tags we need to store in this memory
> > range.  */
> > +  size_t granules = aarch64_mte_get_tag_granules (start_address,
> > segment_size,
> > +						  AARCH64_MTE_GRANULE_S
> > IZE);
> > +
> > +  /* If there are no tag granules to fetch, just return.  */
> > +  if (granules == 0)
> > +    return true;
> > +
> > +  CORE_ADDR address = start_address;
> > +
> > +  /* Vector of tags.  */
> > +  gdb::byte_vector tags;
> > +
> > +  while (granules > 0)
> > +    {
> > +      /* Transfer tags in chunks.  */
> > +      gdb::byte_vector tags_read;
> > +      size_t xfer_len
> > +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> > +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> > +	  granules * AARCH64_MTE_GRANULE_SIZE;
> > +
> > +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> > +				 static_cast<int>
> > (memtag_type::allocation)))
> > +	{
> > +	  warning (_("Failed to read MTE tags from memory range
> > [%s,%s)."),
> > +		     phex_nz (start_address, sizeof (start_address)),
> > +		     phex_nz (end_address, sizeof (end_address)));
> > +	  return false;
> > +	}
> > +
> > +      /* Transfer over the tags that have been read.  */
> > +      tags.insert (tags.end (), tags_read.begin (), tags_read.end
> > ());
> > +
> > +      /* Adjust the remaining granules and starting address.  */
> > +      granules -= tags_read.size ();
> > +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> > +    }
> > +
> > +  /* Pack the MTE tag bits.  */
> > +  aarch64_mte_pack_tags (tags);
> > +
> > +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> > +				 0, tags.size ()))
> > +    {
> > +      warning (_("Failed to write %s bytes of corefile memory "
> > +		 "tag content (%s)."),
> > +	       pulongest (tags.size ()),
> > +	       bfd_errmsg (bfd_get_error ()));
> > +    }
> > +  return true;
> > +}
> > +
> > +/* AArch64 Linux implementation of the
> > gdbarch_decode_memtag_section
> > +   hook.  Decode a memory tag section and return the requested
> > tags.
> > +
> > +   The section is guaranteed to cover the [ADDRESS, ADDRESS +
> > length)
> > +   range.  */
> > +
> > +static gdb::byte_vector
> > +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> > +				     bfd_section *section,
> > +				     int type,
> > +				     CORE_ADDR address, size_t length)
> > +{
> > +  gdb_assert (section != nullptr);
> > +
> > +  /* The requested address must not be less than section->vma.  */
> > +  gdb_assert (section->vma <= address);
> > +
> > +  /* Figure out how many tags we need to fetch in this memory
> > range.  */
> > +  size_t granules = aarch64_mte_get_tag_granules (address, length,
> > +						  AARCH64_MTE_GRANULE_S
> > IZE);
> > +  /* Sanity check.  */
> > +  gdb_assert (granules > 0);
> > +
> > +  /* Fetch the total number of tags in the range [VMA, address +
> > length).  */
> > +  size_t granules_from_vma
> > +    = aarch64_mte_get_tag_granules (section->vma,
> > +				    address - section->vma + length,
> > +				    AARCH64_MTE_GRANULE_SIZE);
> > +
> > +  /* Adjust the tags vector to contain the exact number of packed
> > bytes.  */
> > +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> > +
> > +  /* Figure out the starting offset into the packed tags data.  */
> > +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> > +
> > +  if (!bfd_get_section_contents (section->owner, section,
> > tags.data (),
> > +				 offset, tags.size ()))
> > +    error (_("Couldn't read contents from memtag section."));
> > +
> > +  /* At this point, the tags are packed 2 per byte.  Unpack them
> > before
> > +     returning.  */
> > +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> > +  aarch64_mte_unpack_tags (tags, skip_first);
> > +
> > +  /* Resize to the exact number of tags that was requested.  */
> > +  tags.resize (granules);
> > +
> > +  return tags;
> > +}
> > +
> >   static void
> >   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch
> > *gdbarch)
> >   {
> > @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info
> > info, struct gdbarch *gdbarch)
> >   
> >         set_gdbarch_report_signal_info (gdbarch,
> >   				      aarch64_linux_report_signal_info)
> > ;
> > +
> > +      /* Core file helpers.  */
> > +
> > +      /* Core file helper to create a memory tag section for a
> > particular
> > +	 PT_LOAD segment.  */
> > +      set_gdbarch_create_memtag_section
> > +	(gdbarch, aarch64_linux_create_memtag_section);
> > +
> > +      /* Core file helper to fill a memory tag section with tag
> > data.  */
> > +      set_gdbarch_fill_memtag_section
> > +	(gdbarch, aarch64_linux_fill_memtag_section);
> > +
> > +      /* Core file helper to decode a memory tag section.  */
> > +      set_gdbarch_decode_memtag_section (gdbarch,
> > +					 aarch64_linux_decode_memtag_se
> > ction);
> >       }
> >   
> >     /* Initialize the aarch64_linux_record_tdep.  */
> > diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-
> > linux.c
> > index fc7a8cc00f7..3af6f364e91 100644
> > --- a/gdb/arch/aarch64-mte-linux.c
> > +++ b/gdb/arch/aarch64-mte-linux.c
> > @@ -21,6 +21,62 @@
> >   
> >   /* See arch/aarch64-mte-linux.h */
> >   
> > +void
> > +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> > +{
> > +  /* Nothing to pack?  */
> > +  if (tags.empty ())
> > +    return;
> > +
> > +  /* If the tags vector has an odd number of elements, add another
> > +     zeroed-out element to make it even.  This facilitates
> > packing.  */
> > +  if ((tags.size () % 2) != 0)
> > +    tags.emplace_back (0);
> > +
> > +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> > +       unpacked += 2, packed++)
> > +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> > +
> > +  /* Now we have half the size.  */
> > +  tags.resize (tags.size () / 2);
> > +}
> > +
> > +/* See arch/aarch64-mte-linux.h */
> > +
> > +void
> > +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
> > +{
> > +  /* Nothing to unpack?  */
> > +  if (tags.empty ())
> > +    return;
> > +
> > +  /* An unpacked MTE tags vector will have twice the number of
> > elements
> > +     compared to an unpacked one.  */
> > +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> > +
> > +  int unpacked = 0, packed = 0;
> > +
> > +  if (skip_first)
> > +    {
> > +      /* We are not interested in the first unpacked element, just
> > discard
> > +	 it.  */
> > +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> > +      unpacked++;
> > +      packed++;
> > +    }
> > +
> > +  for (; packed < tags.size (); unpacked += 2, packed++)
> > +    {
> > +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> > +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> > +    }
> > +
> > +  /* Update the original tags vector.  */
> > +  tags = std::move (unpacked_tags);
> > +}
> > +
> > +/* See arch/aarch64-mte-linux.h */
> > +
> >   size_t
> >   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t
> > granule_size)
> >   {
> > diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-
> > linux.h
> > index d158926feff..8a145b447aa 100644
> > --- a/gdb/arch/aarch64-mte-linux.h
> > +++ b/gdb/arch/aarch64-mte-linux.h
> > @@ -32,6 +32,7 @@
> >   
> >   /* We have one tag per 16 bytes of memory.  */
> >   #define AARCH64_MTE_GRANULE_SIZE 16
> > +#define AARCH64_MTE_TAG_BIT_SIZE 4
> >   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
> >   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
> >   
> > @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR
> > address, CORE_ADDR tag);
> >      It is always possible to get the logical tag.  */
> >   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
> >   
> > +/* Given a TAGS vector containing 1 MTE tag per byte, pack the
> > data as
> > +   2 tags per byte and resize the vector.  */
> > +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> > +
> > +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the
> > data as
> > +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE,
> > skip the
> > +   first unpacked element.  Otherwise leave it in the unpacked
> > vector.  */
> > +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool
> > skip_first);
> > +
> >   #endif /* ARCH_AARCH64_LINUX_H */
> > diff --git a/gdb/corelow.c b/gdb/corelow.c
> > index 8c33fb7ebb2..8b8994f80db 100644
> > --- a/gdb/corelow.c
> > +++ b/gdb/corelow.c
> > @@ -52,6 +52,7 @@
> >   #include <unordered_set>
> >   #include "gdbcmd.h"
> >   #include "xml-tdesc.h"
> > +#include "memtag.h"
> >   
> >   #ifndef O_LARGEFILE
> >   #define O_LARGEFILE 0
> > @@ -101,6 +102,13 @@ class core_target final : public
> > process_stratum_target
> >   
> >     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;
> > +
> >     /* A few helpers.  */
> >   
> >     /* Getter, see variable definition.  */
> > @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args,
> > enum info_proc_what 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 (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)
> > +{
> > +  struct gdbarch *gdbarch = target_gdbarch ();
> > +
> > +  /* 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 (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;
> > +}
> > +
> >   /* Get a pointer to the current core target.  If not connected to
> > a
> >      core target, return NULL.  */
> >   
> > diff --git a/gdb/defs.h b/gdb/defs.h
> > index 99bfdd526ff..51a7576a56a 100644
> > --- a/gdb/defs.h
> > +++ b/gdb/defs.h
> > @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
> >   
> >   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned
> > long size,
> >   					 int read, int write, int exec,
> > -					 int modified, void *data);
> > +					 int modified, bool
> > memory_tagged,
> > +					 void *data);
> >   
> >   /* * Possible lvalue types.  Like enum language, this should be
> > in
> >      value.h, but needs to be here for the same reason.  */
> > diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> > index 38ad2ac32b0..36f10f20cfb 100644
> > --- a/gdb/doc/gdb.texinfo
> > +++ b/gdb/doc/gdb.texinfo
> > @@ -25555,6 +25555,25 @@ options that can be controlled at runtime
> > and emulates the @code{prctl}
> >   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information,
> > see the
> >   documentation in the Linux kernel.
> >   
> > +@value{GDBN} supports dumping memory tag data to core files
> > through the
> > +@command{gcore} command and reading memory tag data from core
> > files generated
> > +by the @command{gcore} command or the Linux kernel.
> > +
> > +When a process uses memory-mapped pages protected by memory tags
> > (for
> > +example, AArch64 MTE), this additional information will be
> > recorded in
> > +the core file in the event of a crash or if @value{GDBN} generates
> > a core file
> > +from the current process state.
> > +
> > +The memory tag data will be used so developers can display the
> > memory
> > +tags from a particular memory region (using the @samp{m} modifier
> > to the
> > +@command{x} command, using the @command{print} command or using
> > the various
> > +@command{memory-tag} subcommands.
> > +
> > +In the case of a crash, @value{GDBN} will attempt to retrieve the
> > memory tag
> > +information automatically from the core file, and will show one of
> > the above
> > +messages depending on whether the synchronous or asynchronous mode
> > is selected.
> > +@xref{Memory Tagging}. @xref{Memory}.
> > +
> >   @node i386
> >   @subsection x86 Architecture-specific Issues
> >   
> > diff --git a/gdb/gcore.c b/gdb/gcore.c
> > index fdb22b72a07..b81ef81ab84 100644
> > --- a/gdb/gcore.c
> > +++ b/gdb/gcore.c
> > @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
> >     int p_flags = 0;
> >     int p_type = 0;
> >   
> > +  /* Memory tag segments have already been handled by the
> > architecture, as
> > +     those contain arch-specific information.  If we have one of
> > those, just
> > +     return.  */
> > +  if (startswith (bfd_section_name (osec), "memtag"))
> > +    return;
> > +
> >     /* FIXME: these constants may only be applicable for ELF.  */
> >     if (startswith (bfd_section_name (osec), "load"))
> >       p_type = PT_LOAD;
> > @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
> >   
> >   static int
> >   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int
> > read,
> > -		       int write, int exec, int modified, void *data)
> > +		       int write, int exec, int modified, bool
> > memory_tagged,
> > +		       void *data)
> >   {
> >     bfd *obfd = (bfd *) data;
> >     asection *osec;
> > @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr,
> > unsigned long size, int read,
> >     return 0;
> >   }
> >   
> > +/* gdbarch_find_memory_region callback for creating a memory tag
> > section.
> > +   DATA is 'bfd *' for the core file GDB is creating.  */
> > +
> > +static int
> > +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned
> > long size,
> > +				      int read, int write, int exec,
> > +				      int modified, bool memory_tagged,
> > +				      void *data)
> > +{
> > +  /* Are there memory tags in this particular memory map
> > entry?  */
> > +  if (!memory_tagged)
> > +    return 0;
> > +
> > +  bfd *obfd = (bfd *) data;
> > +
> > +  /* Ask the architecture to create a memory tag section for this
> > particular
> > +     memory map entry.  It will be populated with contents later,
> > as we can't
> > +     start writing the contents before we have all the sections
> > sorted out.  */
> > +  asection *memtag_section
> > +    = gdbarch_create_memtag_section (target_gdbarch (), obfd,
> > vaddr, size);
> > +
> > +  if (memtag_section == nullptr)
> > +    {
> > +      warning (_("Couldn't make gcore memory tag segment: %s"),
> > +	       bfd_errmsg (bfd_get_error ()));
> > +      return 1;
> > +    }
> > +
> > +  if (info_verbose)
> > +    {
> > +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes
> > "
> > +			      "at %s\n",
> > +		  plongest (bfd_section_size (memtag_section)),
> > +		  paddress (target_gdbarch (), vaddr));
> > +    }
> > +
> > +  return 0;
> > +}
> > +
> >   int
> >   objfile_find_memory_regions (struct target_ops *self,
> >   			     find_memory_region_ftype func, void *obfd)
> > @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops
> > *self,
> >   			   (flags & SEC_READONLY) == 0, /*
> > Writable.  */
> >   			   (flags & SEC_CODE) != 0, /* Executable.  */
> >   			   1, /* MODIFIED is unknown, pass it as
> > true.  */
> > +			   false, /* No memory tags in the object
> > file.  */
> >   			   obfd);
> >   	    if (ret != 0)
> >   	      return ret;
> > @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops
> > *self,
> >   	     1, /* Stack section will be writable.  */
> >   	     0, /* Stack section will not be executable.  */
> >   	     1, /* Stack section will be modified.  */
> > +	     false, /* No memory tags in the object file.  */
> >   	     obfd);
> >   
> >     /* Make a heap segment.  */
> > @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops
> > *self,
> >   	     1, /* Heap section will be writable.  */
> >   	     0, /* Heap section will not be executable.  */
> >   	     1, /* Heap section will be modified.  */
> > +	     false, /* No memory tags in the object file.  */
> >   	     obfd);
> >   
> >     return 0;
> > @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection
> > *osec)
> >       }
> >   }
> >   
> > +/* Callback to copy contents to a particular memory tag
> > section.  */
> > +
> > +static void
> > +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> > +{
> > +  /* We are only interested in "memtag" sections.  */
> > +  if (!startswith (bfd_section_name (osec), "memtag"))
> > +    return;
> > +
> > +  /* Fill the section with memory tag contents.  */
> > +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> > +    error (_("Failed to fill memory tag section for core file."));
> > +}
> > +
> >   static int
> >   gcore_memory_sections (bfd *obfd)
> >   {
> > @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
> >   	return 0;			/* FIXME: error return/msg?  */
> >       }
> >   
> > +  /* Take care of dumping memory tags, if there are any.  */
> > +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> > +      || gdbarch_find_memory_regions (target_gdbarch (),
> > +				      gcore_create_memtag_section_callb
> > ack,
> > +				      obfd) != 0)
> > +    {
> > +      if (target_find_memory_regions
> > (gcore_create_memtag_section_callback,
> > +				      obfd) != 0)
> > +	return 0;
> > +    }
> > +
> >     /* Record phdrs for section-to-segment mapping.  */
> >     for (asection *sect : gdb_bfd_sections (obfd))
> >       make_output_phdrs (obfd, sect);
> >   
> > -  /* Copy memory region contents.  */
> > +  /* Copy memory region and memory tag contents.  */
> >     for (asection *sect : gdb_bfd_sections (obfd))
> > -    gcore_copy_callback (obfd, sect);
> > +    {
> > +      gcore_copy_callback (obfd, sect);
> > +      gcore_copy_memtag_section_callback (obfd, sect);
> > +    }
> >   
> >     return 1;
> >   }
> > diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
> > index e8f20c83ff0..6fa1b7591db 100644
> > --- a/gdb/gdbarch-components.py
> > +++ b/gdb/gdbarch-components.py
> > @@ -1522,6 +1522,41 @@ Find core file memory regions
> >       invalid=True,
> >   )
> >   
> > +Method(
> > +    comment="""
> > +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag
> > section to be dumped to a core file
> > +""",
> > +    type="asection *",
> > +    name="create_memtag_section",
> > +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"),
> > ("size_t", "size")],
> > +    predicate=True,
> > +    invalid=True,
> > +)
> > +
> > +Method(
> > +    comment="""
> > +Given a memory tag section OSEC, fill OSEC's contents with the
> > appropriate tag data
> > +""",
> > +    type="bool",
> > +    name="fill_memtag_section",
> > +    params=[("asection *", "osec")],
> > +    predicate=True,
> > +    invalid=True,
> > +)
> > +
> > +Method(
> > +    comment="""
> > +Decode a memory tag SECTION and return the tags of type TYPE
> > contained in
> > +the memory range [ADDRESS, ADDRESS + LENGTH).
> > +If no tags were found, return an empty vector.
> > +""",
> > +    type="gdb::byte_vector",
> > +    name="decode_memtag_section",
> > +    params=[("bfd_section *", "section"), ("int", "type"),
> > ("CORE_ADDR", "address"), ("size_t", "length")],
> > +    predicate=True,
> > +    invalid=True,
> > +)
> > +
> >   Method(
> >       comment="""
> >   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared
> > libraries list from
> > diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> > index 882b9057b1a..1d19f51f21d 100644
> > --- a/gdb/gdbarch-gen.h
> > +++ b/gdb/gdbarch-gen.h
> > @@ -874,6 +874,32 @@ typedef int
> > (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch,
> > find_m
> >   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch,
> > find_memory_region_ftype func, void *data);
> >   extern void set_gdbarch_find_memory_regions (struct gdbarch
> > *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
> >   
> > +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag
> > section to be dumped to a core file */
> > +
> > +extern bool gdbarch_create_memtag_section_p (struct gdbarch
> > *gdbarch);
> > +
> > +typedef asection * (gdbarch_create_memtag_section_ftype) (struct
> > gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> > +extern asection * gdbarch_create_memtag_section (struct gdbarch
> > *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> > +extern void set_gdbarch_create_memtag_section (struct gdbarch
> > *gdbarch, gdbarch_create_memtag_section_ftype
> > *create_memtag_section);
> > +
> > +/* Given a memory tag section OSEC, fill OSEC's contents with the
> > appropriate tag data */
> > +
> > +extern bool gdbarch_fill_memtag_section_p (struct gdbarch
> > *gdbarch);
> > +
> > +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch
> > *gdbarch, asection *osec);
> > +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> > asection *osec);
> > +extern void set_gdbarch_fill_memtag_section (struct gdbarch
> > *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
> > +
> > +/* Decode a memory tag SECTION and return the tags of type TYPE
> > contained in
> > +   the memory range [ADDRESS, ADDRESS + LENGTH).
> > +   If no tags were found, return an empty vector. */
> > +
> > +extern bool gdbarch_decode_memtag_section_p (struct gdbarch
> > *gdbarch);
> > +
> > +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype)
> > (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR
> > address, size_t length);
> > +extern gdb::byte_vector gdbarch_decode_memtag_section (struct
> > gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR
> > address, size_t length);
> > +extern void set_gdbarch_decode_memtag_section (struct gdbarch
> > *gdbarch, gdbarch_decode_memtag_section_ftype
> > *decode_memtag_section);
> > +
> >   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared
> > libraries list from
> >      core file into buffer READBUF with length LEN.  Return the
> > number of bytes read
> >      (zero indicates failure).
> > diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> > index a588bdef61a..f5dbacb14e7 100644
> > --- a/gdb/gdbarch.c
> > +++ b/gdb/gdbarch.c
> > @@ -171,6 +171,9 @@ struct gdbarch
> >     gdbarch_iterate_over_regset_sections_ftype
> > *iterate_over_regset_sections;
> >     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
> >     gdbarch_find_memory_regions_ftype *find_memory_regions;
> > +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> > +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> > +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
> >     gdbarch_core_xfer_shared_libraries_ftype
> > *core_xfer_shared_libraries;
> >     gdbarch_core_xfer_shared_libraries_aix_ftype
> > *core_xfer_shared_libraries_aix;
> >     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> > @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
> >     /* Skip verify of iterate_over_regset_sections, has
> > predicate.  */
> >     /* Skip verify of make_corefile_notes, has predicate.  */
> >     /* Skip verify of find_memory_regions, has predicate.  */
> > +  /* Skip verify of create_memtag_section, has predicate.  */
> > +  /* Skip verify of fill_memtag_section, has predicate.  */
> > +  /* Skip verify of decode_memtag_section, has predicate.  */
> >     /* Skip verify of core_xfer_shared_libraries, has
> > predicate.  */
> >     /* Skip verify of core_xfer_shared_libraries_aix, has
> > predicate.  */
> >     /* Skip verify of core_pid_to_str, has predicate.  */
> > @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch,
> > struct ui_file *file)
> >     gdb_printf (file,
> >                         "gdbarch_dump: find_memory_regions =
> > <%s>\n",
> >                         host_address_to_string (gdbarch-
> > >find_memory_regions));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump:
> > gdbarch_create_memtag_section_p() = %d\n",
> > +                      gdbarch_create_memtag_section_p (gdbarch));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump: create_memtag_section =
> > <%s>\n",
> > +                      host_address_to_string (gdbarch-
> > >create_memtag_section));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump:
> > gdbarch_fill_memtag_section_p() = %d\n",
> > +                      gdbarch_fill_memtag_section_p (gdbarch));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump: fill_memtag_section =
> > <%s>\n",
> > +                      host_address_to_string (gdbarch-
> > >fill_memtag_section));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump:
> > gdbarch_decode_memtag_section_p() = %d\n",
> > +                      gdbarch_decode_memtag_section_p (gdbarch));
> > +  gdb_printf (file,
> > +                      "gdbarch_dump: decode_memtag_section =
> > <%s>\n",
> > +                      host_address_to_string (gdbarch-
> > >decode_memtag_section));
> >     gdb_printf (file,
> >                         "gdbarch_dump:
> > gdbarch_core_xfer_shared_libraries_p() = %d\n",
> >                         gdbarch_core_xfer_shared_libraries_p
> > (gdbarch));
> > @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct
> > gdbarch *gdbarch,
> >     gdbarch->find_memory_regions = find_memory_regions;
> >   }
> >   
> > +bool
> > +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  return gdbarch->create_memtag_section != NULL;
> > +}
> > +
> > +asection *
> > +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> > CORE_ADDR address, size_t size)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  gdb_assert (gdbarch->create_memtag_section != NULL);
> > +  if (gdbarch_debug >= 2)
> > +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section
> > called\n");
> > +  return gdbarch->create_memtag_section (gdbarch, obfd, address,
> > size);
> > +}
> > +
> > +void
> > +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> > +                                   gdbarch_create_memtag_section_f
> > type create_memtag_section)
> > +{
> > +  gdbarch->create_memtag_section = create_memtag_section;
> > +}
> > +
> > +bool
> > +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  return gdbarch->fill_memtag_section != NULL;
> > +}
> > +
> > +bool
> > +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection
> > *osec)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> > +  if (gdbarch_debug >= 2)
> > +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section
> > called\n");
> > +  return gdbarch->fill_memtag_section (gdbarch, osec);
> > +}
> > +
> > +void
> > +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> > +                                 gdbarch_fill_memtag_section_ftype
> > fill_memtag_section)
> > +{
> > +  gdbarch->fill_memtag_section = fill_memtag_section;
> > +}
> > +
> > +bool
> > +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  return gdbarch->decode_memtag_section != NULL;
> > +}
> > +
> > +gdb::byte_vector
> > +gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> > bfd_section *section, int type, CORE_ADDR address, size_t length)
> > +{
> > +  gdb_assert (gdbarch != NULL);
> > +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> > +  if (gdbarch_debug >= 2)
> > +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section
> > called\n");
> > +  return gdbarch->decode_memtag_section (gdbarch, section, type,
> > address, length);
> > +}
> > +
> > +void
> > +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> > +                                   gdbarch_decode_memtag_section_f
> > type decode_memtag_section)
> > +{
> > +  gdbarch->decode_memtag_section = decode_memtag_section;
> > +}
> > +
> >   bool
> >   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
> >   {
> > diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> > index 4e728a06e7e..8a83ed320cf 100644
> > --- a/gdb/linux-tdep.c
> > +++ b/gdb/linux-tdep.c
> > @@ -42,6 +42,7 @@
> >   #include "gcore.h"
> >   #include "gcore-elf.h"
> >   #include "solib-svr4.h"
> > +#include "memtag.h"
> >   
> >   #include <ctype.h>
> >   #include <unordered_map>
> > @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype
> > (ULONGEST vaddr, ULONGEST size,
> >   					    ULONGEST offset, ULONGEST
> > inode,
> >   					    int read, int write,
> >   					    int exec, int modified,
> > +					    bool memory_tagged,
> >   					    const char *filename,
> >   					    void *data);
> >   
> > @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
> >     return smaps;
> >   }
> >   
> > -/* See linux-tdep.h.  */
> > +/* Helper that checks if an address is in a memory tag page for a
> > live
> > +   process.  */
> >   
> > -bool
> > -linux_address_in_memtag_page (CORE_ADDR address)
> > +static bool
> > +linux_process_address_in_memtag_page (CORE_ADDR address)
> >   {
> >     if (current_inferior ()->fake_pid_p)
> >       return false;
> > @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR
> > address)
> >     return false;
> >   }
> >   
> > +/* Helper that checks if an address is in a memory tag page for a
> > core file
> > +   process.  */
> > +
> > +static bool
> > +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> > +{
> > +  if (core_bfd == nullptr)
> > +    return false;
> > +
> > +  memtag_section_info info;
> > +  return get_next_core_memtag_section (core_bfd, nullptr, address,
> > info);
> > +}
> > +
> > +/* See linux-tdep.h.  */
> > +
> > +bool
> > +linux_address_in_memtag_page (CORE_ADDR address)
> > +{
> > +  if (!target_has_execution ())
> > +    return linux_core_file_address_in_memtag_page (address);
> > +
> > +  return linux_process_address_in_memtag_page (address);
> > +}
> > +
> >   /* List memory regions in the inferior for a corefile.  */
> >   
> >   static int
> > @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct
> > gdbarch *gdbarch,
> >   		map.offset, map.inode, map.read, map.write, map.exec,
> >   		1, /* MODIFIED is true because we want to dump
> >   		      the mapping.  */
> > +		map.vmflags.memory_tagging != 0,
> >   		map.filename.c_str (), obfd);
> >   	}
> >       }
> > @@ -1621,12 +1649,14 @@ static int
> >   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
> >   				 ULONGEST offset, ULONGEST inode,
> >   				 int read, int write, int exec, int
> > modified,
> > +				 bool memory_tagged,
> >   				 const char *filename, void *arg)
> >   {
> >     struct linux_find_memory_regions_data *data
> >       = (struct linux_find_memory_regions_data *) arg;
> >   
> > -  return data->func (vaddr, size, read, write, exec, modified,
> > data->obfd);
> > +  return data->func (vaddr, size, read, write, exec, modified,
> > memory_tagged,
> > +		     data->obfd);
> >   }
> >   
> >   /* A variant of linux_find_memory_regions_full that is suitable
> > as the
> > @@ -1675,6 +1705,7 @@ static int
> >   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
> >   			      ULONGEST offset, ULONGEST inode,
> >   			      int read, int write, int exec, int
> > modified,
> > +			      bool memory_tagged,
> >   			      const char *filename, void *data)
> >   {
> >     struct linux_make_mappings_data *map_data
> > diff --git a/gdb/memtag.c b/gdb/memtag.c
> > new file mode 100644
> > index 00000000000..af86137c49d
> > --- /dev/null
> > +++ b/gdb/memtag.c
> > @@ -0,0 +1,61 @@
> > +/* GDB generic memory tagging functions.
> > +
> > +   Copyright (C) 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 <
> > https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> >  >.  */
> > +
> > +#include "defs.h"
> > +#include "memtag.h"
> > +#include "bfd.h"
> > +
> > +/* See memtag.h */
> > +
> > +bool
> > +get_next_core_memtag_section (bfd *abfd, asection *section,
> > +			      CORE_ADDR address, memtag_section_info
> > &info)
> > +{
> > +  /* If the caller provided no SECTION to start from, search from
> > the
> > +     beginning.  */
> > +  if (section == nullptr)
> > +    section = bfd_get_section_by_name (abfd, "memtag");
> > +
> > +  /* Go through all the memtag sections and figure out if ADDRESS
> > +     falls within one of the memory ranges that contain tags.  */
> > +  while (section != nullptr)
> > +    {
> > +      size_t memtag_range_size = section->rawsize;
> > +      size_t tags_size = bfd_section_size (section);
> > +
> > +      /* Empty memory range and empty tag dump should not
> > happen.  */
> > +      gdb_assert (memtag_range_size != 0);
> > +      gdb_assert (tags_size != 0);
> > +
> > +      CORE_ADDR start_address = bfd_section_vma (section);
> > +      CORE_ADDR end_address = start_address + memtag_range_size;
> > +
> > +      /* Is the address within [start_address, end_address)?  */
> > +      if (address >= start_address
> > +	  && address < end_address)
> > +	{
> > +	  info.start_address = start_address;
> > +	  info.end_address = end_address;
> > +	  info.memtag_section = section;
> > +	  return true;
> > +	}
> > +      section = bfd_get_next_section_by_name (abfd, section);
> > +    }
> > +  return false;
> > +}
> > diff --git a/gdb/memtag.h b/gdb/memtag.h
> > new file mode 100644
> > index 00000000000..fe908c1e5e3
> > --- /dev/null
> > +++ b/gdb/memtag.h
> > @@ -0,0 +1,50 @@
> > +/* GDB generic memory tagging definitions.
> > +   Copyright (C) 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 <
> > https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> >  >.  */
> > +
> > +#ifndef MEMTAG_H
> > +#define MEMTAG_H
> > +
> > +#include "bfd.h"
> > +
> > +struct memtag_section_info
> > +{
> > +  /* The start address of the tagged memory range.  */
> > +  CORE_ADDR start_address;
> > +  /* The final address of the tagged memory range.  */
> > +  CORE_ADDR end_address;
> > +  /* The section containing tags for the memory range
> > +     [start_address, end_address).  */
> > +  asection *memtag_section;
> > +};
> > +
> > +/* Helper function to walk through memory tag sections in a core
> > file.
> > +
> > +   Return TRUE if there is a "memtag" section containing
> > ADDRESS.  Return FALSE
> > +   otherwise.
> > +
> > +   If SECTION is provided, search from that section onwards. If
> > SECTION is
> > +   nullptr, then start a new search.
> > +
> > +   If a "memtag" section containing ADDRESS is found, fill INFO
> > with data
> > +   about such section.  Otherwise leave it unchanged.  */
> > +
> > +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> > +				   CORE_ADDR address,
> > +				   memtag_section_info &info);
> > +
> > +#endif /* MEMTAG_H */
> > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > new file mode 100644
> > index 00000000000..b20ebcff424
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> > @@ -0,0 +1,93 @@
> > +/* This test program is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2021 Free Software Foundation, Inc.
> > +
> > +   This program is free software; you can redistribute it and/or
> > modify
> > +   it under the terms of the GNU General Public License as
> > published by
> > +   the Free Software Foundation; either version 3 of the License,
> > or
> > +   (at your option) any later version.
> > +
> > +   This program is distributed in the hope that it will be useful,
> > +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +   GNU General Public License for more details.
> > +
> > +   You should have received a copy of the GNU General Public
> > License
> > +   along with this program.  If not, see <
> > https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> >  >.  */
> > +
> > +/* Exercise AArch64's Memory Tagging Extension with tagged
> > pointers.  */
> > +
> > +/* This test was based on the documentation for the AArch64 Memory
> > Tagging
> > +   Extension from the Linux Kernel, found in the sources in
> > +   Documentation/arm64/memory-tagging-extension.rst.  */
> > +
> > +#include <errno.h>
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <unistd.h>
> > +#include <sys/auxv.h>
> > +#include <sys/mman.h>
> > +#include <sys/prctl.h>
> > +
> > +/* From arch/arm64/include/uapi/asm/hwcap.h */
> > +#define HWCAP2_MTE              (1 << 18)
> > +
> > +/* From arch/arm64/include/uapi/asm/mman.h */
> > +#define PROT_MTE  0x20
> > +
> > +/* From include/uapi/linux/prctl.h */
> > +#define PR_SET_TAGGED_ADDR_CTRL 55
> > +#define PR_GET_TAGGED_ADDR_CTRL 56
> > +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> > +#define PR_MTE_TCF_SHIFT	1
> > +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> > +#define PR_MTE_TAG_SHIFT	3
> > +
> > +void
> > +access_memory (unsigned char *tagged_ptr)
> > +{
> > +  tagged_ptr[0] = 'a';
> > +}
> > +
> > +int
> > +main (int argc, char **argv)
> > +{
> > +  unsigned char *tagged_ptr;
> > +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> > +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> > +
> > +  /* Bail out if MTE is not supported.  */
> > +  if (!(hwcap2 & HWCAP2_MTE))
> > +    return 1;
> > +
> > +  /* Enable the tagged address ABI, synchronous MTE tag check
> > faults and
> > +     allow all non-zero tags in the randomly generated set.  */
> > +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> > +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> > +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> > +	     0, 0, 0))
> > +    {
> > +      perror ("prctl () failed");
> > +      return 1;
> > +    }
> > +
> > +  /* Create a mapping that will have PROT_MTE set.  */
> > +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> > +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> > +  if (tagged_ptr == MAP_FAILED)
> > +    {
> > +      perror ("mmap () failed");
> > +      return 1;
> > +    }
> > +
> > +  /* Enable MTE on the above anonymous mmap.  */
> > +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE |
> > PROT_MTE))
> > +    {
> > +      perror ("mprotect () failed");
> > +      return 1;
> > +    }
> > +
> > +  access_memory (tagged_ptr);
> > +
> > +  return 0;
> > +}
> > diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > new file mode 100644
> > index 00000000000..8a19c4b449e
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> > @@ -0,0 +1,107 @@
> > +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> > +#
> > +# This program is free software; you can redistribute it and/or
> > modify
> > +# it under the terms of the GNU General Public License as
> > published by
> > +# the Free Software Foundation; either version 3 of the License,
> > or
> > +# (at your option) any later version.
> > +#
> > +# This program is distributed in the hope that it will be useful,
> > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +# GNU General Public License for more details.
> > +#
> > +# You should have received a copy of the GNU General Public
> > License
> > +# along with this program.  If not, see <
> > https://urldefense.com/v3/__http://www.gnu.org/licenses/__;!!CTRNKA9wMg0ARbw!0jBVgsoE-RaTGT098YRLfC3gcQJlvfuMujbUqmtqoh3JjeD2BI4JNxmWCGaefncNfKBbmw$
> >  >.
> > +
> > +# This file is part of the gdb testsuite.
> > +
> > +# Test generating and reading a core file with MTE memory tags.
> > +
> > +if {![is_aarch64_target]} {
> > +    verbose "Skipping ${gdb_test_file_name}."
> > +    return
> > +}
> > +
> > +standard_testfile
> > +if { [prepare_for_testing "failed to prepare" ${testfile}
> > ${srcfile}] } {
> > +    return -1
> > +}
> > +
> > +if ![runto_main] {
> > +    untested "could not run to main"
> > +    return -1
> > +}
> > +
> > +# Targets that don't support memory tagging should not execute the
> > +# runtime memory tagging tests.
> > +if {![supports_memtag]} {
> > +    unsupported "memory tagging unsupported"
> > +    return -1
> > +}
> > +
> > +gdb_breakpoint "access_memory"
> > +
> > +if [gdb_continue "access_memory"] {
> > +    return -1
> > +}
> > +
> > +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> > +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> > +for {set i 15} {$i >= 0} {incr i -1} {
> > +    set index [expr [expr 15 - $i] * 16]
> > +    set tag [format "%02x" $i]
> > +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\]
> > 1 $tag" \
> > +	     $atag_msg \
> > +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> > +}
> > +
> > +# Run until a crash and confirm GDB displays memory tag violation
> > +# information.
> > +gdb_test "continue" \
> > +    [multi_line \
> > +	"Program received signal SIGSEGV, Segmentation fault" \
> > +	"Memory tag violation while accessing address $hex" \
> > +	"Allocation tag $hex" \
> > +	"Logical tag $hex\." \
> > +	"$hex in access_memory \\(.*\\) at .*" \
> > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > +	 "display tag violation information for live process"
> > +
> > +# Generate the core file.
> > +set core_filename [standard_output_file "$testfile.core"]
> > +set core_generated [gdb_gcore_cmd "$core_filename" "generate core
> > file"]
> > +
> > +if { !$core_generated } {
> > +    return -1
> > +}
> > +
> > +clean_restart $binfile
> > +
> > +# Load the core file and make sure we see the tag violation fault
> > +# information.
> > +gdb_test "core $core_filename" \
> > +    [multi_line \
> > +	"Core was generated by.*\." \
> > +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> > +	"Memory tag violation while accessing address $hex" \
> > +	"Allocation tag 0xf" \
> > +	"Logical tag 0x0\." \
> > +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> > +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> > +	 "core file shows tag violation information"
> > +
> > +# Make sure we have the tag_ctl register.
> > +gdb_test "info register tag_ctl" \
> > +	 "tag_ctl.*$hex.*${::decimal}" \
> > +	 "tag_ctl is available"
> > +
> > +# Check if the tag granules have the expected values.  If they do,
> > that
> > +# means the core file saved the tags properly and GDB has read
> > them
> > +# correctly.
> > +for {set i 15} {$i >= 0} {incr i -1} {
> > +    set index [expr [expr 15 - $i] * 16]
> > +    set tag [format "%x" $i]
> > +    gdb_test "memory-tag print-allocation-tag
> > &tagged_ptr\[$index\]" \
> > +	     "= 0x$tag" \
> > +	     "memory tag of &tagged_ptr\[$index\] is correct"
> > +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
  2022-05-12 10:36   ` Luis Machado
  2022-05-18 12:46   ` Luis Machado
@ 2022-06-06  9:28   ` Luis Machado
  2022-06-06  9:42     ` Kuan-Ying Lee
  2022-06-06 10:49     ` Eli Zaretskii
  2022-06-22  9:04   ` Luis Machado
  2022-06-27 14:51   ` Pedro Alves
  4 siblings, 2 replies; 47+ messages in thread
From: Luis Machado @ 2022-06-06  9:28 UTC (permalink / raw)
  To: gdb-patches

Ping?

On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> v4:
> 
> - Updated documentation (added cross-references).
> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>    PT_AARCH64_MEMTAG_MTE.
> 
> v3:
> 
> - Updated NEWS and documentation to be more thorough.
> 
> v2:
> 
> - Rework memory tag section handling to use generic section fields.
> 
> --
> 
> Teach GDB how to dump memory tags for AArch64 when using the gcore command
> and how to read memory tag data back from a core file generated by GDB
> (via gcore) or by the Linux kernel.
> 
> The format is documented in the Linux Kernel documentation [1].
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
> of those segments when reading the core file back.
> 
> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
> are stored packed as 2 tags per byte.
> 
> When reading the data back, the tags are unpacked.
> 
> I've added a new testcase to exercise the feature.
> 
> Build-tested with --enable-targets=all and regression tested on aarch64-linux
> Ubuntu 20.04.
> 
> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |  10 ++
>   gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>   gdb/arch/aarch64-mte-linux.h                 |  10 ++
>   gdb/corelow.c                                |  62 +++++++
>   gdb/defs.h                                   |   3 +-
>   gdb/doc/gdb.texinfo                          |  19 +++
>   gdb/gcore.c                                  |  83 ++++++++-
>   gdb/gdbarch-components.py                    |  35 ++++
>   gdb/gdbarch-gen.h                            |  26 +++
>   gdb/gdbarch.c                                |  96 +++++++++++
>   gdb/linux-tdep.c                             |  39 ++++-
>   gdb/memtag.c                                 |  61 +++++++
>   gdb/memtag.h                                 |  50 ++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>   17 files changed, 910 insertions(+), 8 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 418094775a5..fac9364bea4 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 982f4a1a18c..3d925dc3663 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,16 @@
>   
>   *** Changes since GDB 12
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> +  When a process uses memory-mapped pages protected by memory tags (for
> +  example, AArch64 MTE), this additional information will be recorded in
> +  the core file in the event of a crash or if GDB generates a core file
> +  from the current process state.  GDB will show this additional information
> +  automatically, or through one of the memory-tag subcommands.
> +
>   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>   
>   * Remove support for building against Python 2, it is now only possible to
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index 55094b3d88b..12d98e71796 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,9 @@
>   
>   #include "gdbsupport/selftest.h"
>   
> +#include "elf/common.h"
> +#include "elf/aarch64.h"
> +
>   /* Signal frame handling.
>   
>         +------------+  ^
> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
> +
> +static asection *
> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> +				     CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (obfd != nullptr);
> +  gdb_assert (size > 0);
> +
> +  /* Create the section and associated program header.  */
> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
> +
> +  if (mte_section == nullptr)
> +    return nullptr;
> +
> +  bfd_set_section_vma (mte_section, address);
> +  /* The size of the memory range covered by the memory tags.  We reuse the
> +     section's rawsize field for this purpose.  */
> +  mte_section->rawsize = size;
> +  /* Tags are stored packed as 2 tags per byte.  */
> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
> +     refuse to write data to this section.  */
> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> +
> +  /* Store program header information.  */
> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
> +		   &mte_section);
> +
> +  return mte_section;
> +}
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
> +
> +static bool
> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  size_t segment_size = osec->rawsize;
> +  CORE_ADDR start_address = bfd_section_vma (osec);
> +  CORE_ADDR end_address = start_address + segment_size;
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return true;
> +
> +  CORE_ADDR address = start_address;
> +
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  return false;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Pack the MTE tag bits.  */
> +  aarch64_mte_pack_tags (tags);
> +
> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> +				 0, tags.size ()))
> +    {
> +      warning (_("Failed to write %s bytes of corefile memory "
> +		 "tag content (%s)."),
> +	       pulongest (tags.size ()),
> +	       bfd_errmsg (bfd_get_error ()));
> +    }
> +  return true;
> +}
> +
> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
> +   hook.  Decode a memory tag section and return the requested tags.
> +
> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> +				     bfd_section *section,
> +				     int type,
> +				     CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (section != nullptr);
> +
> +  /* The requested address must not be less than section->vma.  */
> +  gdb_assert (section->vma <= address);
> +
> +  /* Figure out how many tags we need to fetch in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +  /* Sanity check.  */
> +  gdb_assert (granules > 0);
> +
> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
> +  size_t granules_from_vma
> +    = aarch64_mte_get_tag_granules (section->vma,
> +				    address - section->vma + length,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> +
> +  /* Figure out the starting offset into the packed tags data.  */
> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> +
> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
> +				 offset, tags.size ()))
> +    error (_("Couldn't read contents from memtag section."));
> +
> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
> +     returning.  */
> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> +  aarch64_mte_unpack_tags (tags, skip_first);
> +
> +  /* Resize to the exact number of tags that was requested.  */
> +  tags.resize (granules);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create a memory tag section for a particular
> +	 PT_LOAD segment.  */
> +      set_gdbarch_create_memtag_section
> +	(gdbarch, aarch64_linux_create_memtag_section);
> +
> +      /* Core file helper to fill a memory tag section with tag data.  */
> +      set_gdbarch_fill_memtag_section
> +	(gdbarch, aarch64_linux_fill_memtag_section);
> +
> +      /* Core file helper to decode a memory tag section.  */
> +      set_gdbarch_decode_memtag_section (gdbarch,
> +					 aarch64_linux_decode_memtag_section);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
> index fc7a8cc00f7..3af6f364e91 100644
> --- a/gdb/arch/aarch64-mte-linux.c
> +++ b/gdb/arch/aarch64-mte-linux.c
> @@ -21,6 +21,62 @@
>   
>   /* See arch/aarch64-mte-linux.h */
>   
> +void
> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> +{
> +  /* Nothing to pack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* If the tags vector has an odd number of elements, add another
> +     zeroed-out element to make it even.  This facilitates packing.  */
> +  if ((tags.size () % 2) != 0)
> +    tags.emplace_back (0);
> +
> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> +       unpacked += 2, packed++)
> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> +
> +  /* Now we have half the size.  */
> +  tags.resize (tags.size () / 2);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
> +void
> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
> +{
> +  /* Nothing to unpack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* An unpacked MTE tags vector will have twice the number of elements
> +     compared to an unpacked one.  */
> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> +
> +  int unpacked = 0, packed = 0;
> +
> +  if (skip_first)
> +    {
> +      /* We are not interested in the first unpacked element, just discard
> +	 it.  */
> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> +      unpacked++;
> +      packed++;
> +    }
> +
> +  for (; packed < tags.size (); unpacked += 2, packed++)
> +    {
> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> +    }
> +
> +  /* Update the original tags vector.  */
> +  tags = std::move (unpacked_tags);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
>   size_t
>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>   {
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index d158926feff..8a145b447aa 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
> +   2 tags per byte and resize the vector.  */
> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> +
> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 8c33fb7ebb2..8b8994f80db 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -52,6 +52,7 @@
>   #include <unordered_set>
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* 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 (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/defs.h b/gdb/defs.h
> index 99bfdd526ff..51a7576a56a 100644
> --- a/gdb/defs.h
> +++ b/gdb/defs.h
> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>   
>   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>   					 int read, int write, int exec,
> -					 int modified, void *data);
> +					 int modified, bool memory_tagged,
> +					 void *data);
>   
>   /* * Possible lvalue types.  Like enum language, this should be in
>      value.h, but needs to be here for the same reason.  */
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 38ad2ac32b0..36f10f20cfb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> +When a process uses memory-mapped pages protected by memory tags (for
> +example, AArch64 MTE), this additional information will be recorded in
> +the core file in the event of a crash or if @value{GDBN} generates a core file
> +from the current process state.
> +
> +The memory tag data will be used so developers can display the memory
> +tags from a particular memory region (using the @samp{m} modifier to the
> +@command{x} command, using the @command{print} command or using the various
> +@command{memory-tag} subcommands.
> +
> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
> +information automatically from the core file, and will show one of the above
> +messages depending on whether the synchronous or asynchronous mode is selected.
> +@xref{Memory Tagging}. @xref{Memory}.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gcore.c b/gdb/gcore.c
> index fdb22b72a07..b81ef81ab84 100644
> --- a/gdb/gcore.c
> +++ b/gdb/gcore.c
> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>     int p_flags = 0;
>     int p_type = 0;
>   
> +  /* Memory tag segments have already been handled by the architecture, as
> +     those contain arch-specific information.  If we have one of those, just
> +     return.  */
> +  if (startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
>     /* FIXME: these constants may only be applicable for ELF.  */
>     if (startswith (bfd_section_name (osec), "load"))
>       p_type = PT_LOAD;
> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>   
>   static int
>   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
> -		       int write, int exec, int modified, void *data)
> +		       int write, int exec, int modified, bool memory_tagged,
> +		       void *data)
>   {
>     bfd *obfd = (bfd *) data;
>     asection *osec;
> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>     return 0;
>   }
>   
> +/* gdbarch_find_memory_region callback for creating a memory tag section.
> +   DATA is 'bfd *' for the core file GDB is creating.  */
> +
> +static int
> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
> +				      int read, int write, int exec,
> +				      int modified, bool memory_tagged,
> +				      void *data)
> +{
> +  /* Are there memory tags in this particular memory map entry?  */
> +  if (!memory_tagged)
> +    return 0;
> +
> +  bfd *obfd = (bfd *) data;
> +
> +  /* Ask the architecture to create a memory tag section for this particular
> +     memory map entry.  It will be populated with contents later, as we can't
> +     start writing the contents before we have all the sections sorted out.  */
> +  asection *memtag_section
> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
> +
> +  if (memtag_section == nullptr)
> +    {
> +      warning (_("Couldn't make gcore memory tag segment: %s"),
> +	       bfd_errmsg (bfd_get_error ()));
> +      return 1;
> +    }
> +
> +  if (info_verbose)
> +    {
> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
> +			      "at %s\n",
> +		  plongest (bfd_section_size (memtag_section)),
> +		  paddress (target_gdbarch (), vaddr));
> +    }
> +
> +  return 0;
> +}
> +
>   int
>   objfile_find_memory_regions (struct target_ops *self,
>   			     find_memory_region_ftype func, void *obfd)
> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   			   (flags & SEC_READONLY) == 0, /* Writable.  */
>   			   (flags & SEC_CODE) != 0, /* Executable.  */
>   			   1, /* MODIFIED is unknown, pass it as true.  */
> +			   false, /* No memory tags in the object file.  */
>   			   obfd);
>   	    if (ret != 0)
>   	      return ret;
> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Stack section will be writable.  */
>   	     0, /* Stack section will not be executable.  */
>   	     1, /* Stack section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     /* Make a heap segment.  */
> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Heap section will be writable.  */
>   	     0, /* Heap section will not be executable.  */
>   	     1, /* Heap section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     return 0;
> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>       }
>   }
>   
> +/* Callback to copy contents to a particular memory tag section.  */
> +
> +static void
> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> +{
> +  /* We are only interested in "memtag" sections.  */
> +  if (!startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
> +  /* Fill the section with memory tag contents.  */
> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> +    error (_("Failed to fill memory tag section for core file."));
> +}
> +
>   static int
>   gcore_memory_sections (bfd *obfd)
>   {
> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>   	return 0;			/* FIXME: error return/msg?  */
>       }
>   
> +  /* Take care of dumping memory tags, if there are any.  */
> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> +      || gdbarch_find_memory_regions (target_gdbarch (),
> +				      gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +    {
> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +	return 0;
> +    }
> +
>     /* Record phdrs for section-to-segment mapping.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
>       make_output_phdrs (obfd, sect);
>   
> -  /* Copy memory region contents.  */
> +  /* Copy memory region and memory tag contents.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
> -    gcore_copy_callback (obfd, sect);
> +    {
> +      gcore_copy_callback (obfd, sect);
> +      gcore_copy_memtag_section_callback (obfd, sect);
> +    }
>   
>     return 1;
>   }
> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
> index e8f20c83ff0..6fa1b7591db 100644
> --- a/gdb/gdbarch-components.py
> +++ b/gdb/gdbarch-components.py
> @@ -1522,6 +1522,41 @@ Find core file memory regions
>       invalid=True,
>   )
>   
> +Method(
> +    comment="""
> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
> +""",
> +    type="asection *",
> +    name="create_memtag_section",
> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
> +""",
> +    type="bool",
> +    name="fill_memtag_section",
> +    params=[("asection *", "osec")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Decode a memory tag SECTION and return the tags of type TYPE contained in
> +the memory range [ADDRESS, ADDRESS + LENGTH).
> +If no tags were found, return an empty vector.
> +""",
> +    type="gdb::byte_vector",
> +    name="decode_memtag_section",
> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
>   Method(
>       comment="""
>   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index 882b9057b1a..1d19f51f21d 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
> +
> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
> +
> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
> +
> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
> +
> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
> +   the memory range [ADDRESS, ADDRESS + LENGTH).
> +   If no tags were found, return an empty vector. */
> +
> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index a588bdef61a..f5dbacb14e7 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -171,6 +171,9 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_section, has predicate.  */
> +  /* Skip verify of fill_memtag_section, has predicate.  */
> +  /* Skip verify of decode_memtag_section, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     gdb_printf (file,
>                         "gdbarch_dump: find_memory_regions = <%s>\n",
>                         host_address_to_string (gdbarch->find_memory_regions));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
> +                      gdbarch_create_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
> +                      gdbarch_fill_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->fill_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
> +                      gdbarch_decode_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_section));
>     gdb_printf (file,
>                         "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>                         gdbarch_core_xfer_shared_libraries_p (gdbarch));
> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_section != NULL;
> +}
> +
> +asection *
> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
> +}
> +
> +void
> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
> +{
> +  gdbarch->create_memtag_section = create_memtag_section;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->fill_memtag_section != NULL;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
> +  return gdbarch->fill_memtag_section (gdbarch, osec);
> +}
> +
> +void
> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
> +{
> +  gdbarch->fill_memtag_section = fill_memtag_section;
> +}
> +
> +bool
> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_section != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
> +{
> +  gdbarch->decode_memtag_section = decode_memtag_section;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 4e728a06e7e..8a83ed320cf 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -42,6 +42,7 @@
>   #include "gcore.h"
>   #include "gcore-elf.h"
>   #include "solib-svr4.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   #include <unordered_map>
> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>   					    ULONGEST offset, ULONGEST inode,
>   					    int read, int write,
>   					    int exec, int modified,
> +					    bool memory_tagged,
>   					    const char *filename,
>   					    void *data);
>   
> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_section_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>   		map.offset, map.inode, map.read, map.write, map.exec,
>   		1, /* MODIFIED is true because we want to dump
>   		      the mapping.  */
> +		map.vmflags.memory_tagging != 0,
>   		map.filename.c_str (), obfd);
>   	}
>       }
> @@ -1621,12 +1649,14 @@ static int
>   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>   				 ULONGEST offset, ULONGEST inode,
>   				 int read, int write, int exec, int modified,
> +				 bool memory_tagged,
>   				 const char *filename, void *arg)
>   {
>     struct linux_find_memory_regions_data *data
>       = (struct linux_find_memory_regions_data *) arg;
>   
> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
> +		     data->obfd);
>   }
>   
>   /* A variant of linux_find_memory_regions_full that is suitable as the
> @@ -1675,6 +1705,7 @@ static int
>   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>   			      ULONGEST offset, ULONGEST inode,
>   			      int read, int write, int exec, int modified,
> +			      bool memory_tagged,
>   			      const char *filename, void *data)
>   {
>     struct linux_make_mappings_data *map_data
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..af86137c49d
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,61 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 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 "memtag.h"
> +#include "bfd.h"
> +
> +/* See memtag.h */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_section_info &info)
> +{
> +  /* If the caller provided no SECTION to start from, search from the
> +     beginning.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, "memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t memtag_range_size = section->rawsize;
> +      size_t tags_size = bfd_section_size (section);
> +
> +      /* Empty memory range and empty tag dump should not happen.  */
> +      gdb_assert (memtag_range_size != 0);
> +      gdb_assert (tags_size != 0);
> +
> +      CORE_ADDR start_address = bfd_section_vma (section);
> +      CORE_ADDR end_address = start_address + memtag_range_size;
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +      section = bfd_get_next_section_by_name (abfd, section);
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..fe908c1e5e3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,50 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_section_info
> +{
> +  /* The start address of the tagged memory range.  */
> +  CORE_ADDR start_address;
> +  /* The final address of the tagged memory range.  */
> +  CORE_ADDR end_address;
> +  /* The section containing tags for the memory range
> +     [start_address, end_address).  */
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through memory tag sections in a core file.
> +
> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address,
> +				   memtag_section_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..8a19c4b449e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,107 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-18 13:58     ` John Baldwin
@ 2022-05-23  9:50       ` Luis Machado
  0 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2022-05-23  9:50 UTC (permalink / raw)
  To: John Baldwin, gdb-patches

Hi,

On 5/18/22 14:58, John Baldwin wrote:
>  From my perspective this looks ok.

Great. Thanks for the feedback.

> 
> On 5/18/22 5:46 AM, Luis Machado via Gdb-patches wrote:
>> Ping? The binutils patch has been approved, but I'd like to push both at the same time.
>>
>> On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
>>> v4:
>>>
>>> - Updated documentation (added cross-references).
>>> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>>>     PT_AARCH64_MEMTAG_MTE.
>>>
>>> v3:
>>>
>>> - Updated NEWS and documentation to be more thorough.
>>>
>>> v2:
>>>
>>> - Rework memory tag section handling to use generic section fields.
>>>
>>> -- 
>>>
>>> Teach GDB how to dump memory tags for AArch64 when using the gcore command
>>> and how to read memory tag data back from a core file generated by GDB
>>> (via gcore) or by the Linux kernel.
>>>
>>> The format is documented in the Linux Kernel documentation [1].
>>>
>>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>>> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
>>> of those segments when reading the core file back.
>>>
>>> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
>>> are stored packed as 2 tags per byte.
>>>
>>> When reading the data back, the tags are unpacked.
>>>
>>> I've added a new testcase to exercise the feature.
>>>
>>> Build-tested with --enable-targets=all and regression tested on aarch64-linux
>>> Ubuntu 20.04.
>>>
>>> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
>>> ---
>>>    gdb/Makefile.in                              |   1 +
>>>    gdb/NEWS                                     |  10 ++
>>>    gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>>>    gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>>>    gdb/arch/aarch64-mte-linux.h                 |  10 ++
>>>    gdb/corelow.c                                |  62 +++++++
>>>    gdb/defs.h                                   |   3 +-
>>>    gdb/doc/gdb.texinfo                          |  19 +++
>>>    gdb/gcore.c                                  |  83 ++++++++-
>>>    gdb/gdbarch-components.py                    |  35 ++++
>>>    gdb/gdbarch-gen.h                            |  26 +++
>>>    gdb/gdbarch.c                                |  96 +++++++++++
>>>    gdb/linux-tdep.c                             |  39 ++++-
>>>    gdb/memtag.c                                 |  61 +++++++
>>>    gdb/memtag.h                                 |  50 ++++++
>>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>>>    17 files changed, 910 insertions(+), 8 deletions(-)
>>>    create mode 100644 gdb/memtag.c
>>>    create mode 100644 gdb/memtag.h
>>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>>
>>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>>> index 418094775a5..fac9364bea4 100644
>>> --- a/gdb/Makefile.in
>>> +++ b/gdb/Makefile.in
>>> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>>>        memattr.c \
>>>        memory-map.c \
>>>        memrange.c \
>>> +    memtag.c \
>>>        minidebug.c \
>>>        minsyms.c \
>>>        mipsread.c \
>>> diff --git a/gdb/NEWS b/gdb/NEWS
>>> index 982f4a1a18c..3d925dc3663 100644
>>> --- a/gdb/NEWS
>>> +++ b/gdb/NEWS
>>> @@ -3,6 +3,16 @@
>>>    *** Changes since GDB 12
>>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>>> +  reading memory tag data for AArch64 MTE from core files generated by
>>> +  the gcore command or the Linux kernel.
>>> +
>>> +  When a process uses memory-mapped pages protected by memory tags (for
>>> +  example, AArch64 MTE), this additional information will be recorded in
>>> +  the core file in the event of a crash or if GDB generates a core file
>>> +  from the current process state.  GDB will show this additional information
>>> +  automatically, or through one of the memory-tag subcommands.
>>> +
>>>    * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>>>    * Remove support for building against Python 2, it is now only possible to
>>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>>> index 55094b3d88b..12d98e71796 100644
>>> --- a/gdb/aarch64-linux-tdep.c
>>> +++ b/gdb/aarch64-linux-tdep.c
>>> @@ -53,6 +53,9 @@
>>>    #include "gdbsupport/selftest.h"
>>> +#include "elf/common.h"
>>> +#include "elf/aarch64.h"
>>> +
>>>    /* Signal frame handling.
>>>          +------------+  ^
>>> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>>        }
>>>    }
>>> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
>>> +
>>> +static asection *
>>> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
>>> +                     CORE_ADDR address, size_t size)
>>> +{
>>> +  gdb_assert (obfd != nullptr);
>>> +  gdb_assert (size > 0);
>>> +
>>> +  /* Create the section and associated program header.  */
>>> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
>>> +
>>> +  if (mte_section == nullptr)
>>> +    return nullptr;
>>> +
>>> +  bfd_set_section_vma (mte_section, address);
>>> +  /* The size of the memory range covered by the memory tags.  We reuse the
>>> +     section's rawsize field for this purpose.  */
>>> +  mte_section->rawsize = size;
>>> +  /* Tags are stored packed as 2 tags per byte.  */
>>> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
>>> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
>>> +     refuse to write data to this section.  */
>>> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
>>> +
>>> +  /* Store program header information.  */
>>> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
>>> +           &mte_section);
>>> +
>>> +  return mte_section;
>>> +}
>>> +
>>> +/* Maximum number of tags to request.  */
>>> +#define MAX_TAGS_TO_TRANSFER 1024
>>> +
>>> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
>>> +
>>> +static bool
>>> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>>> +{
>>> +  /* We only handle MTE tags for now.  */
>>> +
>>> +  size_t segment_size = osec->rawsize;
>>> +  CORE_ADDR start_address = bfd_section_vma (osec);
>>> +  CORE_ADDR end_address = start_address + segment_size;
>>> +
>>> +  /* Figure out how many tags we need to store in this memory range.  */
>>> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
>>> +                          AARCH64_MTE_GRANULE_SIZE);
>>> +
>>> +  /* If there are no tag granules to fetch, just return.  */
>>> +  if (granules == 0)
>>> +    return true;
>>> +
>>> +  CORE_ADDR address = start_address;
>>> +
>>> +  /* Vector of tags.  */
>>> +  gdb::byte_vector tags;
>>> +
>>> +  while (granules > 0)
>>> +    {
>>> +      /* Transfer tags in chunks.  */
>>> +      gdb::byte_vector tags_read;
>>> +      size_t xfer_len
>>> +    = (granules >= MAX_TAGS_TO_TRANSFER)?
>>> +      MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>>> +      granules * AARCH64_MTE_GRANULE_SIZE;
>>> +
>>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>>> +                 static_cast<int> (memtag_type::allocation)))
>>> +    {
>>> +      warning (_("Failed to read MTE tags from memory range [%s,%s)."),
>>> +             phex_nz (start_address, sizeof (start_address)),
>>> +             phex_nz (end_address, sizeof (end_address)));
>>> +      return false;
>>> +    }
>>> +
>>> +      /* Transfer over the tags that have been read.  */
>>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>>> +
>>> +      /* Adjust the remaining granules and starting address.  */
>>> +      granules -= tags_read.size ();
>>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>>> +    }
>>> +
>>> +  /* Pack the MTE tag bits.  */
>>> +  aarch64_mte_pack_tags (tags);
>>> +
>>> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
>>> +                 0, tags.size ()))
>>> +    {
>>> +      warning (_("Failed to write %s bytes of corefile memory "
>>> +         "tag content (%s)."),
>>> +           pulongest (tags.size ()),
>>> +           bfd_errmsg (bfd_get_error ()));
>>> +    }
>>> +  return true;
>>> +}
>>> +
>>> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
>>> +   hook.  Decode a memory tag section and return the requested tags.
>>> +
>>> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
>>> +   range.  */
>>> +
>>> +static gdb::byte_vector
>>> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
>>> +                     bfd_section *section,
>>> +                     int type,
>>> +                     CORE_ADDR address, size_t length)
>>> +{
>>> +  gdb_assert (section != nullptr);
>>> +
>>> +  /* The requested address must not be less than section->vma.  */
>>> +  gdb_assert (section->vma <= address);
>>> +
>>> +  /* Figure out how many tags we need to fetch in this memory range.  */
>>> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
>>> +                          AARCH64_MTE_GRANULE_SIZE);
>>> +  /* Sanity check.  */
>>> +  gdb_assert (granules > 0);
>>> +
>>> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
>>> +  size_t granules_from_vma
>>> +    = aarch64_mte_get_tag_granules (section->vma,
>>> +                    address - section->vma + length,
>>> +                    AARCH64_MTE_GRANULE_SIZE);
>>> +
>>> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
>>> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
>>> +
>>> +  /* Figure out the starting offset into the packed tags data.  */
>>> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
>>> +
>>> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
>>> +                 offset, tags.size ()))
>>> +    error (_("Couldn't read contents from memtag section."));
>>> +
>>> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
>>> +     returning.  */
>>> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
>>> +  aarch64_mte_unpack_tags (tags, skip_first);
>>> +
>>> +  /* Resize to the exact number of tags that was requested.  */
>>> +  tags.resize (granules);
>>> +
>>> +  return tags;
>>> +}
>>> +
>>>    static void
>>>    aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>>    {
>>> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>>          set_gdbarch_report_signal_info (gdbarch,
>>>                          aarch64_linux_report_signal_info);
>>> +
>>> +      /* Core file helpers.  */
>>> +
>>> +      /* Core file helper to create a memory tag section for a particular
>>> +     PT_LOAD segment.  */
>>> +      set_gdbarch_create_memtag_section
>>> +    (gdbarch, aarch64_linux_create_memtag_section);
>>> +
>>> +      /* Core file helper to fill a memory tag section with tag data.  */
>>> +      set_gdbarch_fill_memtag_section
>>> +    (gdbarch, aarch64_linux_fill_memtag_section);
>>> +
>>> +      /* Core file helper to decode a memory tag section.  */
>>> +      set_gdbarch_decode_memtag_section (gdbarch,
>>> +                     aarch64_linux_decode_memtag_section);
>>>        }
>>>      /* Initialize the aarch64_linux_record_tdep.  */
>>> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
>>> index fc7a8cc00f7..3af6f364e91 100644
>>> --- a/gdb/arch/aarch64-mte-linux.c
>>> +++ b/gdb/arch/aarch64-mte-linux.c
>>> @@ -21,6 +21,62 @@
>>>    /* See arch/aarch64-mte-linux.h */
>>> +void
>>> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
>>> +{
>>> +  /* Nothing to pack?  */
>>> +  if (tags.empty ())
>>> +    return;
>>> +
>>> +  /* If the tags vector has an odd number of elements, add another
>>> +     zeroed-out element to make it even.  This facilitates packing.  */
>>> +  if ((tags.size () % 2) != 0)
>>> +    tags.emplace_back (0);
>>> +
>>> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
>>> +       unpacked += 2, packed++)
>>> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
>>> +
>>> +  /* Now we have half the size.  */
>>> +  tags.resize (tags.size () / 2);
>>> +}
>>> +
>>> +/* See arch/aarch64-mte-linux.h */
>>> +
>>> +void
>>> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
>>> +{
>>> +  /* Nothing to unpack?  */
>>> +  if (tags.empty ())
>>> +    return;
>>> +
>>> +  /* An unpacked MTE tags vector will have twice the number of elements
>>> +     compared to an unpacked one.  */
>>> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
>>> +
>>> +  int unpacked = 0, packed = 0;
>>> +
>>> +  if (skip_first)
>>> +    {
>>> +      /* We are not interested in the first unpacked element, just discard
>>> +     it.  */
>>> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
>>> +      unpacked++;
>>> +      packed++;
>>> +    }
>>> +
>>> +  for (; packed < tags.size (); unpacked += 2, packed++)
>>> +    {
>>> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
>>> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
>>> +    }
>>> +
>>> +  /* Update the original tags vector.  */
>>> +  tags = std::move (unpacked_tags);
>>> +}
>>> +
>>> +/* See arch/aarch64-mte-linux.h */
>>> +
>>>    size_t
>>>    aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>>>    {
>>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>>> index d158926feff..8a145b447aa 100644
>>> --- a/gdb/arch/aarch64-mte-linux.h
>>> +++ b/gdb/arch/aarch64-mte-linux.h
>>> @@ -32,6 +32,7 @@
>>>    /* We have one tag per 16 bytes of memory.  */
>>>    #define AARCH64_MTE_GRANULE_SIZE 16
>>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>>    #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>>    #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>>       It is always possible to get the logical tag.  */
>>>    extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
>>> +   2 tags per byte and resize the vector.  */
>>> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
>>> +
>>> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
>>> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
>>> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
>>> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
>>> +
>>>    #endif /* ARCH_AARCH64_LINUX_H */
>>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>>> index 8c33fb7ebb2..8b8994f80db 100644
>>> --- a/gdb/corelow.c
>>> +++ b/gdb/corelow.c
>>> @@ -52,6 +52,7 @@
>>>    #include <unordered_set>
>>>    #include "gdbcmd.h"
>>>    #include "xml-tdesc.h"
>>> +#include "memtag.h"
>>>    #ifndef O_LARGEFILE
>>>    #define O_LARGEFILE 0
>>> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>>>      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;
>>> +
>>>      /* A few helpers.  */
>>>      /* Getter, see variable definition.  */
>>> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
>>> +{
>>> +  struct gdbarch *gdbarch = target_gdbarch ();
>>> +
>>> +  /* 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 (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;
>>> +}
>>> +
>>>    /* Get a pointer to the current core target.  If not connected to a
>>>       core target, return NULL.  */
>>> diff --git a/gdb/defs.h b/gdb/defs.h
>>> index 99bfdd526ff..51a7576a56a 100644
>>> --- a/gdb/defs.h
>>> +++ b/gdb/defs.h
>>> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>>>    typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>>>                         int read, int write, int exec,
>>> -                     int modified, void *data);
>>> +                     int modified, bool memory_tagged,
>>> +                     void *data);
>>>    /* * Possible lvalue types.  Like enum language, this should be in
>>>       value.h, but needs to be here for the same reason.  */
>>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>>> index 38ad2ac32b0..36f10f20cfb 100644
>>> --- a/gdb/doc/gdb.texinfo
>>> +++ b/gdb/doc/gdb.texinfo
>>> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>>>    option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>>>    documentation in the Linux kernel.
>>> +@value{GDBN} supports dumping memory tag data to core files through the
>>> +@command{gcore} command and reading memory tag data from core files generated
>>> +by the @command{gcore} command or the Linux kernel.
>>> +
>>> +When a process uses memory-mapped pages protected by memory tags (for
>>> +example, AArch64 MTE), this additional information will be recorded in
>>> +the core file in the event of a crash or if @value{GDBN} generates a core file
>>> +from the current process state.
>>> +
>>> +The memory tag data will be used so developers can display the memory
>>> +tags from a particular memory region (using the @samp{m} modifier to the
>>> +@command{x} command, using the @command{print} command or using the various
>>> +@command{memory-tag} subcommands.
>>> +
>>> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
>>> +information automatically from the core file, and will show one of the above
>>> +messages depending on whether the synchronous or asynchronous mode is selected.
>>> +@xref{Memory Tagging}. @xref{Memory}.
>>> +
>>>    @node i386
>>>    @subsection x86 Architecture-specific Issues
>>> diff --git a/gdb/gcore.c b/gdb/gcore.c
>>> index fdb22b72a07..b81ef81ab84 100644
>>> --- a/gdb/gcore.c
>>> +++ b/gdb/gcore.c
>>> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>>      int p_flags = 0;
>>>      int p_type = 0;
>>> +  /* Memory tag segments have already been handled by the architecture, as
>>> +     those contain arch-specific information.  If we have one of those, just
>>> +     return.  */
>>> +  if (startswith (bfd_section_name (osec), "memtag"))
>>> +    return;
>>> +
>>>      /* FIXME: these constants may only be applicable for ELF.  */
>>>      if (startswith (bfd_section_name (osec), "load"))
>>>        p_type = PT_LOAD;
>>> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>>    static int
>>>    gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>>> -               int write, int exec, int modified, void *data)
>>> +               int write, int exec, int modified, bool memory_tagged,
>>> +               void *data)
>>>    {
>>>      bfd *obfd = (bfd *) data;
>>>      asection *osec;
>>> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>>>      return 0;
>>>    }
>>> +/* gdbarch_find_memory_region callback for creating a memory tag section.
>>> +   DATA is 'bfd *' for the core file GDB is creating.  */
>>> +
>>> +static int
>>> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
>>> +                      int read, int write, int exec,
>>> +                      int modified, bool memory_tagged,
>>> +                      void *data)
>>> +{
>>> +  /* Are there memory tags in this particular memory map entry?  */
>>> +  if (!memory_tagged)
>>> +    return 0;
>>> +
>>> +  bfd *obfd = (bfd *) data;
>>> +
>>> +  /* Ask the architecture to create a memory tag section for this particular
>>> +     memory map entry.  It will be populated with contents later, as we can't
>>> +     start writing the contents before we have all the sections sorted out.  */
>>> +  asection *memtag_section
>>> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
>>> +
>>> +  if (memtag_section == nullptr)
>>> +    {
>>> +      warning (_("Couldn't make gcore memory tag segment: %s"),
>>> +           bfd_errmsg (bfd_get_error ()));
>>> +      return 1;
>>> +    }
>>> +
>>> +  if (info_verbose)
>>> +    {
>>> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
>>> +                  "at %s\n",
>>> +          plongest (bfd_section_size (memtag_section)),
>>> +          paddress (target_gdbarch (), vaddr));
>>> +    }
>>> +
>>> +  return 0;
>>> +}
>>> +
>>>    int
>>>    objfile_find_memory_regions (struct target_ops *self,
>>>                     find_memory_region_ftype func, void *obfd)
>>> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>>                   (flags & SEC_READONLY) == 0, /* Writable.  */
>>>                   (flags & SEC_CODE) != 0, /* Executable.  */
>>>                   1, /* MODIFIED is unknown, pass it as true.  */
>>> +               false, /* No memory tags in the object file.  */
>>>                   obfd);
>>>            if (ret != 0)
>>>              return ret;
>>> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>>             1, /* Stack section will be writable.  */
>>>             0, /* Stack section will not be executable.  */
>>>             1, /* Stack section will be modified.  */
>>> +         false, /* No memory tags in the object file.  */
>>>             obfd);
>>>      /* Make a heap segment.  */
>>> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>>             1, /* Heap section will be writable.  */
>>>             0, /* Heap section will not be executable.  */
>>>             1, /* Heap section will be modified.  */
>>> +         false, /* No memory tags in the object file.  */
>>>             obfd);
>>>      return 0;
>>> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>>>        }
>>>    }
>>> +/* Callback to copy contents to a particular memory tag section.  */
>>> +
>>> +static void
>>> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
>>> +{
>>> +  /* We are only interested in "memtag" sections.  */
>>> +  if (!startswith (bfd_section_name (osec), "memtag"))
>>> +    return;
>>> +
>>> +  /* Fill the section with memory tag contents.  */
>>> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
>>> +    error (_("Failed to fill memory tag section for core file."));
>>> +}
>>> +
>>>    static int
>>>    gcore_memory_sections (bfd *obfd)
>>>    {
>>> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>>>        return 0;            /* FIXME: error return/msg?  */
>>>        }
>>> +  /* Take care of dumping memory tags, if there are any.  */
>>> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
>>> +      || gdbarch_find_memory_regions (target_gdbarch (),
>>> +                      gcore_create_memtag_section_callback,
>>> +                      obfd) != 0)
>>> +    {
>>> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
>>> +                      obfd) != 0)
>>> +    return 0;
>>> +    }
>>> +
>>>      /* Record phdrs for section-to-segment mapping.  */
>>>      for (asection *sect : gdb_bfd_sections (obfd))
>>>        make_output_phdrs (obfd, sect);
>>> -  /* Copy memory region contents.  */
>>> +  /* Copy memory region and memory tag contents.  */
>>>      for (asection *sect : gdb_bfd_sections (obfd))
>>> -    gcore_copy_callback (obfd, sect);
>>> +    {
>>> +      gcore_copy_callback (obfd, sect);
>>> +      gcore_copy_memtag_section_callback (obfd, sect);
>>> +    }
>>>      return 1;
>>>    }
>>> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
>>> index e8f20c83ff0..6fa1b7591db 100644
>>> --- a/gdb/gdbarch-components.py
>>> +++ b/gdb/gdbarch-components.py
>>> @@ -1522,6 +1522,41 @@ Find core file memory regions
>>>        invalid=True,
>>>    )
>>> +Method(
>>> +    comment="""
>>> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
>>> +""",
>>> +    type="asection *",
>>> +    name="create_memtag_section",
>>> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>> +Method(
>>> +    comment="""
>>> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
>>> +""",
>>> +    type="bool",
>>> +    name="fill_memtag_section",
>>> +    params=[("asection *", "osec")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>> +Method(
>>> +    comment="""
>>> +Decode a memory tag SECTION and return the tags of type TYPE contained in
>>> +the memory range [ADDRESS, ADDRESS + LENGTH).
>>> +If no tags were found, return an empty vector.
>>> +""",
>>> +    type="gdb::byte_vector",
>>> +    name="decode_memtag_section",
>>> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
>>> +    predicate=True,
>>> +    invalid=True,
>>> +)
>>> +
>>>    Method(
>>>        comment="""
>>>    Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
>>> index 882b9057b1a..1d19f51f21d 100644
>>> --- a/gdb/gdbarch-gen.h
>>> +++ b/gdb/gdbarch-gen.h
>>> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>>>    extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>>>    extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
>>> +
>>> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
>>> +
>>> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>>> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>>> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
>>> +
>>> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
>>> +
>>> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
>>> +
>>> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
>>> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
>>> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
>>> +
>>> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
>>> +   the memory range [ADDRESS, ADDRESS + LENGTH).
>>> +   If no tags were found, return an empty vector. */
>>> +
>>> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
>>> +
>>> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>>> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>>> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
>>> +
>>>    /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>>       core file into buffer READBUF with length LEN.  Return the number of bytes read
>>>       (zero indicates failure).
>>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>>> index a588bdef61a..f5dbacb14e7 100644
>>> --- a/gdb/gdbarch.c
>>> +++ b/gdb/gdbarch.c
>>> @@ -171,6 +171,9 @@ struct gdbarch
>>>      gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>>>      gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>>      gdbarch_find_memory_regions_ftype *find_memory_regions;
>>> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
>>> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
>>> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>>>      gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>>      gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>>>      gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>>> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>>      /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>>      /* Skip verify of make_corefile_notes, has predicate.  */
>>>      /* Skip verify of find_memory_regions, has predicate.  */
>>> +  /* Skip verify of create_memtag_section, has predicate.  */
>>> +  /* Skip verify of fill_memtag_section, has predicate.  */
>>> +  /* Skip verify of decode_memtag_section, has predicate.  */
>>>      /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>>      /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>>      /* Skip verify of core_pid_to_str, has predicate.  */
>>> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>>>      gdb_printf (file,
>>>                          "gdbarch_dump: find_memory_regions = <%s>\n",
>>>                          host_address_to_string (gdbarch->find_memory_regions));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
>>> +                      gdbarch_create_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
>>> +                      host_address_to_string (gdbarch->create_memtag_section));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
>>> +                      gdbarch_fill_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
>>> +                      host_address_to_string (gdbarch->fill_memtag_section));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
>>> +                      gdbarch_decode_memtag_section_p (gdbarch));
>>> +  gdb_printf (file,
>>> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
>>> +                      host_address_to_string (gdbarch->decode_memtag_section));
>>>      gdb_printf (file,
>>>                          "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>>>                          gdbarch_core_xfer_shared_libraries_p (gdbarch));
>>> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>>      gdbarch->find_memory_regions = find_memory_regions;
>>>    }
>>> +bool
>>> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->create_memtag_section != NULL;
>>> +}
>>> +
>>> +asection *
>>> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->create_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
>>> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
>>> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
>>> +{
>>> +  gdbarch->create_memtag_section = create_memtag_section;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->fill_memtag_section != NULL;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
>>> +  return gdbarch->fill_memtag_section (gdbarch, osec);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
>>> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
>>> +{
>>> +  gdbarch->fill_memtag_section = fill_memtag_section;
>>> +}
>>> +
>>> +bool
>>> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  return gdbarch->decode_memtag_section != NULL;
>>> +}
>>> +
>>> +gdb::byte_vector
>>> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
>>> +{
>>> +  gdb_assert (gdbarch != NULL);
>>> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
>>> +  if (gdbarch_debug >= 2)
>>> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
>>> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
>>> +}
>>> +
>>> +void
>>> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
>>> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
>>> +{
>>> +  gdbarch->decode_memtag_section = decode_memtag_section;
>>> +}
>>> +
>>>    bool
>>>    gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>>    {
>>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>>> index 4e728a06e7e..8a83ed320cf 100644
>>> --- a/gdb/linux-tdep.c
>>> +++ b/gdb/linux-tdep.c
>>> @@ -42,6 +42,7 @@
>>>    #include "gcore.h"
>>>    #include "gcore-elf.h"
>>>    #include "solib-svr4.h"
>>> +#include "memtag.h"
>>>    #include <ctype.h>
>>>    #include <unordered_map>
>>> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>>>                            ULONGEST offset, ULONGEST inode,
>>>                            int read, int write,
>>>                            int exec, int modified,
>>> +                        bool memory_tagged,
>>>                            const char *filename,
>>>                            void *data);
>>> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>>>      return smaps;
>>>    }
>>> -/* See linux-tdep.h.  */
>>> +/* Helper that checks if an address is in a memory tag page for a live
>>> +   process.  */
>>> -bool
>>> -linux_address_in_memtag_page (CORE_ADDR address)
>>> +static bool
>>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>>    {
>>>      if (current_inferior ()->fake_pid_p)
>>>        return false;
>>> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>>      return false;
>>>    }
>>> +/* Helper that checks if an address is in a memory tag page for a core file
>>> +   process.  */
>>> +
>>> +static bool
>>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>>> +{
>>> +  if (core_bfd == nullptr)
>>> +    return false;
>>> +
>>> +  memtag_section_info info;
>>> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
>>> +}
>>> +
>>> +/* See linux-tdep.h.  */
>>> +
>>> +bool
>>> +linux_address_in_memtag_page (CORE_ADDR address)
>>> +{
>>> +  if (!target_has_execution ())
>>> +    return linux_core_file_address_in_memtag_page (address);
>>> +
>>> +  return linux_process_address_in_memtag_page (address);
>>> +}
>>> +
>>>    /* List memory regions in the inferior for a corefile.  */
>>>    static int
>>> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>>>            map.offset, map.inode, map.read, map.write, map.exec,
>>>            1, /* MODIFIED is true because we want to dump
>>>                  the mapping.  */
>>> +        map.vmflags.memory_tagging != 0,
>>>            map.filename.c_str (), obfd);
>>>        }
>>>        }
>>> @@ -1621,12 +1649,14 @@ static int
>>>    linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>>>                     ULONGEST offset, ULONGEST inode,
>>>                     int read, int write, int exec, int modified,
>>> +                 bool memory_tagged,
>>>                     const char *filename, void *arg)
>>>    {
>>>      struct linux_find_memory_regions_data *data
>>>        = (struct linux_find_memory_regions_data *) arg;
>>> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
>>> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
>>> +             data->obfd);
>>>    }
>>>    /* A variant of linux_find_memory_regions_full that is suitable as the
>>> @@ -1675,6 +1705,7 @@ static int
>>>    linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>>>                      ULONGEST offset, ULONGEST inode,
>>>                      int read, int write, int exec, int modified,
>>> +                  bool memory_tagged,
>>>                      const char *filename, void *data)
>>>    {
>>>      struct linux_make_mappings_data *map_data
>>> diff --git a/gdb/memtag.c b/gdb/memtag.c
>>> new file mode 100644
>>> index 00000000000..af86137c49d
>>> --- /dev/null
>>> +++ b/gdb/memtag.c
>>> @@ -0,0 +1,61 @@
>>> +/* GDB generic memory tagging functions.
>>> +
>>> +   Copyright (C) 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 "memtag.h"
>>> +#include "bfd.h"
>>> +
>>> +/* See memtag.h */
>>> +
>>> +bool
>>> +get_next_core_memtag_section (bfd *abfd, asection *section,
>>> +                  CORE_ADDR address, memtag_section_info &info)
>>> +{
>>> +  /* If the caller provided no SECTION to start from, search from the
>>> +     beginning.  */
>>> +  if (section == nullptr)
>>> +    section = bfd_get_section_by_name (abfd, "memtag");
>>> +
>>> +  /* Go through all the memtag sections and figure out if ADDRESS
>>> +     falls within one of the memory ranges that contain tags.  */
>>> +  while (section != nullptr)
>>> +    {
>>> +      size_t memtag_range_size = section->rawsize;
>>> +      size_t tags_size = bfd_section_size (section);
>>> +
>>> +      /* Empty memory range and empty tag dump should not happen.  */
>>> +      gdb_assert (memtag_range_size != 0);
>>> +      gdb_assert (tags_size != 0);
>>> +
>>> +      CORE_ADDR start_address = bfd_section_vma (section);
>>> +      CORE_ADDR end_address = start_address + memtag_range_size;
>>> +
>>> +      /* Is the address within [start_address, end_address)?  */
>>> +      if (address >= start_address
>>> +      && address < end_address)
>>> +    {
>>> +      info.start_address = start_address;
>>> +      info.end_address = end_address;
>>> +      info.memtag_section = section;
>>> +      return true;
>>> +    }
>>> +      section = bfd_get_next_section_by_name (abfd, section);
>>> +    }
>>> +  return false;
>>> +}
>>> diff --git a/gdb/memtag.h b/gdb/memtag.h
>>> new file mode 100644
>>> index 00000000000..fe908c1e5e3
>>> --- /dev/null
>>> +++ b/gdb/memtag.h
>>> @@ -0,0 +1,50 @@
>>> +/* GDB generic memory tagging definitions.
>>> +   Copyright (C) 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/>.  */
>>> +
>>> +#ifndef MEMTAG_H
>>> +#define MEMTAG_H
>>> +
>>> +#include "bfd.h"
>>> +
>>> +struct memtag_section_info
>>> +{
>>> +  /* The start address of the tagged memory range.  */
>>> +  CORE_ADDR start_address;
>>> +  /* The final address of the tagged memory range.  */
>>> +  CORE_ADDR end_address;
>>> +  /* The section containing tags for the memory range
>>> +     [start_address, end_address).  */
>>> +  asection *memtag_section;
>>> +};
>>> +
>>> +/* Helper function to walk through memory tag sections in a core file.
>>> +
>>> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
>>> +   otherwise.
>>> +
>>> +   If SECTION is provided, search from that section onwards. If SECTION is
>>> +   nullptr, then start a new search.
>>> +
>>> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
>>> +   about such section.  Otherwise leave it unchanged.  */
>>> +
>>> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
>>> +                   CORE_ADDR address,
>>> +                   memtag_section_info &info);
>>> +
>>> +#endif /* MEMTAG_H */
>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>> new file mode 100644
>>> index 00000000000..b20ebcff424
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>> @@ -0,0 +1,93 @@
>>> +/* This test program is part of GDB, the GNU debugger.
>>> +
>>> +   Copyright 2021 Free Software Foundation, Inc.
>>> +
>>> +   This program is free software; you can redistribute it and/or modify
>>> +   it under the terms of the GNU General Public License as published by
>>> +   the Free Software Foundation; either version 3 of the License, or
>>> +   (at your option) any later version.
>>> +
>>> +   This program is distributed in the hope that it will be useful,
>>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> +   GNU General Public License for more details.
>>> +
>>> +   You should have received a copy of the GNU General Public License
>>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>>> +
>>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
>>> +
>>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>>> +   Extension from the Linux Kernel, found in the sources in
>>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>>> +
>>> +#include <errno.h>
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +#include <unistd.h>
>>> +#include <sys/auxv.h>
>>> +#include <sys/mman.h>
>>> +#include <sys/prctl.h>
>>> +
>>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>>> +#define HWCAP2_MTE              (1 << 18)
>>> +
>>> +/* From arch/arm64/include/uapi/asm/mman.h */
>>> +#define PROT_MTE  0x20
>>> +
>>> +/* From include/uapi/linux/prctl.h */
>>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>>> +#define PR_TAGGED_ADDR_ENABLE    (1UL << 0)
>>> +#define PR_MTE_TCF_SHIFT    1
>>> +#define PR_MTE_TCF_SYNC        (1UL << PR_MTE_TCF_SHIFT)
>>> +#define PR_MTE_TAG_SHIFT    3
>>> +
>>> +void
>>> +access_memory (unsigned char *tagged_ptr)
>>> +{
>>> +  tagged_ptr[0] = 'a';
>>> +}
>>> +
>>> +int
>>> +main (int argc, char **argv)
>>> +{
>>> +  unsigned char *tagged_ptr;
>>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>>> +
>>> +  /* Bail out if MTE is not supported.  */
>>> +  if (!(hwcap2 & HWCAP2_MTE))
>>> +    return 1;
>>> +
>>> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
>>> +     allow all non-zero tags in the randomly generated set.  */
>>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>>> +         PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>>> +         | (0xfffe << PR_MTE_TAG_SHIFT),
>>> +         0, 0, 0))
>>> +    {
>>> +      perror ("prctl () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  /* Create a mapping that will have PROT_MTE set.  */
>>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>>> +             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>>> +  if (tagged_ptr == MAP_FAILED)
>>> +    {
>>> +      perror ("mmap () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  /* Enable MTE on the above anonymous mmap.  */
>>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>>> +    {
>>> +      perror ("mprotect () failed");
>>> +      return 1;
>>> +    }
>>> +
>>> +  access_memory (tagged_ptr);
>>> +
>>> +  return 0;
>>> +}
>>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>> new file mode 100644
>>> index 00000000000..8a19c4b449e
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>> @@ -0,0 +1,107 @@
>>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>>> +#
>>> +# This program is free software; you can redistribute it and/or modify
>>> +# it under the terms of the GNU General Public License as published by
>>> +# the Free Software Foundation; either version 3 of the License, or
>>> +# (at your option) any later version.
>>> +#
>>> +# This program is distributed in the hope that it will be useful,
>>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>> +# GNU General Public License for more details.
>>> +#
>>> +# You should have received a copy of the GNU General Public License
>>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>>> +
>>> +# This file is part of the gdb testsuite.
>>> +
>>> +# Test generating and reading a core file with MTE memory tags.
>>> +
>>> +if {![is_aarch64_target]} {
>>> +    verbose "Skipping ${gdb_test_file_name}."
>>> +    return
>>> +}
>>> +
>>> +standard_testfile
>>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>>> +    return -1
>>> +}
>>> +
>>> +if ![runto_main] {
>>> +    untested "could not run to main"
>>> +    return -1
>>> +}
>>> +
>>> +# Targets that don't support memory tagging should not execute the
>>> +# runtime memory tagging tests.
>>> +if {![supports_memtag]} {
>>> +    unsupported "memory tagging unsupported"
>>> +    return -1
>>> +}
>>> +
>>> +gdb_breakpoint "access_memory"
>>> +
>>> +if [gdb_continue "access_memory"] {
>>> +    return -1
>>> +}
>>> +
>>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>> +    set index [expr [expr 15 - $i] * 16]
>>> +    set tag [format "%02x" $i]
>>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>>> +         $atag_msg \
>>> +         "set memory tag of &tagged_ptr\[$index\] to $tag"
>>> +}
>>> +
>>> +# Run until a crash and confirm GDB displays memory tag violation
>>> +# information.
>>> +gdb_test "continue" \
>>> +    [multi_line \
>>> +    "Program received signal SIGSEGV, Segmentation fault" \
>>> +    "Memory tag violation while accessing address $hex" \
>>> +    "Allocation tag $hex" \
>>> +    "Logical tag $hex\." \
>>> +    "$hex in access_memory \\(.*\\) at .*" \
>>> +    ".*tagged_ptr\\\[0\\\] = 'a';"] \
>>> +     "display tag violation information for live process"
>>> +
>>> +# Generate the core file.
>>> +set core_filename [standard_output_file "$testfile.core"]
>>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>>> +
>>> +if { !$core_generated } {
>>> +    return -1
>>> +}
>>> +
>>> +clean_restart $binfile
>>> +
>>> +# Load the core file and make sure we see the tag violation fault
>>> +# information.
>>> +gdb_test "core $core_filename" \
>>> +    [multi_line \
>>> +    "Core was generated by.*\." \
>>> +    "Program terminated with signal SIGSEGV, Segmentation fault" \
>>> +    "Memory tag violation while accessing address $hex" \
>>> +    "Allocation tag 0xf" \
>>> +    "Logical tag 0x0\." \
>>> +    "#0.*$hex in access_memory \\(.*\\) at .*" \
>>> +    ".*tagged_ptr\\\[0\\\] = 'a';"] \
>>> +     "core file shows tag violation information"
>>> +
>>> +# Make sure we have the tag_ctl register.
>>> +gdb_test "info register tag_ctl" \
>>> +     "tag_ctl.*$hex.*${::decimal}" \
>>> +     "tag_ctl is available"
>>> +
>>> +# Check if the tag granules have the expected values.  If they do, that
>>> +# means the core file saved the tags properly and GDB has read them
>>> +# correctly.
>>> +for {set i 15} {$i >= 0} {incr i -1} {
>>> +    set index [expr [expr 15 - $i] * 16]
>>> +    set tag [format "%x" $i]
>>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>>> +         "= 0x$tag" \
>>> +         "memory tag of &tagged_ptr\[$index\] is correct"
>>> +}
>>
> 
> 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-18 12:46   ` Luis Machado
  2022-05-18 13:58     ` John Baldwin
@ 2022-05-23  9:49     ` Luis Machado
  1 sibling, 0 replies; 47+ messages in thread
From: Luis Machado @ 2022-05-23  9:49 UTC (permalink / raw)
  To: gdb-patches

Ping?

On 5/18/22 13:46, Luis Machado via Gdb-patches wrote:
> Ping? The binutils patch has been approved, but I'd like to push both at the same time.
> 
> On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
>> v4:
>>
>> - Updated documentation (added cross-references).
>> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>>    PT_AARCH64_MEMTAG_MTE.
>>
>> v3:
>>
>> - Updated NEWS and documentation to be more thorough.
>>
>> v2:
>>
>> - Rework memory tag section handling to use generic section fields.
>>
>> -- 
>>
>> Teach GDB how to dump memory tags for AArch64 when using the gcore command
>> and how to read memory tag data back from a core file generated by GDB
>> (via gcore) or by the Linux kernel.
>>
>> The format is documented in the Linux Kernel documentation [1].
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
>> of those segments when reading the core file back.
>>
>> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
>> are stored packed as 2 tags per byte.
>>
>> When reading the data back, the tags are unpacked.
>>
>> I've added a new testcase to exercise the feature.
>>
>> Build-tested with --enable-targets=all and regression tested on aarch64-linux
>> Ubuntu 20.04.
>>
>> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
>> ---
>>   gdb/Makefile.in                              |   1 +
>>   gdb/NEWS                                     |  10 ++
>>   gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>>   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>>   gdb/arch/aarch64-mte-linux.h                 |  10 ++
>>   gdb/corelow.c                                |  62 +++++++
>>   gdb/defs.h                                   |   3 +-
>>   gdb/doc/gdb.texinfo                          |  19 +++
>>   gdb/gcore.c                                  |  83 ++++++++-
>>   gdb/gdbarch-components.py                    |  35 ++++
>>   gdb/gdbarch-gen.h                            |  26 +++
>>   gdb/gdbarch.c                                |  96 +++++++++++
>>   gdb/linux-tdep.c                             |  39 ++++-
>>   gdb/memtag.c                                 |  61 +++++++
>>   gdb/memtag.h                                 |  50 ++++++
>>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>>   17 files changed, 910 insertions(+), 8 deletions(-)
>>   create mode 100644 gdb/memtag.c
>>   create mode 100644 gdb/memtag.h
>>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>
>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>> index 418094775a5..fac9364bea4 100644
>> --- a/gdb/Makefile.in
>> +++ b/gdb/Makefile.in
>> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>>       memattr.c \
>>       memory-map.c \
>>       memrange.c \
>> +    memtag.c \
>>       minidebug.c \
>>       minsyms.c \
>>       mipsread.c \
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 982f4a1a18c..3d925dc3663 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,16 @@
>>   *** Changes since GDB 12
>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>> +  reading memory tag data for AArch64 MTE from core files generated by
>> +  the gcore command or the Linux kernel.
>> +
>> +  When a process uses memory-mapped pages protected by memory tags (for
>> +  example, AArch64 MTE), this additional information will be recorded in
>> +  the core file in the event of a crash or if GDB generates a core file
>> +  from the current process state.  GDB will show this additional information
>> +  automatically, or through one of the memory-tag subcommands.
>> +
>>   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>>   * Remove support for building against Python 2, it is now only possible to
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index 55094b3d88b..12d98e71796 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -53,6 +53,9 @@
>>   #include "gdbsupport/selftest.h"
>> +#include "elf/common.h"
>> +#include "elf/aarch64.h"
>> +
>>   /* Signal frame handling.
>>         +------------+  ^
>> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>       }
>>   }
>> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
>> +
>> +static asection *
>> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
>> +                     CORE_ADDR address, size_t size)
>> +{
>> +  gdb_assert (obfd != nullptr);
>> +  gdb_assert (size > 0);
>> +
>> +  /* Create the section and associated program header.  */
>> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
>> +
>> +  if (mte_section == nullptr)
>> +    return nullptr;
>> +
>> +  bfd_set_section_vma (mte_section, address);
>> +  /* The size of the memory range covered by the memory tags.  We reuse the
>> +     section's rawsize field for this purpose.  */
>> +  mte_section->rawsize = size;
>> +  /* Tags are stored packed as 2 tags per byte.  */
>> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
>> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
>> +     refuse to write data to this section.  */
>> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
>> +
>> +  /* Store program header information.  */
>> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
>> +           &mte_section);
>> +
>> +  return mte_section;
>> +}
>> +
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
>> +
>> +static bool
>> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  size_t segment_size = osec->rawsize;
>> +  CORE_ADDR start_address = bfd_section_vma (osec);
>> +  CORE_ADDR end_address = start_address + segment_size;
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
>> +                          AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return true;
>> +
>> +  CORE_ADDR address = start_address;
>> +
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +    = (granules >= MAX_TAGS_TO_TRANSFER)?
>> +      MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +      granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +                 static_cast<int> (memtag_type::allocation)))
>> +    {
>> +      warning (_("Failed to read MTE tags from memory range [%s,%s)."),
>> +             phex_nz (start_address, sizeof (start_address)),
>> +             phex_nz (end_address, sizeof (end_address)));
>> +      return false;
>> +    }
>> +
>> +      /* Transfer over the tags that have been read.  */
>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>> +
>> +      /* Adjust the remaining granules and starting address.  */
>> +      granules -= tags_read.size ();
>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> +    }
>> +
>> +  /* Pack the MTE tag bits.  */
>> +  aarch64_mte_pack_tags (tags);
>> +
>> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
>> +                 0, tags.size ()))
>> +    {
>> +      warning (_("Failed to write %s bytes of corefile memory "
>> +         "tag content (%s)."),
>> +           pulongest (tags.size ()),
>> +           bfd_errmsg (bfd_get_error ()));
>> +    }
>> +  return true;
>> +}
>> +
>> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
>> +   hook.  Decode a memory tag section and return the requested tags.
>> +
>> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
>> +   range.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
>> +                     bfd_section *section,
>> +                     int type,
>> +                     CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (section != nullptr);
>> +
>> +  /* The requested address must not be less than section->vma.  */
>> +  gdb_assert (section->vma <= address);
>> +
>> +  /* Figure out how many tags we need to fetch in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
>> +                          AARCH64_MTE_GRANULE_SIZE);
>> +  /* Sanity check.  */
>> +  gdb_assert (granules > 0);
>> +
>> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
>> +  size_t granules_from_vma
>> +    = aarch64_mte_get_tag_granules (section->vma,
>> +                    address - section->vma + length,
>> +                    AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
>> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
>> +
>> +  /* Figure out the starting offset into the packed tags data.  */
>> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
>> +
>> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
>> +                 offset, tags.size ()))
>> +    error (_("Couldn't read contents from memtag section."));
>> +
>> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
>> +     returning.  */
>> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
>> +  aarch64_mte_unpack_tags (tags, skip_first);
>> +
>> +  /* Resize to the exact number of tags that was requested.  */
>> +  tags.resize (granules);
>> +
>> +  return tags;
>> +}
>> +
>>   static void
>>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>   {
>> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>         set_gdbarch_report_signal_info (gdbarch,
>>                         aarch64_linux_report_signal_info);
>> +
>> +      /* Core file helpers.  */
>> +
>> +      /* Core file helper to create a memory tag section for a particular
>> +     PT_LOAD segment.  */
>> +      set_gdbarch_create_memtag_section
>> +    (gdbarch, aarch64_linux_create_memtag_section);
>> +
>> +      /* Core file helper to fill a memory tag section with tag data.  */
>> +      set_gdbarch_fill_memtag_section
>> +    (gdbarch, aarch64_linux_fill_memtag_section);
>> +
>> +      /* Core file helper to decode a memory tag section.  */
>> +      set_gdbarch_decode_memtag_section (gdbarch,
>> +                     aarch64_linux_decode_memtag_section);
>>       }
>>     /* Initialize the aarch64_linux_record_tdep.  */
>> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
>> index fc7a8cc00f7..3af6f364e91 100644
>> --- a/gdb/arch/aarch64-mte-linux.c
>> +++ b/gdb/arch/aarch64-mte-linux.c
>> @@ -21,6 +21,62 @@
>>   /* See arch/aarch64-mte-linux.h */
>> +void
>> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
>> +{
>> +  /* Nothing to pack?  */
>> +  if (tags.empty ())
>> +    return;
>> +
>> +  /* If the tags vector has an odd number of elements, add another
>> +     zeroed-out element to make it even.  This facilitates packing.  */
>> +  if ((tags.size () % 2) != 0)
>> +    tags.emplace_back (0);
>> +
>> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
>> +       unpacked += 2, packed++)
>> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
>> +
>> +  /* Now we have half the size.  */
>> +  tags.resize (tags.size () / 2);
>> +}
>> +
>> +/* See arch/aarch64-mte-linux.h */
>> +
>> +void
>> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
>> +{
>> +  /* Nothing to unpack?  */
>> +  if (tags.empty ())
>> +    return;
>> +
>> +  /* An unpacked MTE tags vector will have twice the number of elements
>> +     compared to an unpacked one.  */
>> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
>> +
>> +  int unpacked = 0, packed = 0;
>> +
>> +  if (skip_first)
>> +    {
>> +      /* We are not interested in the first unpacked element, just discard
>> +     it.  */
>> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
>> +      unpacked++;
>> +      packed++;
>> +    }
>> +
>> +  for (; packed < tags.size (); unpacked += 2, packed++)
>> +    {
>> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
>> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
>> +    }
>> +
>> +  /* Update the original tags vector.  */
>> +  tags = std::move (unpacked_tags);
>> +}
>> +
>> +/* See arch/aarch64-mte-linux.h */
>> +
>>   size_t
>>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>>   {
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index d158926feff..8a145b447aa 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>   /* We have one tag per 16 bytes of memory.  */
>>   #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>      It is always possible to get the logical tag.  */
>>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
>> +   2 tags per byte and resize the vector.  */
>> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
>> +
>> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
>> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
>> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
>> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
>> +
>>   #endif /* ARCH_AARCH64_LINUX_H */
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index 8c33fb7ebb2..8b8994f80db 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -52,6 +52,7 @@
>>   #include <unordered_set>
>>   #include "gdbcmd.h"
>>   #include "xml-tdesc.h"
>> +#include "memtag.h"
>>   #ifndef O_LARGEFILE
>>   #define O_LARGEFILE 0
>> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>>     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;
>> +
>>     /* A few helpers.  */
>>     /* Getter, see variable definition.  */
>> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* 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 (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;
>> +}
>> +
>>   /* Get a pointer to the current core target.  If not connected to a
>>      core target, return NULL.  */
>> diff --git a/gdb/defs.h b/gdb/defs.h
>> index 99bfdd526ff..51a7576a56a 100644
>> --- a/gdb/defs.h
>> +++ b/gdb/defs.h
>> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>>   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>>                        int read, int write, int exec,
>> -                     int modified, void *data);
>> +                     int modified, bool memory_tagged,
>> +                     void *data);
>>   /* * Possible lvalue types.  Like enum language, this should be in
>>      value.h, but needs to be here for the same reason.  */
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 38ad2ac32b0..36f10f20cfb 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>>   documentation in the Linux kernel.
>> +@value{GDBN} supports dumping memory tag data to core files through the
>> +@command{gcore} command and reading memory tag data from core files generated
>> +by the @command{gcore} command or the Linux kernel.
>> +
>> +When a process uses memory-mapped pages protected by memory tags (for
>> +example, AArch64 MTE), this additional information will be recorded in
>> +the core file in the event of a crash or if @value{GDBN} generates a core file
>> +from the current process state.
>> +
>> +The memory tag data will be used so developers can display the memory
>> +tags from a particular memory region (using the @samp{m} modifier to the
>> +@command{x} command, using the @command{print} command or using the various
>> +@command{memory-tag} subcommands.
>> +
>> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
>> +information automatically from the core file, and will show one of the above
>> +messages depending on whether the synchronous or asynchronous mode is selected.
>> +@xref{Memory Tagging}. @xref{Memory}.
>> +
>>   @node i386
>>   @subsection x86 Architecture-specific Issues
>> diff --git a/gdb/gcore.c b/gdb/gcore.c
>> index fdb22b72a07..b81ef81ab84 100644
>> --- a/gdb/gcore.c
>> +++ b/gdb/gcore.c
>> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>     int p_flags = 0;
>>     int p_type = 0;
>> +  /* Memory tag segments have already been handled by the architecture, as
>> +     those contain arch-specific information.  If we have one of those, just
>> +     return.  */
>> +  if (startswith (bfd_section_name (osec), "memtag"))
>> +    return;
>> +
>>     /* FIXME: these constants may only be applicable for ELF.  */
>>     if (startswith (bfd_section_name (osec), "load"))
>>       p_type = PT_LOAD;
>> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>   static int
>>   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>> -               int write, int exec, int modified, void *data)
>> +               int write, int exec, int modified, bool memory_tagged,
>> +               void *data)
>>   {
>>     bfd *obfd = (bfd *) data;
>>     asection *osec;
>> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>>     return 0;
>>   }
>> +/* gdbarch_find_memory_region callback for creating a memory tag section.
>> +   DATA is 'bfd *' for the core file GDB is creating.  */
>> +
>> +static int
>> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
>> +                      int read, int write, int exec,
>> +                      int modified, bool memory_tagged,
>> +                      void *data)
>> +{
>> +  /* Are there memory tags in this particular memory map entry?  */
>> +  if (!memory_tagged)
>> +    return 0;
>> +
>> +  bfd *obfd = (bfd *) data;
>> +
>> +  /* Ask the architecture to create a memory tag section for this particular
>> +     memory map entry.  It will be populated with contents later, as we can't
>> +     start writing the contents before we have all the sections sorted out.  */
>> +  asection *memtag_section
>> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
>> +
>> +  if (memtag_section == nullptr)
>> +    {
>> +      warning (_("Couldn't make gcore memory tag segment: %s"),
>> +           bfd_errmsg (bfd_get_error ()));
>> +      return 1;
>> +    }
>> +
>> +  if (info_verbose)
>> +    {
>> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
>> +                  "at %s\n",
>> +          plongest (bfd_section_size (memtag_section)),
>> +          paddress (target_gdbarch (), vaddr));
>> +    }
>> +
>> +  return 0;
>> +}
>> +
>>   int
>>   objfile_find_memory_regions (struct target_ops *self,
>>                    find_memory_region_ftype func, void *obfd)
>> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>                  (flags & SEC_READONLY) == 0, /* Writable.  */
>>                  (flags & SEC_CODE) != 0, /* Executable.  */
>>                  1, /* MODIFIED is unknown, pass it as true.  */
>> +               false, /* No memory tags in the object file.  */
>>                  obfd);
>>           if (ret != 0)
>>             return ret;
>> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>            1, /* Stack section will be writable.  */
>>            0, /* Stack section will not be executable.  */
>>            1, /* Stack section will be modified.  */
>> +         false, /* No memory tags in the object file.  */
>>            obfd);
>>     /* Make a heap segment.  */
>> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>            1, /* Heap section will be writable.  */
>>            0, /* Heap section will not be executable.  */
>>            1, /* Heap section will be modified.  */
>> +         false, /* No memory tags in the object file.  */
>>            obfd);
>>     return 0;
>> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>>       }
>>   }
>> +/* Callback to copy contents to a particular memory tag section.  */
>> +
>> +static void
>> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
>> +{
>> +  /* We are only interested in "memtag" sections.  */
>> +  if (!startswith (bfd_section_name (osec), "memtag"))
>> +    return;
>> +
>> +  /* Fill the section with memory tag contents.  */
>> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
>> +    error (_("Failed to fill memory tag section for core file."));
>> +}
>> +
>>   static int
>>   gcore_memory_sections (bfd *obfd)
>>   {
>> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>>       return 0;            /* FIXME: error return/msg?  */
>>       }
>> +  /* Take care of dumping memory tags, if there are any.  */
>> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
>> +      || gdbarch_find_memory_regions (target_gdbarch (),
>> +                      gcore_create_memtag_section_callback,
>> +                      obfd) != 0)
>> +    {
>> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
>> +                      obfd) != 0)
>> +    return 0;
>> +    }
>> +
>>     /* Record phdrs for section-to-segment mapping.  */
>>     for (asection *sect : gdb_bfd_sections (obfd))
>>       make_output_phdrs (obfd, sect);
>> -  /* Copy memory region contents.  */
>> +  /* Copy memory region and memory tag contents.  */
>>     for (asection *sect : gdb_bfd_sections (obfd))
>> -    gcore_copy_callback (obfd, sect);
>> +    {
>> +      gcore_copy_callback (obfd, sect);
>> +      gcore_copy_memtag_section_callback (obfd, sect);
>> +    }
>>     return 1;
>>   }
>> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
>> index e8f20c83ff0..6fa1b7591db 100644
>> --- a/gdb/gdbarch-components.py
>> +++ b/gdb/gdbarch-components.py
>> @@ -1522,6 +1522,41 @@ Find core file memory regions
>>       invalid=True,
>>   )
>> +Method(
>> +    comment="""
>> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
>> +""",
>> +    type="asection *",
>> +    name="create_memtag_section",
>> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>> +Method(
>> +    comment="""
>> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
>> +""",
>> +    type="bool",
>> +    name="fill_memtag_section",
>> +    params=[("asection *", "osec")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>> +Method(
>> +    comment="""
>> +Decode a memory tag SECTION and return the tags of type TYPE contained in
>> +the memory range [ADDRESS, ADDRESS + LENGTH).
>> +If no tags were found, return an empty vector.
>> +""",
>> +    type="gdb::byte_vector",
>> +    name="decode_memtag_section",
>> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>>   Method(
>>       comment="""
>>   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
>> index 882b9057b1a..1d19f51f21d 100644
>> --- a/gdb/gdbarch-gen.h
>> +++ b/gdb/gdbarch-gen.h
>> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
>> +
>> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
>> +
>> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
>> +
>> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
>> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
>> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
>> +
>> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
>> +   the memory range [ADDRESS, ADDRESS + LENGTH).
>> +   If no tags were found, return an empty vector. */
>> +
>> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
>> +
>>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>>      (zero indicates failure).
>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>> index a588bdef61a..f5dbacb14e7 100644
>> --- a/gdb/gdbarch.c
>> +++ b/gdb/gdbarch.c
>> @@ -171,6 +171,9 @@ struct gdbarch
>>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>     gdbarch_find_memory_regions_ftype *find_memory_regions;
>> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
>> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
>> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>     /* Skip verify of make_corefile_notes, has predicate.  */
>>     /* Skip verify of find_memory_regions, has predicate.  */
>> +  /* Skip verify of create_memtag_section, has predicate.  */
>> +  /* Skip verify of fill_memtag_section, has predicate.  */
>> +  /* Skip verify of decode_memtag_section, has predicate.  */
>>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>     /* Skip verify of core_pid_to_str, has predicate.  */
>> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>>     gdb_printf (file,
>>                         "gdbarch_dump: find_memory_regions = <%s>\n",
>>                         host_address_to_string (gdbarch->find_memory_regions));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
>> +                      gdbarch_create_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->create_memtag_section));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
>> +                      gdbarch_fill_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->fill_memtag_section));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
>> +                      gdbarch_decode_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->decode_memtag_section));
>>     gdb_printf (file,
>>                         "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>>                         gdbarch_core_xfer_shared_libraries_p (gdbarch));
>> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>     gdbarch->find_memory_regions = find_memory_regions;
>>   }
>> +bool
>> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->create_memtag_section != NULL;
>> +}
>> +
>> +asection *
>> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->create_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
>> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
>> +}
>> +
>> +void
>> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
>> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
>> +{
>> +  gdbarch->create_memtag_section = create_memtag_section;
>> +}
>> +
>> +bool
>> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->fill_memtag_section != NULL;
>> +}
>> +
>> +bool
>> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
>> +  return gdbarch->fill_memtag_section (gdbarch, osec);
>> +}
>> +
>> +void
>> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
>> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
>> +{
>> +  gdbarch->fill_memtag_section = fill_memtag_section;
>> +}
>> +
>> +bool
>> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->decode_memtag_section != NULL;
>> +}
>> +
>> +gdb::byte_vector
>> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
>> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
>> +}
>> +
>> +void
>> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
>> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
>> +{
>> +  gdbarch->decode_memtag_section = decode_memtag_section;
>> +}
>> +
>>   bool
>>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>   {
>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>> index 4e728a06e7e..8a83ed320cf 100644
>> --- a/gdb/linux-tdep.c
>> +++ b/gdb/linux-tdep.c
>> @@ -42,6 +42,7 @@
>>   #include "gcore.h"
>>   #include "gcore-elf.h"
>>   #include "solib-svr4.h"
>> +#include "memtag.h"
>>   #include <ctype.h>
>>   #include <unordered_map>
>> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>>                           ULONGEST offset, ULONGEST inode,
>>                           int read, int write,
>>                           int exec, int modified,
>> +                        bool memory_tagged,
>>                           const char *filename,
>>                           void *data);
>> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>>     return smaps;
>>   }
>> -/* See linux-tdep.h.  */
>> +/* Helper that checks if an address is in a memory tag page for a live
>> +   process.  */
>> -bool
>> -linux_address_in_memtag_page (CORE_ADDR address)
>> +static bool
>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>   {
>>     if (current_inferior ()->fake_pid_p)
>>       return false;
>> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>     return false;
>>   }
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> +   process.  */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (core_bfd == nullptr)
>> +    return false;
>> +
>> +  memtag_section_info info;
>> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
>> +}
>> +
>> +/* See linux-tdep.h.  */
>> +
>> +bool
>> +linux_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (!target_has_execution ())
>> +    return linux_core_file_address_in_memtag_page (address);
>> +
>> +  return linux_process_address_in_memtag_page (address);
>> +}
>> +
>>   /* List memory regions in the inferior for a corefile.  */
>>   static int
>> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>>           map.offset, map.inode, map.read, map.write, map.exec,
>>           1, /* MODIFIED is true because we want to dump
>>                 the mapping.  */
>> +        map.vmflags.memory_tagging != 0,
>>           map.filename.c_str (), obfd);
>>       }
>>       }
>> @@ -1621,12 +1649,14 @@ static int
>>   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>>                    ULONGEST offset, ULONGEST inode,
>>                    int read, int write, int exec, int modified,
>> +                 bool memory_tagged,
>>                    const char *filename, void *arg)
>>   {
>>     struct linux_find_memory_regions_data *data
>>       = (struct linux_find_memory_regions_data *) arg;
>> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
>> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
>> +             data->obfd);
>>   }
>>   /* A variant of linux_find_memory_regions_full that is suitable as the
>> @@ -1675,6 +1705,7 @@ static int
>>   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>>                     ULONGEST offset, ULONGEST inode,
>>                     int read, int write, int exec, int modified,
>> +                  bool memory_tagged,
>>                     const char *filename, void *data)
>>   {
>>     struct linux_make_mappings_data *map_data
>> diff --git a/gdb/memtag.c b/gdb/memtag.c
>> new file mode 100644
>> index 00000000000..af86137c49d
>> --- /dev/null
>> +++ b/gdb/memtag.c
>> @@ -0,0 +1,61 @@
>> +/* GDB generic memory tagging functions.
>> +
>> +   Copyright (C) 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 "memtag.h"
>> +#include "bfd.h"
>> +
>> +/* See memtag.h */
>> +
>> +bool
>> +get_next_core_memtag_section (bfd *abfd, asection *section,
>> +                  CORE_ADDR address, memtag_section_info &info)
>> +{
>> +  /* If the caller provided no SECTION to start from, search from the
>> +     beginning.  */
>> +  if (section == nullptr)
>> +    section = bfd_get_section_by_name (abfd, "memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t memtag_range_size = section->rawsize;
>> +      size_t tags_size = bfd_section_size (section);
>> +
>> +      /* Empty memory range and empty tag dump should not happen.  */
>> +      gdb_assert (memtag_range_size != 0);
>> +      gdb_assert (tags_size != 0);
>> +
>> +      CORE_ADDR start_address = bfd_section_vma (section);
>> +      CORE_ADDR end_address = start_address + memtag_range_size;
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +      && address < end_address)
>> +    {
>> +      info.start_address = start_address;
>> +      info.end_address = end_address;
>> +      info.memtag_section = section;
>> +      return true;
>> +    }
>> +      section = bfd_get_next_section_by_name (abfd, section);
>> +    }
>> +  return false;
>> +}
>> diff --git a/gdb/memtag.h b/gdb/memtag.h
>> new file mode 100644
>> index 00000000000..fe908c1e5e3
>> --- /dev/null
>> +++ b/gdb/memtag.h
>> @@ -0,0 +1,50 @@
>> +/* GDB generic memory tagging definitions.
>> +   Copyright (C) 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/>.  */
>> +
>> +#ifndef MEMTAG_H
>> +#define MEMTAG_H
>> +
>> +#include "bfd.h"
>> +
>> +struct memtag_section_info
>> +{
>> +  /* The start address of the tagged memory range.  */
>> +  CORE_ADDR start_address;
>> +  /* The final address of the tagged memory range.  */
>> +  CORE_ADDR end_address;
>> +  /* The section containing tags for the memory range
>> +     [start_address, end_address).  */
>> +  asection *memtag_section;
>> +};
>> +
>> +/* Helper function to walk through memory tag sections in a core file.
>> +
>> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
>> +   otherwise.
>> +
>> +   If SECTION is provided, search from that section onwards. If SECTION is
>> +   nullptr, then start a new search.
>> +
>> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
>> +   about such section.  Otherwise leave it unchanged.  */
>> +
>> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
>> +                   CORE_ADDR address,
>> +                   memtag_section_info &info);
>> +
>> +#endif /* MEMTAG_H */
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> new file mode 100644
>> index 00000000000..b20ebcff424
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2021 Free Software Foundation, Inc.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
>> +
>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>> +   Extension from the Linux Kernel, found in the sources in
>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>> +
>> +#include <errno.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <sys/auxv.h>
>> +#include <sys/mman.h>
>> +#include <sys/prctl.h>
>> +
>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>> +#define HWCAP2_MTE              (1 << 18)
>> +
>> +/* From arch/arm64/include/uapi/asm/mman.h */
>> +#define PROT_MTE  0x20
>> +
>> +/* From include/uapi/linux/prctl.h */
>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>> +#define PR_TAGGED_ADDR_ENABLE    (1UL << 0)
>> +#define PR_MTE_TCF_SHIFT    1
>> +#define PR_MTE_TCF_SYNC        (1UL << PR_MTE_TCF_SHIFT)
>> +#define PR_MTE_TAG_SHIFT    3
>> +
>> +void
>> +access_memory (unsigned char *tagged_ptr)
>> +{
>> +  tagged_ptr[0] = 'a';
>> +}
>> +
>> +int
>> +main (int argc, char **argv)
>> +{
>> +  unsigned char *tagged_ptr;
>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> +
>> +  /* Bail out if MTE is not supported.  */
>> +  if (!(hwcap2 & HWCAP2_MTE))
>> +    return 1;
>> +
>> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
>> +     allow all non-zero tags in the randomly generated set.  */
>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>> +         PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>> +         | (0xfffe << PR_MTE_TAG_SHIFT),
>> +         0, 0, 0))
>> +    {
>> +      perror ("prctl () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Create a mapping that will have PROT_MTE set.  */
>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>> +             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> +  if (tagged_ptr == MAP_FAILED)
>> +    {
>> +      perror ("mmap () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Enable MTE on the above anonymous mmap.  */
>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>> +    {
>> +      perror ("mprotect () failed");
>> +      return 1;
>> +    }
>> +
>> +  access_memory (tagged_ptr);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..8a19c4b449e
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,107 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# This file is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> +    verbose "Skipping ${gdb_test_file_name}."
>> +    return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    untested "could not run to main"
>> +    return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> +    unsupported "memory tagging unsupported"
>> +    return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> +    return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%02x" $i]
>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> +         $atag_msg \
>> +         "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +    "Program received signal SIGSEGV, Segmentation fault" \
>> +    "Memory tag violation while accessing address $hex" \
>> +    "Allocation tag $hex" \
>> +    "Logical tag $hex\." \
>> +    "$hex in access_memory \\(.*\\) at .*" \
>> +    ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +     "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> +    return -1
>> +}
>> +
>> +clean_restart $binfile
>> +
>> +# Load the core file and make sure we see the tag violation fault
>> +# information.
>> +gdb_test "core $core_filename" \
>> +    [multi_line \
>> +    "Core was generated by.*\." \
>> +    "Program terminated with signal SIGSEGV, Segmentation fault" \
>> +    "Memory tag violation while accessing address $hex" \
>> +    "Allocation tag 0xf" \
>> +    "Logical tag 0x0\." \
>> +    "#0.*$hex in access_memory \\(.*\\) at .*" \
>> +    ".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +     "core file shows tag violation information"
>> +
>> +# Make sure we have the tag_ctl register.
>> +gdb_test "info register tag_ctl" \
>> +     "tag_ctl.*$hex.*${::decimal}" \
>> +     "tag_ctl is available"
>> +
>> +# Check if the tag granules have the expected values.  If they do, that
>> +# means the core file saved the tags properly and GDB has read them
>> +# correctly.
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%x" $i]
>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>> +         "= 0x$tag" \
>> +         "memory tag of &tagged_ptr\[$index\] is correct"
>> +}
> 


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-18 12:46   ` Luis Machado
@ 2022-05-18 13:58     ` John Baldwin
  2022-05-23  9:50       ` Luis Machado
  2022-05-23  9:49     ` Luis Machado
  1 sibling, 1 reply; 47+ messages in thread
From: John Baldwin @ 2022-05-18 13:58 UTC (permalink / raw)
  To: Luis Machado, gdb-patches

 From my perspective this looks ok.

On 5/18/22 5:46 AM, Luis Machado via Gdb-patches wrote:
> Ping? The binutils patch has been approved, but I'd like to push both at the same time.
> 
> On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
>> v4:
>>
>> - Updated documentation (added cross-references).
>> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>>     PT_AARCH64_MEMTAG_MTE.
>>
>> v3:
>>
>> - Updated NEWS and documentation to be more thorough.
>>
>> v2:
>>
>> - Rework memory tag section handling to use generic section fields.
>>
>> --
>>
>> Teach GDB how to dump memory tags for AArch64 when using the gcore command
>> and how to read memory tag data back from a core file generated by GDB
>> (via gcore) or by the Linux kernel.
>>
>> The format is documented in the Linux Kernel documentation [1].
>>
>> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
>> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
>> of those segments when reading the core file back.
>>
>> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
>> are stored packed as 2 tags per byte.
>>
>> When reading the data back, the tags are unpacked.
>>
>> I've added a new testcase to exercise the feature.
>>
>> Build-tested with --enable-targets=all and regression tested on aarch64-linux
>> Ubuntu 20.04.
>>
>> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
>> ---
>>    gdb/Makefile.in                              |   1 +
>>    gdb/NEWS                                     |  10 ++
>>    gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>>    gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>>    gdb/arch/aarch64-mte-linux.h                 |  10 ++
>>    gdb/corelow.c                                |  62 +++++++
>>    gdb/defs.h                                   |   3 +-
>>    gdb/doc/gdb.texinfo                          |  19 +++
>>    gdb/gcore.c                                  |  83 ++++++++-
>>    gdb/gdbarch-components.py                    |  35 ++++
>>    gdb/gdbarch-gen.h                            |  26 +++
>>    gdb/gdbarch.c                                |  96 +++++++++++
>>    gdb/linux-tdep.c                             |  39 ++++-
>>    gdb/memtag.c                                 |  61 +++++++
>>    gdb/memtag.h                                 |  50 ++++++
>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>>    gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>>    17 files changed, 910 insertions(+), 8 deletions(-)
>>    create mode 100644 gdb/memtag.c
>>    create mode 100644 gdb/memtag.h
>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>>    create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>>
>> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
>> index 418094775a5..fac9364bea4 100644
>> --- a/gdb/Makefile.in
>> +++ b/gdb/Makefile.in
>> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>>    	memattr.c \
>>    	memory-map.c \
>>    	memrange.c \
>> +	memtag.c \
>>    	minidebug.c \
>>    	minsyms.c \
>>    	mipsread.c \
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 982f4a1a18c..3d925dc3663 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,16 @@
>>    
>>    *** Changes since GDB 12
>>    
>> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
>> +  reading memory tag data for AArch64 MTE from core files generated by
>> +  the gcore command or the Linux kernel.
>> +
>> +  When a process uses memory-mapped pages protected by memory tags (for
>> +  example, AArch64 MTE), this additional information will be recorded in
>> +  the core file in the event of a crash or if GDB generates a core file
>> +  from the current process state.  GDB will show this additional information
>> +  automatically, or through one of the memory-tag subcommands.
>> +
>>    * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>>    
>>    * Remove support for building against Python 2, it is now only possible to
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index 55094b3d88b..12d98e71796 100644
>> --- a/gdb/aarch64-linux-tdep.c
>> +++ b/gdb/aarch64-linux-tdep.c
>> @@ -53,6 +53,9 @@
>>    
>>    #include "gdbsupport/selftest.h"
>>    
>> +#include "elf/common.h"
>> +#include "elf/aarch64.h"
>> +
>>    /* Signal frame handling.
>>    
>>          +------------+  ^
>> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>>        }
>>    }
>>    
>> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
>> +
>> +static asection *
>> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
>> +				     CORE_ADDR address, size_t size)
>> +{
>> +  gdb_assert (obfd != nullptr);
>> +  gdb_assert (size > 0);
>> +
>> +  /* Create the section and associated program header.  */
>> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
>> +
>> +  if (mte_section == nullptr)
>> +    return nullptr;
>> +
>> +  bfd_set_section_vma (mte_section, address);
>> +  /* The size of the memory range covered by the memory tags.  We reuse the
>> +     section's rawsize field for this purpose.  */
>> +  mte_section->rawsize = size;
>> +  /* Tags are stored packed as 2 tags per byte.  */
>> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
>> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
>> +     refuse to write data to this section.  */
>> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
>> +
>> +  /* Store program header information.  */
>> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
>> +		   &mte_section);
>> +
>> +  return mte_section;
>> +}
>> +
>> +/* Maximum number of tags to request.  */
>> +#define MAX_TAGS_TO_TRANSFER 1024
>> +
>> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
>> +
>> +static bool
>> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>> +{
>> +  /* We only handle MTE tags for now.  */
>> +
>> +  size_t segment_size = osec->rawsize;
>> +  CORE_ADDR start_address = bfd_section_vma (osec);
>> +  CORE_ADDR end_address = start_address + segment_size;
>> +
>> +  /* Figure out how many tags we need to store in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
>> +						  AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* If there are no tag granules to fetch, just return.  */
>> +  if (granules == 0)
>> +    return true;
>> +
>> +  CORE_ADDR address = start_address;
>> +
>> +  /* Vector of tags.  */
>> +  gdb::byte_vector tags;
>> +
>> +  while (granules > 0)
>> +    {
>> +      /* Transfer tags in chunks.  */
>> +      gdb::byte_vector tags_read;
>> +      size_t xfer_len
>> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
>> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
>> +	  granules * AARCH64_MTE_GRANULE_SIZE;
>> +
>> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
>> +				 static_cast<int> (memtag_type::allocation)))
>> +	{
>> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
>> +		     phex_nz (start_address, sizeof (start_address)),
>> +		     phex_nz (end_address, sizeof (end_address)));
>> +	  return false;
>> +	}
>> +
>> +      /* Transfer over the tags that have been read.  */
>> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
>> +
>> +      /* Adjust the remaining granules and starting address.  */
>> +      granules -= tags_read.size ();
>> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
>> +    }
>> +
>> +  /* Pack the MTE tag bits.  */
>> +  aarch64_mte_pack_tags (tags);
>> +
>> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
>> +				 0, tags.size ()))
>> +    {
>> +      warning (_("Failed to write %s bytes of corefile memory "
>> +		 "tag content (%s)."),
>> +	       pulongest (tags.size ()),
>> +	       bfd_errmsg (bfd_get_error ()));
>> +    }
>> +  return true;
>> +}
>> +
>> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
>> +   hook.  Decode a memory tag section and return the requested tags.
>> +
>> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
>> +   range.  */
>> +
>> +static gdb::byte_vector
>> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
>> +				     bfd_section *section,
>> +				     int type,
>> +				     CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (section != nullptr);
>> +
>> +  /* The requested address must not be less than section->vma.  */
>> +  gdb_assert (section->vma <= address);
>> +
>> +  /* Figure out how many tags we need to fetch in this memory range.  */
>> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
>> +						  AARCH64_MTE_GRANULE_SIZE);
>> +  /* Sanity check.  */
>> +  gdb_assert (granules > 0);
>> +
>> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
>> +  size_t granules_from_vma
>> +    = aarch64_mte_get_tag_granules (section->vma,
>> +				    address - section->vma + length,
>> +				    AARCH64_MTE_GRANULE_SIZE);
>> +
>> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
>> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
>> +
>> +  /* Figure out the starting offset into the packed tags data.  */
>> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
>> +
>> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
>> +				 offset, tags.size ()))
>> +    error (_("Couldn't read contents from memtag section."));
>> +
>> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
>> +     returning.  */
>> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
>> +  aarch64_mte_unpack_tags (tags, skip_first);
>> +
>> +  /* Resize to the exact number of tags that was requested.  */
>> +  tags.resize (granules);
>> +
>> +  return tags;
>> +}
>> +
>>    static void
>>    aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>    {
>> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>>    
>>          set_gdbarch_report_signal_info (gdbarch,
>>    				      aarch64_linux_report_signal_info);
>> +
>> +      /* Core file helpers.  */
>> +
>> +      /* Core file helper to create a memory tag section for a particular
>> +	 PT_LOAD segment.  */
>> +      set_gdbarch_create_memtag_section
>> +	(gdbarch, aarch64_linux_create_memtag_section);
>> +
>> +      /* Core file helper to fill a memory tag section with tag data.  */
>> +      set_gdbarch_fill_memtag_section
>> +	(gdbarch, aarch64_linux_fill_memtag_section);
>> +
>> +      /* Core file helper to decode a memory tag section.  */
>> +      set_gdbarch_decode_memtag_section (gdbarch,
>> +					 aarch64_linux_decode_memtag_section);
>>        }
>>    
>>      /* Initialize the aarch64_linux_record_tdep.  */
>> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
>> index fc7a8cc00f7..3af6f364e91 100644
>> --- a/gdb/arch/aarch64-mte-linux.c
>> +++ b/gdb/arch/aarch64-mte-linux.c
>> @@ -21,6 +21,62 @@
>>    
>>    /* See arch/aarch64-mte-linux.h */
>>    
>> +void
>> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
>> +{
>> +  /* Nothing to pack?  */
>> +  if (tags.empty ())
>> +    return;
>> +
>> +  /* If the tags vector has an odd number of elements, add another
>> +     zeroed-out element to make it even.  This facilitates packing.  */
>> +  if ((tags.size () % 2) != 0)
>> +    tags.emplace_back (0);
>> +
>> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
>> +       unpacked += 2, packed++)
>> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
>> +
>> +  /* Now we have half the size.  */
>> +  tags.resize (tags.size () / 2);
>> +}
>> +
>> +/* See arch/aarch64-mte-linux.h */
>> +
>> +void
>> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
>> +{
>> +  /* Nothing to unpack?  */
>> +  if (tags.empty ())
>> +    return;
>> +
>> +  /* An unpacked MTE tags vector will have twice the number of elements
>> +     compared to an unpacked one.  */
>> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
>> +
>> +  int unpacked = 0, packed = 0;
>> +
>> +  if (skip_first)
>> +    {
>> +      /* We are not interested in the first unpacked element, just discard
>> +	 it.  */
>> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
>> +      unpacked++;
>> +      packed++;
>> +    }
>> +
>> +  for (; packed < tags.size (); unpacked += 2, packed++)
>> +    {
>> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
>> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
>> +    }
>> +
>> +  /* Update the original tags vector.  */
>> +  tags = std::move (unpacked_tags);
>> +}
>> +
>> +/* See arch/aarch64-mte-linux.h */
>> +
>>    size_t
>>    aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>>    {
>> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
>> index d158926feff..8a145b447aa 100644
>> --- a/gdb/arch/aarch64-mte-linux.h
>> +++ b/gdb/arch/aarch64-mte-linux.h
>> @@ -32,6 +32,7 @@
>>    
>>    /* We have one tag per 16 bytes of memory.  */
>>    #define AARCH64_MTE_GRANULE_SIZE 16
>> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>>    #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>>    #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>>    
>> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>>       It is always possible to get the logical tag.  */
>>    extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>>    
>> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
>> +   2 tags per byte and resize the vector.  */
>> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
>> +
>> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
>> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
>> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
>> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
>> +
>>    #endif /* ARCH_AARCH64_LINUX_H */
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index 8c33fb7ebb2..8b8994f80db 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -52,6 +52,7 @@
>>    #include <unordered_set>
>>    #include "gdbcmd.h"
>>    #include "xml-tdesc.h"
>> +#include "memtag.h"
>>    
>>    #ifndef O_LARGEFILE
>>    #define O_LARGEFILE 0
>> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>>    
>>      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;
>> +
>>      /* A few helpers.  */
>>    
>>      /* Getter, see variable definition.  */
>> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
>> +{
>> +  struct gdbarch *gdbarch = target_gdbarch ();
>> +
>> +  /* 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 (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;
>> +}
>> +
>>    /* Get a pointer to the current core target.  If not connected to a
>>       core target, return NULL.  */
>>    
>> diff --git a/gdb/defs.h b/gdb/defs.h
>> index 99bfdd526ff..51a7576a56a 100644
>> --- a/gdb/defs.h
>> +++ b/gdb/defs.h
>> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>>    
>>    typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>>    					 int read, int write, int exec,
>> -					 int modified, void *data);
>> +					 int modified, bool memory_tagged,
>> +					 void *data);
>>    
>>    /* * Possible lvalue types.  Like enum language, this should be in
>>       value.h, but needs to be here for the same reason.  */
>> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
>> index 38ad2ac32b0..36f10f20cfb 100644
>> --- a/gdb/doc/gdb.texinfo
>> +++ b/gdb/doc/gdb.texinfo
>> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>>    option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>>    documentation in the Linux kernel.
>>    
>> +@value{GDBN} supports dumping memory tag data to core files through the
>> +@command{gcore} command and reading memory tag data from core files generated
>> +by the @command{gcore} command or the Linux kernel.
>> +
>> +When a process uses memory-mapped pages protected by memory tags (for
>> +example, AArch64 MTE), this additional information will be recorded in
>> +the core file in the event of a crash or if @value{GDBN} generates a core file
>> +from the current process state.
>> +
>> +The memory tag data will be used so developers can display the memory
>> +tags from a particular memory region (using the @samp{m} modifier to the
>> +@command{x} command, using the @command{print} command or using the various
>> +@command{memory-tag} subcommands.
>> +
>> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
>> +information automatically from the core file, and will show one of the above
>> +messages depending on whether the synchronous or asynchronous mode is selected.
>> +@xref{Memory Tagging}. @xref{Memory}.
>> +
>>    @node i386
>>    @subsection x86 Architecture-specific Issues
>>    
>> diff --git a/gdb/gcore.c b/gdb/gcore.c
>> index fdb22b72a07..b81ef81ab84 100644
>> --- a/gdb/gcore.c
>> +++ b/gdb/gcore.c
>> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>      int p_flags = 0;
>>      int p_type = 0;
>>    
>> +  /* Memory tag segments have already been handled by the architecture, as
>> +     those contain arch-specific information.  If we have one of those, just
>> +     return.  */
>> +  if (startswith (bfd_section_name (osec), "memtag"))
>> +    return;
>> +
>>      /* FIXME: these constants may only be applicable for ELF.  */
>>      if (startswith (bfd_section_name (osec), "load"))
>>        p_type = PT_LOAD;
>> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>>    
>>    static int
>>    gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>> -		       int write, int exec, int modified, void *data)
>> +		       int write, int exec, int modified, bool memory_tagged,
>> +		       void *data)
>>    {
>>      bfd *obfd = (bfd *) data;
>>      asection *osec;
>> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>>      return 0;
>>    }
>>    
>> +/* gdbarch_find_memory_region callback for creating a memory tag section.
>> +   DATA is 'bfd *' for the core file GDB is creating.  */
>> +
>> +static int
>> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
>> +				      int read, int write, int exec,
>> +				      int modified, bool memory_tagged,
>> +				      void *data)
>> +{
>> +  /* Are there memory tags in this particular memory map entry?  */
>> +  if (!memory_tagged)
>> +    return 0;
>> +
>> +  bfd *obfd = (bfd *) data;
>> +
>> +  /* Ask the architecture to create a memory tag section for this particular
>> +     memory map entry.  It will be populated with contents later, as we can't
>> +     start writing the contents before we have all the sections sorted out.  */
>> +  asection *memtag_section
>> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
>> +
>> +  if (memtag_section == nullptr)
>> +    {
>> +      warning (_("Couldn't make gcore memory tag segment: %s"),
>> +	       bfd_errmsg (bfd_get_error ()));
>> +      return 1;
>> +    }
>> +
>> +  if (info_verbose)
>> +    {
>> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
>> +			      "at %s\n",
>> +		  plongest (bfd_section_size (memtag_section)),
>> +		  paddress (target_gdbarch (), vaddr));
>> +    }
>> +
>> +  return 0;
>> +}
>> +
>>    int
>>    objfile_find_memory_regions (struct target_ops *self,
>>    			     find_memory_region_ftype func, void *obfd)
>> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>    			   (flags & SEC_READONLY) == 0, /* Writable.  */
>>    			   (flags & SEC_CODE) != 0, /* Executable.  */
>>    			   1, /* MODIFIED is unknown, pass it as true.  */
>> +			   false, /* No memory tags in the object file.  */
>>    			   obfd);
>>    	    if (ret != 0)
>>    	      return ret;
>> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>    	     1, /* Stack section will be writable.  */
>>    	     0, /* Stack section will not be executable.  */
>>    	     1, /* Stack section will be modified.  */
>> +	     false, /* No memory tags in the object file.  */
>>    	     obfd);
>>    
>>      /* Make a heap segment.  */
>> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>>    	     1, /* Heap section will be writable.  */
>>    	     0, /* Heap section will not be executable.  */
>>    	     1, /* Heap section will be modified.  */
>> +	     false, /* No memory tags in the object file.  */
>>    	     obfd);
>>    
>>      return 0;
>> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>>        }
>>    }
>>    
>> +/* Callback to copy contents to a particular memory tag section.  */
>> +
>> +static void
>> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
>> +{
>> +  /* We are only interested in "memtag" sections.  */
>> +  if (!startswith (bfd_section_name (osec), "memtag"))
>> +    return;
>> +
>> +  /* Fill the section with memory tag contents.  */
>> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
>> +    error (_("Failed to fill memory tag section for core file."));
>> +}
>> +
>>    static int
>>    gcore_memory_sections (bfd *obfd)
>>    {
>> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>>    	return 0;			/* FIXME: error return/msg?  */
>>        }
>>    
>> +  /* Take care of dumping memory tags, if there are any.  */
>> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
>> +      || gdbarch_find_memory_regions (target_gdbarch (),
>> +				      gcore_create_memtag_section_callback,
>> +				      obfd) != 0)
>> +    {
>> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
>> +				      obfd) != 0)
>> +	return 0;
>> +    }
>> +
>>      /* Record phdrs for section-to-segment mapping.  */
>>      for (asection *sect : gdb_bfd_sections (obfd))
>>        make_output_phdrs (obfd, sect);
>>    
>> -  /* Copy memory region contents.  */
>> +  /* Copy memory region and memory tag contents.  */
>>      for (asection *sect : gdb_bfd_sections (obfd))
>> -    gcore_copy_callback (obfd, sect);
>> +    {
>> +      gcore_copy_callback (obfd, sect);
>> +      gcore_copy_memtag_section_callback (obfd, sect);
>> +    }
>>    
>>      return 1;
>>    }
>> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
>> index e8f20c83ff0..6fa1b7591db 100644
>> --- a/gdb/gdbarch-components.py
>> +++ b/gdb/gdbarch-components.py
>> @@ -1522,6 +1522,41 @@ Find core file memory regions
>>        invalid=True,
>>    )
>>    
>> +Method(
>> +    comment="""
>> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
>> +""",
>> +    type="asection *",
>> +    name="create_memtag_section",
>> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>> +Method(
>> +    comment="""
>> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
>> +""",
>> +    type="bool",
>> +    name="fill_memtag_section",
>> +    params=[("asection *", "osec")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>> +Method(
>> +    comment="""
>> +Decode a memory tag SECTION and return the tags of type TYPE contained in
>> +the memory range [ADDRESS, ADDRESS + LENGTH).
>> +If no tags were found, return an empty vector.
>> +""",
>> +    type="gdb::byte_vector",
>> +    name="decode_memtag_section",
>> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
>> +    predicate=True,
>> +    invalid=True,
>> +)
>> +
>>    Method(
>>        comment="""
>>    Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
>> index 882b9057b1a..1d19f51f21d 100644
>> --- a/gdb/gdbarch-gen.h
>> +++ b/gdb/gdbarch-gen.h
>> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>>    extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>>    extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>>    
>> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
>> +
>> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
>> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
>> +
>> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
>> +
>> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
>> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
>> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
>> +
>> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
>> +   the memory range [ADDRESS, ADDRESS + LENGTH).
>> +   If no tags were found, return an empty vector. */
>> +
>> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
>> +
>> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
>> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
>> +
>>    /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>>       core file into buffer READBUF with length LEN.  Return the number of bytes read
>>       (zero indicates failure).
>> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
>> index a588bdef61a..f5dbacb14e7 100644
>> --- a/gdb/gdbarch.c
>> +++ b/gdb/gdbarch.c
>> @@ -171,6 +171,9 @@ struct gdbarch
>>      gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>>      gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>>      gdbarch_find_memory_regions_ftype *find_memory_regions;
>> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
>> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
>> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>>      gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>>      gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>>      gdbarch_core_pid_to_str_ftype *core_pid_to_str;
>> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>>      /* Skip verify of iterate_over_regset_sections, has predicate.  */
>>      /* Skip verify of make_corefile_notes, has predicate.  */
>>      /* Skip verify of find_memory_regions, has predicate.  */
>> +  /* Skip verify of create_memtag_section, has predicate.  */
>> +  /* Skip verify of fill_memtag_section, has predicate.  */
>> +  /* Skip verify of decode_memtag_section, has predicate.  */
>>      /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>>      /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>>      /* Skip verify of core_pid_to_str, has predicate.  */
>> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>>      gdb_printf (file,
>>                          "gdbarch_dump: find_memory_regions = <%s>\n",
>>                          host_address_to_string (gdbarch->find_memory_regions));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
>> +                      gdbarch_create_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->create_memtag_section));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
>> +                      gdbarch_fill_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->fill_memtag_section));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
>> +                      gdbarch_decode_memtag_section_p (gdbarch));
>> +  gdb_printf (file,
>> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
>> +                      host_address_to_string (gdbarch->decode_memtag_section));
>>      gdb_printf (file,
>>                          "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>>                          gdbarch_core_xfer_shared_libraries_p (gdbarch));
>> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>>      gdbarch->find_memory_regions = find_memory_regions;
>>    }
>>    
>> +bool
>> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->create_memtag_section != NULL;
>> +}
>> +
>> +asection *
>> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->create_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
>> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
>> +}
>> +
>> +void
>> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
>> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
>> +{
>> +  gdbarch->create_memtag_section = create_memtag_section;
>> +}
>> +
>> +bool
>> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->fill_memtag_section != NULL;
>> +}
>> +
>> +bool
>> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
>> +  return gdbarch->fill_memtag_section (gdbarch, osec);
>> +}
>> +
>> +void
>> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
>> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
>> +{
>> +  gdbarch->fill_memtag_section = fill_memtag_section;
>> +}
>> +
>> +bool
>> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  return gdbarch->decode_memtag_section != NULL;
>> +}
>> +
>> +gdb::byte_vector
>> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
>> +{
>> +  gdb_assert (gdbarch != NULL);
>> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
>> +  if (gdbarch_debug >= 2)
>> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
>> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
>> +}
>> +
>> +void
>> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
>> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
>> +{
>> +  gdbarch->decode_memtag_section = decode_memtag_section;
>> +}
>> +
>>    bool
>>    gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>>    {
>> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
>> index 4e728a06e7e..8a83ed320cf 100644
>> --- a/gdb/linux-tdep.c
>> +++ b/gdb/linux-tdep.c
>> @@ -42,6 +42,7 @@
>>    #include "gcore.h"
>>    #include "gcore-elf.h"
>>    #include "solib-svr4.h"
>> +#include "memtag.h"
>>    
>>    #include <ctype.h>
>>    #include <unordered_map>
>> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>>    					    ULONGEST offset, ULONGEST inode,
>>    					    int read, int write,
>>    					    int exec, int modified,
>> +					    bool memory_tagged,
>>    					    const char *filename,
>>    					    void *data);
>>    
>> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>>      return smaps;
>>    }
>>    
>> -/* See linux-tdep.h.  */
>> +/* Helper that checks if an address is in a memory tag page for a live
>> +   process.  */
>>    
>> -bool
>> -linux_address_in_memtag_page (CORE_ADDR address)
>> +static bool
>> +linux_process_address_in_memtag_page (CORE_ADDR address)
>>    {
>>      if (current_inferior ()->fake_pid_p)
>>        return false;
>> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>>      return false;
>>    }
>>    
>> +/* Helper that checks if an address is in a memory tag page for a core file
>> +   process.  */
>> +
>> +static bool
>> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (core_bfd == nullptr)
>> +    return false;
>> +
>> +  memtag_section_info info;
>> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
>> +}
>> +
>> +/* See linux-tdep.h.  */
>> +
>> +bool
>> +linux_address_in_memtag_page (CORE_ADDR address)
>> +{
>> +  if (!target_has_execution ())
>> +    return linux_core_file_address_in_memtag_page (address);
>> +
>> +  return linux_process_address_in_memtag_page (address);
>> +}
>> +
>>    /* List memory regions in the inferior for a corefile.  */
>>    
>>    static int
>> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>>    		map.offset, map.inode, map.read, map.write, map.exec,
>>    		1, /* MODIFIED is true because we want to dump
>>    		      the mapping.  */
>> +		map.vmflags.memory_tagging != 0,
>>    		map.filename.c_str (), obfd);
>>    	}
>>        }
>> @@ -1621,12 +1649,14 @@ static int
>>    linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>>    				 ULONGEST offset, ULONGEST inode,
>>    				 int read, int write, int exec, int modified,
>> +				 bool memory_tagged,
>>    				 const char *filename, void *arg)
>>    {
>>      struct linux_find_memory_regions_data *data
>>        = (struct linux_find_memory_regions_data *) arg;
>>    
>> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
>> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
>> +		     data->obfd);
>>    }
>>    
>>    /* A variant of linux_find_memory_regions_full that is suitable as the
>> @@ -1675,6 +1705,7 @@ static int
>>    linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>>    			      ULONGEST offset, ULONGEST inode,
>>    			      int read, int write, int exec, int modified,
>> +			      bool memory_tagged,
>>    			      const char *filename, void *data)
>>    {
>>      struct linux_make_mappings_data *map_data
>> diff --git a/gdb/memtag.c b/gdb/memtag.c
>> new file mode 100644
>> index 00000000000..af86137c49d
>> --- /dev/null
>> +++ b/gdb/memtag.c
>> @@ -0,0 +1,61 @@
>> +/* GDB generic memory tagging functions.
>> +
>> +   Copyright (C) 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 "memtag.h"
>> +#include "bfd.h"
>> +
>> +/* See memtag.h */
>> +
>> +bool
>> +get_next_core_memtag_section (bfd *abfd, asection *section,
>> +			      CORE_ADDR address, memtag_section_info &info)
>> +{
>> +  /* If the caller provided no SECTION to start from, search from the
>> +     beginning.  */
>> +  if (section == nullptr)
>> +    section = bfd_get_section_by_name (abfd, "memtag");
>> +
>> +  /* Go through all the memtag sections and figure out if ADDRESS
>> +     falls within one of the memory ranges that contain tags.  */
>> +  while (section != nullptr)
>> +    {
>> +      size_t memtag_range_size = section->rawsize;
>> +      size_t tags_size = bfd_section_size (section);
>> +
>> +      /* Empty memory range and empty tag dump should not happen.  */
>> +      gdb_assert (memtag_range_size != 0);
>> +      gdb_assert (tags_size != 0);
>> +
>> +      CORE_ADDR start_address = bfd_section_vma (section);
>> +      CORE_ADDR end_address = start_address + memtag_range_size;
>> +
>> +      /* Is the address within [start_address, end_address)?  */
>> +      if (address >= start_address
>> +	  && address < end_address)
>> +	{
>> +	  info.start_address = start_address;
>> +	  info.end_address = end_address;
>> +	  info.memtag_section = section;
>> +	  return true;
>> +	}
>> +      section = bfd_get_next_section_by_name (abfd, section);
>> +    }
>> +  return false;
>> +}
>> diff --git a/gdb/memtag.h b/gdb/memtag.h
>> new file mode 100644
>> index 00000000000..fe908c1e5e3
>> --- /dev/null
>> +++ b/gdb/memtag.h
>> @@ -0,0 +1,50 @@
>> +/* GDB generic memory tagging definitions.
>> +   Copyright (C) 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/>.  */
>> +
>> +#ifndef MEMTAG_H
>> +#define MEMTAG_H
>> +
>> +#include "bfd.h"
>> +
>> +struct memtag_section_info
>> +{
>> +  /* The start address of the tagged memory range.  */
>> +  CORE_ADDR start_address;
>> +  /* The final address of the tagged memory range.  */
>> +  CORE_ADDR end_address;
>> +  /* The section containing tags for the memory range
>> +     [start_address, end_address).  */
>> +  asection *memtag_section;
>> +};
>> +
>> +/* Helper function to walk through memory tag sections in a core file.
>> +
>> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
>> +   otherwise.
>> +
>> +   If SECTION is provided, search from that section onwards. If SECTION is
>> +   nullptr, then start a new search.
>> +
>> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
>> +   about such section.  Otherwise leave it unchanged.  */
>> +
>> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
>> +				   CORE_ADDR address,
>> +				   memtag_section_info &info);
>> +
>> +#endif /* MEMTAG_H */
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> new file mode 100644
>> index 00000000000..b20ebcff424
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>> @@ -0,0 +1,93 @@
>> +/* This test program is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2021 Free Software Foundation, Inc.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
>> +
>> +/* This test was based on the documentation for the AArch64 Memory Tagging
>> +   Extension from the Linux Kernel, found in the sources in
>> +   Documentation/arm64/memory-tagging-extension.rst.  */
>> +
>> +#include <errno.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <unistd.h>
>> +#include <sys/auxv.h>
>> +#include <sys/mman.h>
>> +#include <sys/prctl.h>
>> +
>> +/* From arch/arm64/include/uapi/asm/hwcap.h */
>> +#define HWCAP2_MTE              (1 << 18)
>> +
>> +/* From arch/arm64/include/uapi/asm/mman.h */
>> +#define PROT_MTE  0x20
>> +
>> +/* From include/uapi/linux/prctl.h */
>> +#define PR_SET_TAGGED_ADDR_CTRL 55
>> +#define PR_GET_TAGGED_ADDR_CTRL 56
>> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
>> +#define PR_MTE_TCF_SHIFT	1
>> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
>> +#define PR_MTE_TAG_SHIFT	3
>> +
>> +void
>> +access_memory (unsigned char *tagged_ptr)
>> +{
>> +  tagged_ptr[0] = 'a';
>> +}
>> +
>> +int
>> +main (int argc, char **argv)
>> +{
>> +  unsigned char *tagged_ptr;
>> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
>> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
>> +
>> +  /* Bail out if MTE is not supported.  */
>> +  if (!(hwcap2 & HWCAP2_MTE))
>> +    return 1;
>> +
>> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
>> +     allow all non-zero tags in the randomly generated set.  */
>> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
>> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
>> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
>> +	     0, 0, 0))
>> +    {
>> +      perror ("prctl () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Create a mapping that will have PROT_MTE set.  */
>> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
>> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
>> +  if (tagged_ptr == MAP_FAILED)
>> +    {
>> +      perror ("mmap () failed");
>> +      return 1;
>> +    }
>> +
>> +  /* Enable MTE on the above anonymous mmap.  */
>> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
>> +    {
>> +      perror ("mprotect () failed");
>> +      return 1;
>> +    }
>> +
>> +  access_memory (tagged_ptr);
>> +
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> new file mode 100644
>> index 00000000000..8a19c4b449e
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
>> @@ -0,0 +1,107 @@
>> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
>> +#
>> +# This program is free software; you can redistribute it and/or modify
>> +# it under the terms of the GNU General Public License as published by
>> +# the Free Software Foundation; either version 3 of the License, or
>> +# (at your option) any later version.
>> +#
>> +# This program is distributed in the hope that it will be useful,
>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +# GNU General Public License for more details.
>> +#
>> +# You should have received a copy of the GNU General Public License
>> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> +# This file is part of the gdb testsuite.
>> +
>> +# Test generating and reading a core file with MTE memory tags.
>> +
>> +if {![is_aarch64_target]} {
>> +    verbose "Skipping ${gdb_test_file_name}."
>> +    return
>> +}
>> +
>> +standard_testfile
>> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
>> +    return -1
>> +}
>> +
>> +if ![runto_main] {
>> +    untested "could not run to main"
>> +    return -1
>> +}
>> +
>> +# Targets that don't support memory tagging should not execute the
>> +# runtime memory tagging tests.
>> +if {![supports_memtag]} {
>> +    unsupported "memory tagging unsupported"
>> +    return -1
>> +}
>> +
>> +gdb_breakpoint "access_memory"
>> +
>> +if [gdb_continue "access_memory"] {
>> +    return -1
>> +}
>> +
>> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
>> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%02x" $i]
>> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
>> +	     $atag_msg \
>> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
>> +}
>> +
>> +# Run until a crash and confirm GDB displays memory tag violation
>> +# information.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +	"Program received signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag $hex" \
>> +	"Logical tag $hex\." \
>> +	"$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "display tag violation information for live process"
>> +
>> +# Generate the core file.
>> +set core_filename [standard_output_file "$testfile.core"]
>> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
>> +
>> +if { !$core_generated } {
>> +    return -1
>> +}
>> +
>> +clean_restart $binfile
>> +
>> +# Load the core file and make sure we see the tag violation fault
>> +# information.
>> +gdb_test "core $core_filename" \
>> +    [multi_line \
>> +	"Core was generated by.*\." \
>> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
>> +	"Memory tag violation while accessing address $hex" \
>> +	"Allocation tag 0xf" \
>> +	"Logical tag 0x0\." \
>> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
>> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
>> +	 "core file shows tag violation information"
>> +
>> +# Make sure we have the tag_ctl register.
>> +gdb_test "info register tag_ctl" \
>> +	 "tag_ctl.*$hex.*${::decimal}" \
>> +	 "tag_ctl is available"
>> +
>> +# Check if the tag granules have the expected values.  If they do, that
>> +# means the core file saved the tags properly and GDB has read them
>> +# correctly.
>> +for {set i 15} {$i >= 0} {incr i -1} {
>> +    set index [expr [expr 15 - $i] * 16]
>> +    set tag [format "%x" $i]
>> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
>> +	     "= 0x$tag" \
>> +	     "memory tag of &tagged_ptr\[$index\] is correct"
>> +}
> 


-- 
John Baldwin

^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
  2022-05-12 10:36   ` Luis Machado
@ 2022-05-18 12:46   ` Luis Machado
  2022-05-18 13:58     ` John Baldwin
  2022-05-23  9:49     ` Luis Machado
  2022-06-06  9:28   ` Luis Machado
                     ` (2 subsequent siblings)
  4 siblings, 2 replies; 47+ messages in thread
From: Luis Machado @ 2022-05-18 12:46 UTC (permalink / raw)
  To: gdb-patches

Ping? The binutils patch has been approved, but I'd like to push both at the same time.

On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> v4:
> 
> - Updated documentation (added cross-references).
> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>    PT_AARCH64_MEMTAG_MTE.
> 
> v3:
> 
> - Updated NEWS and documentation to be more thorough.
> 
> v2:
> 
> - Rework memory tag section handling to use generic section fields.
> 
> --
> 
> Teach GDB how to dump memory tags for AArch64 when using the gcore command
> and how to read memory tag data back from a core file generated by GDB
> (via gcore) or by the Linux kernel.
> 
> The format is documented in the Linux Kernel documentation [1].
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
> of those segments when reading the core file back.
> 
> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
> are stored packed as 2 tags per byte.
> 
> When reading the data back, the tags are unpacked.
> 
> I've added a new testcase to exercise the feature.
> 
> Build-tested with --enable-targets=all and regression tested on aarch64-linux
> Ubuntu 20.04.
> 
> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |  10 ++
>   gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>   gdb/arch/aarch64-mte-linux.h                 |  10 ++
>   gdb/corelow.c                                |  62 +++++++
>   gdb/defs.h                                   |   3 +-
>   gdb/doc/gdb.texinfo                          |  19 +++
>   gdb/gcore.c                                  |  83 ++++++++-
>   gdb/gdbarch-components.py                    |  35 ++++
>   gdb/gdbarch-gen.h                            |  26 +++
>   gdb/gdbarch.c                                |  96 +++++++++++
>   gdb/linux-tdep.c                             |  39 ++++-
>   gdb/memtag.c                                 |  61 +++++++
>   gdb/memtag.h                                 |  50 ++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>   17 files changed, 910 insertions(+), 8 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 418094775a5..fac9364bea4 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 982f4a1a18c..3d925dc3663 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,16 @@
>   
>   *** Changes since GDB 12
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> +  When a process uses memory-mapped pages protected by memory tags (for
> +  example, AArch64 MTE), this additional information will be recorded in
> +  the core file in the event of a crash or if GDB generates a core file
> +  from the current process state.  GDB will show this additional information
> +  automatically, or through one of the memory-tag subcommands.
> +
>   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>   
>   * Remove support for building against Python 2, it is now only possible to
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index 55094b3d88b..12d98e71796 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,9 @@
>   
>   #include "gdbsupport/selftest.h"
>   
> +#include "elf/common.h"
> +#include "elf/aarch64.h"
> +
>   /* Signal frame handling.
>   
>         +------------+  ^
> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
> +
> +static asection *
> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> +				     CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (obfd != nullptr);
> +  gdb_assert (size > 0);
> +
> +  /* Create the section and associated program header.  */
> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
> +
> +  if (mte_section == nullptr)
> +    return nullptr;
> +
> +  bfd_set_section_vma (mte_section, address);
> +  /* The size of the memory range covered by the memory tags.  We reuse the
> +     section's rawsize field for this purpose.  */
> +  mte_section->rawsize = size;
> +  /* Tags are stored packed as 2 tags per byte.  */
> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
> +     refuse to write data to this section.  */
> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> +
> +  /* Store program header information.  */
> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
> +		   &mte_section);
> +
> +  return mte_section;
> +}
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
> +
> +static bool
> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  size_t segment_size = osec->rawsize;
> +  CORE_ADDR start_address = bfd_section_vma (osec);
> +  CORE_ADDR end_address = start_address + segment_size;
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return true;
> +
> +  CORE_ADDR address = start_address;
> +
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  return false;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Pack the MTE tag bits.  */
> +  aarch64_mte_pack_tags (tags);
> +
> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> +				 0, tags.size ()))
> +    {
> +      warning (_("Failed to write %s bytes of corefile memory "
> +		 "tag content (%s)."),
> +	       pulongest (tags.size ()),
> +	       bfd_errmsg (bfd_get_error ()));
> +    }
> +  return true;
> +}
> +
> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
> +   hook.  Decode a memory tag section and return the requested tags.
> +
> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> +				     bfd_section *section,
> +				     int type,
> +				     CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (section != nullptr);
> +
> +  /* The requested address must not be less than section->vma.  */
> +  gdb_assert (section->vma <= address);
> +
> +  /* Figure out how many tags we need to fetch in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +  /* Sanity check.  */
> +  gdb_assert (granules > 0);
> +
> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
> +  size_t granules_from_vma
> +    = aarch64_mte_get_tag_granules (section->vma,
> +				    address - section->vma + length,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> +
> +  /* Figure out the starting offset into the packed tags data.  */
> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> +
> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
> +				 offset, tags.size ()))
> +    error (_("Couldn't read contents from memtag section."));
> +
> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
> +     returning.  */
> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> +  aarch64_mte_unpack_tags (tags, skip_first);
> +
> +  /* Resize to the exact number of tags that was requested.  */
> +  tags.resize (granules);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create a memory tag section for a particular
> +	 PT_LOAD segment.  */
> +      set_gdbarch_create_memtag_section
> +	(gdbarch, aarch64_linux_create_memtag_section);
> +
> +      /* Core file helper to fill a memory tag section with tag data.  */
> +      set_gdbarch_fill_memtag_section
> +	(gdbarch, aarch64_linux_fill_memtag_section);
> +
> +      /* Core file helper to decode a memory tag section.  */
> +      set_gdbarch_decode_memtag_section (gdbarch,
> +					 aarch64_linux_decode_memtag_section);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
> index fc7a8cc00f7..3af6f364e91 100644
> --- a/gdb/arch/aarch64-mte-linux.c
> +++ b/gdb/arch/aarch64-mte-linux.c
> @@ -21,6 +21,62 @@
>   
>   /* See arch/aarch64-mte-linux.h */
>   
> +void
> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> +{
> +  /* Nothing to pack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* If the tags vector has an odd number of elements, add another
> +     zeroed-out element to make it even.  This facilitates packing.  */
> +  if ((tags.size () % 2) != 0)
> +    tags.emplace_back (0);
> +
> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> +       unpacked += 2, packed++)
> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> +
> +  /* Now we have half the size.  */
> +  tags.resize (tags.size () / 2);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
> +void
> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
> +{
> +  /* Nothing to unpack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* An unpacked MTE tags vector will have twice the number of elements
> +     compared to an unpacked one.  */
> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> +
> +  int unpacked = 0, packed = 0;
> +
> +  if (skip_first)
> +    {
> +      /* We are not interested in the first unpacked element, just discard
> +	 it.  */
> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> +      unpacked++;
> +      packed++;
> +    }
> +
> +  for (; packed < tags.size (); unpacked += 2, packed++)
> +    {
> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> +    }
> +
> +  /* Update the original tags vector.  */
> +  tags = std::move (unpacked_tags);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
>   size_t
>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>   {
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index d158926feff..8a145b447aa 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
> +   2 tags per byte and resize the vector.  */
> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> +
> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 8c33fb7ebb2..8b8994f80db 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -52,6 +52,7 @@
>   #include <unordered_set>
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* 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 (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/defs.h b/gdb/defs.h
> index 99bfdd526ff..51a7576a56a 100644
> --- a/gdb/defs.h
> +++ b/gdb/defs.h
> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>   
>   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>   					 int read, int write, int exec,
> -					 int modified, void *data);
> +					 int modified, bool memory_tagged,
> +					 void *data);
>   
>   /* * Possible lvalue types.  Like enum language, this should be in
>      value.h, but needs to be here for the same reason.  */
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 38ad2ac32b0..36f10f20cfb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> +When a process uses memory-mapped pages protected by memory tags (for
> +example, AArch64 MTE), this additional information will be recorded in
> +the core file in the event of a crash or if @value{GDBN} generates a core file
> +from the current process state.
> +
> +The memory tag data will be used so developers can display the memory
> +tags from a particular memory region (using the @samp{m} modifier to the
> +@command{x} command, using the @command{print} command or using the various
> +@command{memory-tag} subcommands.
> +
> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
> +information automatically from the core file, and will show one of the above
> +messages depending on whether the synchronous or asynchronous mode is selected.
> +@xref{Memory Tagging}. @xref{Memory}.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gcore.c b/gdb/gcore.c
> index fdb22b72a07..b81ef81ab84 100644
> --- a/gdb/gcore.c
> +++ b/gdb/gcore.c
> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>     int p_flags = 0;
>     int p_type = 0;
>   
> +  /* Memory tag segments have already been handled by the architecture, as
> +     those contain arch-specific information.  If we have one of those, just
> +     return.  */
> +  if (startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
>     /* FIXME: these constants may only be applicable for ELF.  */
>     if (startswith (bfd_section_name (osec), "load"))
>       p_type = PT_LOAD;
> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>   
>   static int
>   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
> -		       int write, int exec, int modified, void *data)
> +		       int write, int exec, int modified, bool memory_tagged,
> +		       void *data)
>   {
>     bfd *obfd = (bfd *) data;
>     asection *osec;
> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>     return 0;
>   }
>   
> +/* gdbarch_find_memory_region callback for creating a memory tag section.
> +   DATA is 'bfd *' for the core file GDB is creating.  */
> +
> +static int
> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
> +				      int read, int write, int exec,
> +				      int modified, bool memory_tagged,
> +				      void *data)
> +{
> +  /* Are there memory tags in this particular memory map entry?  */
> +  if (!memory_tagged)
> +    return 0;
> +
> +  bfd *obfd = (bfd *) data;
> +
> +  /* Ask the architecture to create a memory tag section for this particular
> +     memory map entry.  It will be populated with contents later, as we can't
> +     start writing the contents before we have all the sections sorted out.  */
> +  asection *memtag_section
> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
> +
> +  if (memtag_section == nullptr)
> +    {
> +      warning (_("Couldn't make gcore memory tag segment: %s"),
> +	       bfd_errmsg (bfd_get_error ()));
> +      return 1;
> +    }
> +
> +  if (info_verbose)
> +    {
> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
> +			      "at %s\n",
> +		  plongest (bfd_section_size (memtag_section)),
> +		  paddress (target_gdbarch (), vaddr));
> +    }
> +
> +  return 0;
> +}
> +
>   int
>   objfile_find_memory_regions (struct target_ops *self,
>   			     find_memory_region_ftype func, void *obfd)
> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   			   (flags & SEC_READONLY) == 0, /* Writable.  */
>   			   (flags & SEC_CODE) != 0, /* Executable.  */
>   			   1, /* MODIFIED is unknown, pass it as true.  */
> +			   false, /* No memory tags in the object file.  */
>   			   obfd);
>   	    if (ret != 0)
>   	      return ret;
> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Stack section will be writable.  */
>   	     0, /* Stack section will not be executable.  */
>   	     1, /* Stack section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     /* Make a heap segment.  */
> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Heap section will be writable.  */
>   	     0, /* Heap section will not be executable.  */
>   	     1, /* Heap section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     return 0;
> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>       }
>   }
>   
> +/* Callback to copy contents to a particular memory tag section.  */
> +
> +static void
> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> +{
> +  /* We are only interested in "memtag" sections.  */
> +  if (!startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
> +  /* Fill the section with memory tag contents.  */
> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> +    error (_("Failed to fill memory tag section for core file."));
> +}
> +
>   static int
>   gcore_memory_sections (bfd *obfd)
>   {
> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>   	return 0;			/* FIXME: error return/msg?  */
>       }
>   
> +  /* Take care of dumping memory tags, if there are any.  */
> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> +      || gdbarch_find_memory_regions (target_gdbarch (),
> +				      gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +    {
> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +	return 0;
> +    }
> +
>     /* Record phdrs for section-to-segment mapping.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
>       make_output_phdrs (obfd, sect);
>   
> -  /* Copy memory region contents.  */
> +  /* Copy memory region and memory tag contents.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
> -    gcore_copy_callback (obfd, sect);
> +    {
> +      gcore_copy_callback (obfd, sect);
> +      gcore_copy_memtag_section_callback (obfd, sect);
> +    }
>   
>     return 1;
>   }
> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
> index e8f20c83ff0..6fa1b7591db 100644
> --- a/gdb/gdbarch-components.py
> +++ b/gdb/gdbarch-components.py
> @@ -1522,6 +1522,41 @@ Find core file memory regions
>       invalid=True,
>   )
>   
> +Method(
> +    comment="""
> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
> +""",
> +    type="asection *",
> +    name="create_memtag_section",
> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
> +""",
> +    type="bool",
> +    name="fill_memtag_section",
> +    params=[("asection *", "osec")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Decode a memory tag SECTION and return the tags of type TYPE contained in
> +the memory range [ADDRESS, ADDRESS + LENGTH).
> +If no tags were found, return an empty vector.
> +""",
> +    type="gdb::byte_vector",
> +    name="decode_memtag_section",
> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
>   Method(
>       comment="""
>   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index 882b9057b1a..1d19f51f21d 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
> +
> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
> +
> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
> +
> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
> +
> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
> +   the memory range [ADDRESS, ADDRESS + LENGTH).
> +   If no tags were found, return an empty vector. */
> +
> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index a588bdef61a..f5dbacb14e7 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -171,6 +171,9 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_section, has predicate.  */
> +  /* Skip verify of fill_memtag_section, has predicate.  */
> +  /* Skip verify of decode_memtag_section, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     gdb_printf (file,
>                         "gdbarch_dump: find_memory_regions = <%s>\n",
>                         host_address_to_string (gdbarch->find_memory_regions));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
> +                      gdbarch_create_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
> +                      gdbarch_fill_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->fill_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
> +                      gdbarch_decode_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_section));
>     gdb_printf (file,
>                         "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>                         gdbarch_core_xfer_shared_libraries_p (gdbarch));
> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_section != NULL;
> +}
> +
> +asection *
> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
> +}
> +
> +void
> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
> +{
> +  gdbarch->create_memtag_section = create_memtag_section;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->fill_memtag_section != NULL;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
> +  return gdbarch->fill_memtag_section (gdbarch, osec);
> +}
> +
> +void
> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
> +{
> +  gdbarch->fill_memtag_section = fill_memtag_section;
> +}
> +
> +bool
> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_section != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
> +{
> +  gdbarch->decode_memtag_section = decode_memtag_section;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 4e728a06e7e..8a83ed320cf 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -42,6 +42,7 @@
>   #include "gcore.h"
>   #include "gcore-elf.h"
>   #include "solib-svr4.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   #include <unordered_map>
> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>   					    ULONGEST offset, ULONGEST inode,
>   					    int read, int write,
>   					    int exec, int modified,
> +					    bool memory_tagged,
>   					    const char *filename,
>   					    void *data);
>   
> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_section_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>   		map.offset, map.inode, map.read, map.write, map.exec,
>   		1, /* MODIFIED is true because we want to dump
>   		      the mapping.  */
> +		map.vmflags.memory_tagging != 0,
>   		map.filename.c_str (), obfd);
>   	}
>       }
> @@ -1621,12 +1649,14 @@ static int
>   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>   				 ULONGEST offset, ULONGEST inode,
>   				 int read, int write, int exec, int modified,
> +				 bool memory_tagged,
>   				 const char *filename, void *arg)
>   {
>     struct linux_find_memory_regions_data *data
>       = (struct linux_find_memory_regions_data *) arg;
>   
> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
> +		     data->obfd);
>   }
>   
>   /* A variant of linux_find_memory_regions_full that is suitable as the
> @@ -1675,6 +1705,7 @@ static int
>   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>   			      ULONGEST offset, ULONGEST inode,
>   			      int read, int write, int exec, int modified,
> +			      bool memory_tagged,
>   			      const char *filename, void *data)
>   {
>     struct linux_make_mappings_data *map_data
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..af86137c49d
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,61 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 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 "memtag.h"
> +#include "bfd.h"
> +
> +/* See memtag.h */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_section_info &info)
> +{
> +  /* If the caller provided no SECTION to start from, search from the
> +     beginning.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, "memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t memtag_range_size = section->rawsize;
> +      size_t tags_size = bfd_section_size (section);
> +
> +      /* Empty memory range and empty tag dump should not happen.  */
> +      gdb_assert (memtag_range_size != 0);
> +      gdb_assert (tags_size != 0);
> +
> +      CORE_ADDR start_address = bfd_section_vma (section);
> +      CORE_ADDR end_address = start_address + memtag_range_size;
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +      section = bfd_get_next_section_by_name (abfd, section);
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..fe908c1e5e3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,50 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_section_info
> +{
> +  /* The start address of the tagged memory range.  */
> +  CORE_ADDR start_address;
> +  /* The final address of the tagged memory range.  */
> +  CORE_ADDR end_address;
> +  /* The section containing tags for the memory range
> +     [start_address, end_address).  */
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through memory tag sections in a core file.
> +
> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address,
> +				   memtag_section_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..8a19c4b449e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,107 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* Re: [PATCH, v4] [AArch64] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
@ 2022-05-12 10:36   ` Luis Machado
  2022-05-18 12:46   ` Luis Machado
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 47+ messages in thread
From: Luis Machado @ 2022-05-12 10:36 UTC (permalink / raw)
  To: gdb-patches

ping?

On 5/3/22 22:56, Luis Machado via Gdb-patches wrote:
> v4:
> 
> - Updated documentation (added cross-references).
> - Updated the segment name from PT_ARM_MEMTAG_MTE to
>    PT_AARCH64_MEMTAG_MTE.
> 
> v3:
> 
> - Updated NEWS and documentation to be more thorough.
> 
> v2:
> 
> - Rework memory tag section handling to use generic section fields.
> 
> --
> 
> Teach GDB how to dump memory tags for AArch64 when using the gcore command
> and how to read memory tag data back from a core file generated by GDB
> (via gcore) or by the Linux kernel.
> 
> The format is documented in the Linux Kernel documentation [1].
> 
> Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
> own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
> of those segments when reading the core file back.
> 
> To save a little bit of space, given MTE tags only take 4 bits, the memory tags
> are stored packed as 2 tags per byte.
> 
> When reading the data back, the tags are unpacked.
> 
> I've added a new testcase to exercise the feature.
> 
> Build-tested with --enable-targets=all and regression tested on aarch64-linux
> Ubuntu 20.04.
> 
> [1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
> ---
>   gdb/Makefile.in                              |   1 +
>   gdb/NEWS                                     |  10 ++
>   gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
>   gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
>   gdb/arch/aarch64-mte-linux.h                 |  10 ++
>   gdb/corelow.c                                |  62 +++++++
>   gdb/defs.h                                   |   3 +-
>   gdb/doc/gdb.texinfo                          |  19 +++
>   gdb/gcore.c                                  |  83 ++++++++-
>   gdb/gdbarch-components.py                    |  35 ++++
>   gdb/gdbarch-gen.h                            |  26 +++
>   gdb/gdbarch.c                                |  96 +++++++++++
>   gdb/linux-tdep.c                             |  39 ++++-
>   gdb/memtag.c                                 |  61 +++++++
>   gdb/memtag.h                                 |  50 ++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
>   gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
>   17 files changed, 910 insertions(+), 8 deletions(-)
>   create mode 100644 gdb/memtag.c
>   create mode 100644 gdb/memtag.h
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
>   create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> 
> diff --git a/gdb/Makefile.in b/gdb/Makefile.in
> index 418094775a5..fac9364bea4 100644
> --- a/gdb/Makefile.in
> +++ b/gdb/Makefile.in
> @@ -1120,6 +1120,7 @@ COMMON_SFILES = \
>   	memattr.c \
>   	memory-map.c \
>   	memrange.c \
> +	memtag.c \
>   	minidebug.c \
>   	minsyms.c \
>   	mipsread.c \
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 982f4a1a18c..3d925dc3663 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,16 @@
>   
>   *** Changes since GDB 12
>   
> +* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
> +  reading memory tag data for AArch64 MTE from core files generated by
> +  the gcore command or the Linux kernel.
> +
> +  When a process uses memory-mapped pages protected by memory tags (for
> +  example, AArch64 MTE), this additional information will be recorded in
> +  the core file in the event of a crash or if GDB generates a core file
> +  from the current process state.  GDB will show this additional information
> +  automatically, or through one of the memory-tag subcommands.
> +
>   * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
>   
>   * Remove support for building against Python 2, it is now only possible to
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index 55094b3d88b..12d98e71796 100644
> --- a/gdb/aarch64-linux-tdep.c
> +++ b/gdb/aarch64-linux-tdep.c
> @@ -53,6 +53,9 @@
>   
>   #include "gdbsupport/selftest.h"
>   
> +#include "elf/common.h"
> +#include "elf/aarch64.h"
> +
>   /* Signal frame handling.
>   
>         +------------+  ^
> @@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
>       }
>   }
>   
> +/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
> +
> +static asection *
> +aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
> +				     CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (obfd != nullptr);
> +  gdb_assert (size > 0);
> +
> +  /* Create the section and associated program header.  */
> +  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
> +
> +  if (mte_section == nullptr)
> +    return nullptr;
> +
> +  bfd_set_section_vma (mte_section, address);
> +  /* The size of the memory range covered by the memory tags.  We reuse the
> +     section's rawsize field for this purpose.  */
> +  mte_section->rawsize = size;
> +  /* Tags are stored packed as 2 tags per byte.  */
> +  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
> +  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
> +     refuse to write data to this section.  */
> +  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
> +
> +  /* Store program header information.  */
> +  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
> +		   &mte_section);
> +
> +  return mte_section;
> +}
> +
> +/* Maximum number of tags to request.  */
> +#define MAX_TAGS_TO_TRANSFER 1024
> +
> +/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
> +
> +static bool
> +aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  /* We only handle MTE tags for now.  */
> +
> +  size_t segment_size = osec->rawsize;
> +  CORE_ADDR start_address = bfd_section_vma (osec);
> +  CORE_ADDR end_address = start_address + segment_size;
> +
> +  /* Figure out how many tags we need to store in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* If there are no tag granules to fetch, just return.  */
> +  if (granules == 0)
> +    return true;
> +
> +  CORE_ADDR address = start_address;
> +
> +  /* Vector of tags.  */
> +  gdb::byte_vector tags;
> +
> +  while (granules > 0)
> +    {
> +      /* Transfer tags in chunks.  */
> +      gdb::byte_vector tags_read;
> +      size_t xfer_len
> +	= (granules >= MAX_TAGS_TO_TRANSFER)?
> +	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
> +	  granules * AARCH64_MTE_GRANULE_SIZE;
> +
> +      if (!target_fetch_memtags (address, xfer_len, tags_read,
> +				 static_cast<int> (memtag_type::allocation)))
> +	{
> +	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
> +		     phex_nz (start_address, sizeof (start_address)),
> +		     phex_nz (end_address, sizeof (end_address)));
> +	  return false;
> +	}
> +
> +      /* Transfer over the tags that have been read.  */
> +      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
> +
> +      /* Adjust the remaining granules and starting address.  */
> +      granules -= tags_read.size ();
> +      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
> +    }
> +
> +  /* Pack the MTE tag bits.  */
> +  aarch64_mte_pack_tags (tags);
> +
> +  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
> +				 0, tags.size ()))
> +    {
> +      warning (_("Failed to write %s bytes of corefile memory "
> +		 "tag content (%s)."),
> +	       pulongest (tags.size ()),
> +	       bfd_errmsg (bfd_get_error ()));
> +    }
> +  return true;
> +}
> +
> +/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
> +   hook.  Decode a memory tag section and return the requested tags.
> +
> +   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
> +   range.  */
> +
> +static gdb::byte_vector
> +aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
> +				     bfd_section *section,
> +				     int type,
> +				     CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (section != nullptr);
> +
> +  /* The requested address must not be less than section->vma.  */
> +  gdb_assert (section->vma <= address);
> +
> +  /* Figure out how many tags we need to fetch in this memory range.  */
> +  size_t granules = aarch64_mte_get_tag_granules (address, length,
> +						  AARCH64_MTE_GRANULE_SIZE);
> +  /* Sanity check.  */
> +  gdb_assert (granules > 0);
> +
> +  /* Fetch the total number of tags in the range [VMA, address + length).  */
> +  size_t granules_from_vma
> +    = aarch64_mte_get_tag_granules (section->vma,
> +				    address - section->vma + length,
> +				    AARCH64_MTE_GRANULE_SIZE);
> +
> +  /* Adjust the tags vector to contain the exact number of packed bytes.  */
> +  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
> +
> +  /* Figure out the starting offset into the packed tags data.  */
> +  file_ptr offset = ((granules_from_vma - granules) >> 1);
> +
> +  if (!bfd_get_section_contents (section->owner, section, tags.data (),
> +				 offset, tags.size ()))
> +    error (_("Couldn't read contents from memtag section."));
> +
> +  /* At this point, the tags are packed 2 per byte.  Unpack them before
> +     returning.  */
> +  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
> +  aarch64_mte_unpack_tags (tags, skip_first);
> +
> +  /* Resize to the exact number of tags that was requested.  */
> +  tags.resize (granules);
> +
> +  return tags;
> +}
> +
>   static void
>   aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   {
> @@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
>   
>         set_gdbarch_report_signal_info (gdbarch,
>   				      aarch64_linux_report_signal_info);
> +
> +      /* Core file helpers.  */
> +
> +      /* Core file helper to create a memory tag section for a particular
> +	 PT_LOAD segment.  */
> +      set_gdbarch_create_memtag_section
> +	(gdbarch, aarch64_linux_create_memtag_section);
> +
> +      /* Core file helper to fill a memory tag section with tag data.  */
> +      set_gdbarch_fill_memtag_section
> +	(gdbarch, aarch64_linux_fill_memtag_section);
> +
> +      /* Core file helper to decode a memory tag section.  */
> +      set_gdbarch_decode_memtag_section (gdbarch,
> +					 aarch64_linux_decode_memtag_section);
>       }
>   
>     /* Initialize the aarch64_linux_record_tdep.  */
> diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
> index fc7a8cc00f7..3af6f364e91 100644
> --- a/gdb/arch/aarch64-mte-linux.c
> +++ b/gdb/arch/aarch64-mte-linux.c
> @@ -21,6 +21,62 @@
>   
>   /* See arch/aarch64-mte-linux.h */
>   
> +void
> +aarch64_mte_pack_tags (gdb::byte_vector &tags)
> +{
> +  /* Nothing to pack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* If the tags vector has an odd number of elements, add another
> +     zeroed-out element to make it even.  This facilitates packing.  */
> +  if ((tags.size () % 2) != 0)
> +    tags.emplace_back (0);
> +
> +  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
> +       unpacked += 2, packed++)
> +    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
> +
> +  /* Now we have half the size.  */
> +  tags.resize (tags.size () / 2);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
> +void
> +aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
> +{
> +  /* Nothing to unpack?  */
> +  if (tags.empty ())
> +    return;
> +
> +  /* An unpacked MTE tags vector will have twice the number of elements
> +     compared to an unpacked one.  */
> +  gdb::byte_vector unpacked_tags (tags.size () * 2);
> +
> +  int unpacked = 0, packed = 0;
> +
> +  if (skip_first)
> +    {
> +      /* We are not interested in the first unpacked element, just discard
> +	 it.  */
> +      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
> +      unpacked++;
> +      packed++;
> +    }
> +
> +  for (; packed < tags.size (); unpacked += 2, packed++)
> +    {
> +      unpacked_tags[unpacked] = tags[packed] & 0xf;
> +      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
> +    }
> +
> +  /* Update the original tags vector.  */
> +  tags = std::move (unpacked_tags);
> +}
> +
> +/* See arch/aarch64-mte-linux.h */
> +
>   size_t
>   aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
>   {
> diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
> index d158926feff..8a145b447aa 100644
> --- a/gdb/arch/aarch64-mte-linux.h
> +++ b/gdb/arch/aarch64-mte-linux.h
> @@ -32,6 +32,7 @@
>   
>   /* We have one tag per 16 bytes of memory.  */
>   #define AARCH64_MTE_GRANULE_SIZE 16
> +#define AARCH64_MTE_TAG_BIT_SIZE 4
>   #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
>   #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
>   
> @@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
>      It is always possible to get the logical tag.  */
>   extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
>   
> +/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
> +   2 tags per byte and resize the vector.  */
> +void aarch64_mte_pack_tags (gdb::byte_vector &tags);
> +
> +/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
> +   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
> +   first unpacked element.  Otherwise leave it in the unpacked vector.  */
> +void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
> +
>   #endif /* ARCH_AARCH64_LINUX_H */
> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index 8c33fb7ebb2..8b8994f80db 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -52,6 +52,7 @@
>   #include <unordered_set>
>   #include "gdbcmd.h"
>   #include "xml-tdesc.h"
> +#include "memtag.h"
>   
>   #ifndef O_LARGEFILE
>   #define O_LARGEFILE 0
> @@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
>   
>     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;
> +
>     /* A few helpers.  */
>   
>     /* Getter, see variable definition.  */
> @@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
> +{
> +  struct gdbarch *gdbarch = target_gdbarch ();
> +
> +  /* 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 (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;
> +}
> +
>   /* Get a pointer to the current core target.  If not connected to a
>      core target, return NULL.  */
>   
> diff --git a/gdb/defs.h b/gdb/defs.h
> index 99bfdd526ff..51a7576a56a 100644
> --- a/gdb/defs.h
> +++ b/gdb/defs.h
> @@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
>   
>   typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
>   					 int read, int write, int exec,
> -					 int modified, void *data);
> +					 int modified, bool memory_tagged,
> +					 void *data);
>   
>   /* * Possible lvalue types.  Like enum language, this should be in
>      value.h, but needs to be here for the same reason.  */
> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index 38ad2ac32b0..36f10f20cfb 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
>   option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
>   documentation in the Linux kernel.
>   
> +@value{GDBN} supports dumping memory tag data to core files through the
> +@command{gcore} command and reading memory tag data from core files generated
> +by the @command{gcore} command or the Linux kernel.
> +
> +When a process uses memory-mapped pages protected by memory tags (for
> +example, AArch64 MTE), this additional information will be recorded in
> +the core file in the event of a crash or if @value{GDBN} generates a core file
> +from the current process state.
> +
> +The memory tag data will be used so developers can display the memory
> +tags from a particular memory region (using the @samp{m} modifier to the
> +@command{x} command, using the @command{print} command or using the various
> +@command{memory-tag} subcommands.
> +
> +In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
> +information automatically from the core file, and will show one of the above
> +messages depending on whether the synchronous or asynchronous mode is selected.
> +@xref{Memory Tagging}. @xref{Memory}.
> +
>   @node i386
>   @subsection x86 Architecture-specific Issues
>   
> diff --git a/gdb/gcore.c b/gdb/gcore.c
> index fdb22b72a07..b81ef81ab84 100644
> --- a/gdb/gcore.c
> +++ b/gdb/gcore.c
> @@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
>     int p_flags = 0;
>     int p_type = 0;
>   
> +  /* Memory tag segments have already been handled by the architecture, as
> +     those contain arch-specific information.  If we have one of those, just
> +     return.  */
> +  if (startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
>     /* FIXME: these constants may only be applicable for ELF.  */
>     if (startswith (bfd_section_name (osec), "load"))
>       p_type = PT_LOAD;
> @@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
>   
>   static int
>   gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
> -		       int write, int exec, int modified, void *data)
> +		       int write, int exec, int modified, bool memory_tagged,
> +		       void *data)
>   {
>     bfd *obfd = (bfd *) data;
>     asection *osec;
> @@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
>     return 0;
>   }
>   
> +/* gdbarch_find_memory_region callback for creating a memory tag section.
> +   DATA is 'bfd *' for the core file GDB is creating.  */
> +
> +static int
> +gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
> +				      int read, int write, int exec,
> +				      int modified, bool memory_tagged,
> +				      void *data)
> +{
> +  /* Are there memory tags in this particular memory map entry?  */
> +  if (!memory_tagged)
> +    return 0;
> +
> +  bfd *obfd = (bfd *) data;
> +
> +  /* Ask the architecture to create a memory tag section for this particular
> +     memory map entry.  It will be populated with contents later, as we can't
> +     start writing the contents before we have all the sections sorted out.  */
> +  asection *memtag_section
> +    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
> +
> +  if (memtag_section == nullptr)
> +    {
> +      warning (_("Couldn't make gcore memory tag segment: %s"),
> +	       bfd_errmsg (bfd_get_error ()));
> +      return 1;
> +    }
> +
> +  if (info_verbose)
> +    {
> +      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
> +			      "at %s\n",
> +		  plongest (bfd_section_size (memtag_section)),
> +		  paddress (target_gdbarch (), vaddr));
> +    }
> +
> +  return 0;
> +}
> +
>   int
>   objfile_find_memory_regions (struct target_ops *self,
>   			     find_memory_region_ftype func, void *obfd)
> @@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   			   (flags & SEC_READONLY) == 0, /* Writable.  */
>   			   (flags & SEC_CODE) != 0, /* Executable.  */
>   			   1, /* MODIFIED is unknown, pass it as true.  */
> +			   false, /* No memory tags in the object file.  */
>   			   obfd);
>   	    if (ret != 0)
>   	      return ret;
> @@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Stack section will be writable.  */
>   	     0, /* Stack section will not be executable.  */
>   	     1, /* Stack section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     /* Make a heap segment.  */
> @@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
>   	     1, /* Heap section will be writable.  */
>   	     0, /* Heap section will not be executable.  */
>   	     1, /* Heap section will be modified.  */
> +	     false, /* No memory tags in the object file.  */
>   	     obfd);
>   
>     return 0;
> @@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
>       }
>   }
>   
> +/* Callback to copy contents to a particular memory tag section.  */
> +
> +static void
> +gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
> +{
> +  /* We are only interested in "memtag" sections.  */
> +  if (!startswith (bfd_section_name (osec), "memtag"))
> +    return;
> +
> +  /* Fill the section with memory tag contents.  */
> +  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
> +    error (_("Failed to fill memory tag section for core file."));
> +}
> +
>   static int
>   gcore_memory_sections (bfd *obfd)
>   {
> @@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
>   	return 0;			/* FIXME: error return/msg?  */
>       }
>   
> +  /* Take care of dumping memory tags, if there are any.  */
> +  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
> +      || gdbarch_find_memory_regions (target_gdbarch (),
> +				      gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +    {
> +      if (target_find_memory_regions (gcore_create_memtag_section_callback,
> +				      obfd) != 0)
> +	return 0;
> +    }
> +
>     /* Record phdrs for section-to-segment mapping.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
>       make_output_phdrs (obfd, sect);
>   
> -  /* Copy memory region contents.  */
> +  /* Copy memory region and memory tag contents.  */
>     for (asection *sect : gdb_bfd_sections (obfd))
> -    gcore_copy_callback (obfd, sect);
> +    {
> +      gcore_copy_callback (obfd, sect);
> +      gcore_copy_memtag_section_callback (obfd, sect);
> +    }
>   
>     return 1;
>   }
> diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
> index e8f20c83ff0..6fa1b7591db 100644
> --- a/gdb/gdbarch-components.py
> +++ b/gdb/gdbarch-components.py
> @@ -1522,6 +1522,41 @@ Find core file memory regions
>       invalid=True,
>   )
>   
> +Method(
> +    comment="""
> +Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
> +""",
> +    type="asection *",
> +    name="create_memtag_section",
> +    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
> +""",
> +    type="bool",
> +    name="fill_memtag_section",
> +    params=[("asection *", "osec")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
> +Method(
> +    comment="""
> +Decode a memory tag SECTION and return the tags of type TYPE contained in
> +the memory range [ADDRESS, ADDRESS + LENGTH).
> +If no tags were found, return an empty vector.
> +""",
> +    type="gdb::byte_vector",
> +    name="decode_memtag_section",
> +    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
> +    predicate=True,
> +    invalid=True,
> +)
> +
>   Method(
>       comment="""
>   Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
> diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
> index 882b9057b1a..1d19f51f21d 100644
> --- a/gdb/gdbarch-gen.h
> +++ b/gdb/gdbarch-gen.h
> @@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
>   extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
>   extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
>   
> +/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
> +
> +extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
> +extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
> +
> +/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
> +
> +extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
> +extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
> +extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
> +
> +/* Decode a memory tag SECTION and return the tags of type TYPE contained in
> +   the memory range [ADDRESS, ADDRESS + LENGTH).
> +   If no tags were found, return an empty vector. */
> +
> +extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
> +
> +typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
> +extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
> +
>   /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
>      core file into buffer READBUF with length LEN.  Return the number of bytes read
>      (zero indicates failure).
> diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
> index a588bdef61a..f5dbacb14e7 100644
> --- a/gdb/gdbarch.c
> +++ b/gdb/gdbarch.c
> @@ -171,6 +171,9 @@ struct gdbarch
>     gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
>     gdbarch_make_corefile_notes_ftype *make_corefile_notes;
>     gdbarch_find_memory_regions_ftype *find_memory_regions;
> +  gdbarch_create_memtag_section_ftype *create_memtag_section;
> +  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
> +  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
>     gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
>     gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
>     gdbarch_core_pid_to_str_ftype *core_pid_to_str;
> @@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
>     /* Skip verify of iterate_over_regset_sections, has predicate.  */
>     /* Skip verify of make_corefile_notes, has predicate.  */
>     /* Skip verify of find_memory_regions, has predicate.  */
> +  /* Skip verify of create_memtag_section, has predicate.  */
> +  /* Skip verify of fill_memtag_section, has predicate.  */
> +  /* Skip verify of decode_memtag_section, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries, has predicate.  */
>     /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
>     /* Skip verify of core_pid_to_str, has predicate.  */
> @@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
>     gdb_printf (file,
>                         "gdbarch_dump: find_memory_regions = <%s>\n",
>                         host_address_to_string (gdbarch->find_memory_regions));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
> +                      gdbarch_create_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: create_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->create_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
> +                      gdbarch_fill_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: fill_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->fill_memtag_section));
> +  gdb_printf (file,
> +                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
> +                      gdbarch_decode_memtag_section_p (gdbarch));
> +  gdb_printf (file,
> +                      "gdbarch_dump: decode_memtag_section = <%s>\n",
> +                      host_address_to_string (gdbarch->decode_memtag_section));
>     gdb_printf (file,
>                         "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
>                         gdbarch_core_xfer_shared_libraries_p (gdbarch));
> @@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
>     gdbarch->find_memory_regions = find_memory_regions;
>   }
>   
> +bool
> +gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->create_memtag_section != NULL;
> +}
> +
> +asection *
> +gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->create_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
> +  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
> +}
> +
> +void
> +set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_create_memtag_section_ftype create_memtag_section)
> +{
> +  gdbarch->create_memtag_section = create_memtag_section;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->fill_memtag_section != NULL;
> +}
> +
> +bool
> +gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->fill_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
> +  return gdbarch->fill_memtag_section (gdbarch, osec);
> +}
> +
> +void
> +set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
> +                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
> +{
> +  gdbarch->fill_memtag_section = fill_memtag_section;
> +}
> +
> +bool
> +gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  return gdbarch->decode_memtag_section != NULL;
> +}
> +
> +gdb::byte_vector
> +gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
> +{
> +  gdb_assert (gdbarch != NULL);
> +  gdb_assert (gdbarch->decode_memtag_section != NULL);
> +  if (gdbarch_debug >= 2)
> +    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
> +  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
> +}
> +
> +void
> +set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
> +                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
> +{
> +  gdbarch->decode_memtag_section = decode_memtag_section;
> +}
> +
>   bool
>   gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
>   {
> diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
> index 4e728a06e7e..8a83ed320cf 100644
> --- a/gdb/linux-tdep.c
> +++ b/gdb/linux-tdep.c
> @@ -42,6 +42,7 @@
>   #include "gcore.h"
>   #include "gcore-elf.h"
>   #include "solib-svr4.h"
> +#include "memtag.h"
>   
>   #include <ctype.h>
>   #include <unordered_map>
> @@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
>   					    ULONGEST offset, ULONGEST inode,
>   					    int read, int write,
>   					    int exec, int modified,
> +					    bool memory_tagged,
>   					    const char *filename,
>   					    void *data);
>   
> @@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
>     return smaps;
>   }
>   
> -/* See linux-tdep.h.  */
> +/* Helper that checks if an address is in a memory tag page for a live
> +   process.  */
>   
> -bool
> -linux_address_in_memtag_page (CORE_ADDR address)
> +static bool
> +linux_process_address_in_memtag_page (CORE_ADDR address)
>   {
>     if (current_inferior ()->fake_pid_p)
>       return false;
> @@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
>     return false;
>   }
>   
> +/* Helper that checks if an address is in a memory tag page for a core file
> +   process.  */
> +
> +static bool
> +linux_core_file_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (core_bfd == nullptr)
> +    return false;
> +
> +  memtag_section_info info;
> +  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
> +}
> +
> +/* See linux-tdep.h.  */
> +
> +bool
> +linux_address_in_memtag_page (CORE_ADDR address)
> +{
> +  if (!target_has_execution ())
> +    return linux_core_file_address_in_memtag_page (address);
> +
> +  return linux_process_address_in_memtag_page (address);
> +}
> +
>   /* List memory regions in the inferior for a corefile.  */
>   
>   static int
> @@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
>   		map.offset, map.inode, map.read, map.write, map.exec,
>   		1, /* MODIFIED is true because we want to dump
>   		      the mapping.  */
> +		map.vmflags.memory_tagging != 0,
>   		map.filename.c_str (), obfd);
>   	}
>       }
> @@ -1621,12 +1649,14 @@ static int
>   linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
>   				 ULONGEST offset, ULONGEST inode,
>   				 int read, int write, int exec, int modified,
> +				 bool memory_tagged,
>   				 const char *filename, void *arg)
>   {
>     struct linux_find_memory_regions_data *data
>       = (struct linux_find_memory_regions_data *) arg;
>   
> -  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
> +  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
> +		     data->obfd);
>   }
>   
>   /* A variant of linux_find_memory_regions_full that is suitable as the
> @@ -1675,6 +1705,7 @@ static int
>   linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
>   			      ULONGEST offset, ULONGEST inode,
>   			      int read, int write, int exec, int modified,
> +			      bool memory_tagged,
>   			      const char *filename, void *data)
>   {
>     struct linux_make_mappings_data *map_data
> diff --git a/gdb/memtag.c b/gdb/memtag.c
> new file mode 100644
> index 00000000000..af86137c49d
> --- /dev/null
> +++ b/gdb/memtag.c
> @@ -0,0 +1,61 @@
> +/* GDB generic memory tagging functions.
> +
> +   Copyright (C) 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 "memtag.h"
> +#include "bfd.h"
> +
> +/* See memtag.h */
> +
> +bool
> +get_next_core_memtag_section (bfd *abfd, asection *section,
> +			      CORE_ADDR address, memtag_section_info &info)
> +{
> +  /* If the caller provided no SECTION to start from, search from the
> +     beginning.  */
> +  if (section == nullptr)
> +    section = bfd_get_section_by_name (abfd, "memtag");
> +
> +  /* Go through all the memtag sections and figure out if ADDRESS
> +     falls within one of the memory ranges that contain tags.  */
> +  while (section != nullptr)
> +    {
> +      size_t memtag_range_size = section->rawsize;
> +      size_t tags_size = bfd_section_size (section);
> +
> +      /* Empty memory range and empty tag dump should not happen.  */
> +      gdb_assert (memtag_range_size != 0);
> +      gdb_assert (tags_size != 0);
> +
> +      CORE_ADDR start_address = bfd_section_vma (section);
> +      CORE_ADDR end_address = start_address + memtag_range_size;
> +
> +      /* Is the address within [start_address, end_address)?  */
> +      if (address >= start_address
> +	  && address < end_address)
> +	{
> +	  info.start_address = start_address;
> +	  info.end_address = end_address;
> +	  info.memtag_section = section;
> +	  return true;
> +	}
> +      section = bfd_get_next_section_by_name (abfd, section);
> +    }
> +  return false;
> +}
> diff --git a/gdb/memtag.h b/gdb/memtag.h
> new file mode 100644
> index 00000000000..fe908c1e5e3
> --- /dev/null
> +++ b/gdb/memtag.h
> @@ -0,0 +1,50 @@
> +/* GDB generic memory tagging definitions.
> +   Copyright (C) 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/>.  */
> +
> +#ifndef MEMTAG_H
> +#define MEMTAG_H
> +
> +#include "bfd.h"
> +
> +struct memtag_section_info
> +{
> +  /* The start address of the tagged memory range.  */
> +  CORE_ADDR start_address;
> +  /* The final address of the tagged memory range.  */
> +  CORE_ADDR end_address;
> +  /* The section containing tags for the memory range
> +     [start_address, end_address).  */
> +  asection *memtag_section;
> +};
> +
> +/* Helper function to walk through memory tag sections in a core file.
> +
> +   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
> +   otherwise.
> +
> +   If SECTION is provided, search from that section onwards. If SECTION is
> +   nullptr, then start a new search.
> +
> +   If a "memtag" section containing ADDRESS is found, fill INFO with data
> +   about such section.  Otherwise leave it unchanged.  */
> +
> +bool get_next_core_memtag_section (bfd *abfd, asection *section,
> +				   CORE_ADDR address,
> +				   memtag_section_info &info);
> +
> +#endif /* MEMTAG_H */
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> new file mode 100644
> index 00000000000..b20ebcff424
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
> @@ -0,0 +1,93 @@
> +/* This test program is part of GDB, the GNU debugger.
> +
> +   Copyright 2021 Free Software Foundation, Inc.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
> +
> +/* This test was based on the documentation for the AArch64 Memory Tagging
> +   Extension from the Linux Kernel, found in the sources in
> +   Documentation/arm64/memory-tagging-extension.rst.  */
> +
> +#include <errno.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/auxv.h>
> +#include <sys/mman.h>
> +#include <sys/prctl.h>
> +
> +/* From arch/arm64/include/uapi/asm/hwcap.h */
> +#define HWCAP2_MTE              (1 << 18)
> +
> +/* From arch/arm64/include/uapi/asm/mman.h */
> +#define PROT_MTE  0x20
> +
> +/* From include/uapi/linux/prctl.h */
> +#define PR_SET_TAGGED_ADDR_CTRL 55
> +#define PR_GET_TAGGED_ADDR_CTRL 56
> +#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
> +#define PR_MTE_TCF_SHIFT	1
> +#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
> +#define PR_MTE_TAG_SHIFT	3
> +
> +void
> +access_memory (unsigned char *tagged_ptr)
> +{
> +  tagged_ptr[0] = 'a';
> +}
> +
> +int
> +main (int argc, char **argv)
> +{
> +  unsigned char *tagged_ptr;
> +  unsigned long page_sz = sysconf (_SC_PAGESIZE);
> +  unsigned long hwcap2 = getauxval(AT_HWCAP2);
> +
> +  /* Bail out if MTE is not supported.  */
> +  if (!(hwcap2 & HWCAP2_MTE))
> +    return 1;
> +
> +  /* Enable the tagged address ABI, synchronous MTE tag check faults and
> +     allow all non-zero tags in the randomly generated set.  */
> +  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
> +	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
> +	     | (0xfffe << PR_MTE_TAG_SHIFT),
> +	     0, 0, 0))
> +    {
> +      perror ("prctl () failed");
> +      return 1;
> +    }
> +
> +  /* Create a mapping that will have PROT_MTE set.  */
> +  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
> +		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> +  if (tagged_ptr == MAP_FAILED)
> +    {
> +      perror ("mmap () failed");
> +      return 1;
> +    }
> +
> +  /* Enable MTE on the above anonymous mmap.  */
> +  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
> +    {
> +      perror ("mprotect () failed");
> +      return 1;
> +    }
> +
> +  access_memory (tagged_ptr);
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> new file mode 100644
> index 00000000000..8a19c4b449e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
> @@ -0,0 +1,107 @@
> +# Copyright (C) 2018-2021 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test generating and reading a core file with MTE memory tags.
> +
> +if {![is_aarch64_target]} {
> +    verbose "Skipping ${gdb_test_file_name}."
> +    return
> +}
> +
> +standard_testfile
> +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> +    return -1
> +}
> +
> +if ![runto_main] {
> +    untested "could not run to main"
> +    return -1
> +}
> +
> +# Targets that don't support memory tagging should not execute the
> +# runtime memory tagging tests.
> +if {![supports_memtag]} {
> +    unsupported "memory tagging unsupported"
> +    return -1
> +}
> +
> +gdb_breakpoint "access_memory"
> +
> +if [gdb_continue "access_memory"] {
> +    return -1
> +}
> +
> +# Set each tag granule to a different tag value, from 0x0 to 0xf.
> +set atag_msg "Allocation tag\\(s\\) updated successfully\."
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%02x" $i]
> +    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
> +	     $atag_msg \
> +	     "set memory tag of &tagged_ptr\[$index\] to $tag"
> +}
> +
> +# Run until a crash and confirm GDB displays memory tag violation
> +# information.
> +gdb_test "continue" \
> +    [multi_line \
> +	"Program received signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag $hex" \
> +	"Logical tag $hex\." \
> +	"$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "display tag violation information for live process"
> +
> +# Generate the core file.
> +set core_filename [standard_output_file "$testfile.core"]
> +set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
> +
> +if { !$core_generated } {
> +    return -1
> +}
> +
> +clean_restart $binfile
> +
> +# Load the core file and make sure we see the tag violation fault
> +# information.
> +gdb_test "core $core_filename" \
> +    [multi_line \
> +	"Core was generated by.*\." \
> +	"Program terminated with signal SIGSEGV, Segmentation fault" \
> +	"Memory tag violation while accessing address $hex" \
> +	"Allocation tag 0xf" \
> +	"Logical tag 0x0\." \
> +	"#0.*$hex in access_memory \\(.*\\) at .*" \
> +	".*tagged_ptr\\\[0\\\] = 'a';"] \
> +	 "core file shows tag violation information"
> +
> +# Make sure we have the tag_ctl register.
> +gdb_test "info register tag_ctl" \
> +	 "tag_ctl.*$hex.*${::decimal}" \
> +	 "tag_ctl is available"
> +
> +# Check if the tag granules have the expected values.  If they do, that
> +# means the core file saved the tags properly and GDB has read them
> +# correctly.
> +for {set i 15} {$i >= 0} {incr i -1} {
> +    set index [expr [expr 15 - $i] * 16]
> +    set tag [format "%x" $i]
> +    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
> +	     "= 0x$tag" \
> +	     "memory tag of &tagged_ptr\[$index\] is correct"
> +}


^ permalink raw reply	[flat|nested] 47+ messages in thread

* [PATCH, v4] [AArch64] MTE corefile support
  2022-03-31 14:03 [AArch64] " Luis Machado
@ 2022-05-03 21:56 ` Luis Machado
  2022-05-12 10:36   ` Luis Machado
                     ` (4 more replies)
  0 siblings, 5 replies; 47+ messages in thread
From: Luis Machado @ 2022-05-03 21:56 UTC (permalink / raw)
  To: gdb-patches

v4:

- Updated documentation (added cross-references).
- Updated the segment name from PT_ARM_MEMTAG_MTE to
  PT_AARCH64_MEMTAG_MTE.

v3:

- Updated NEWS and documentation to be more thorough.

v2:

- Rework memory tag section handling to use generic section fields.

--

Teach GDB how to dump memory tags for AArch64 when using the gcore command
and how to read memory tag data back from a core file generated by GDB
(via gcore) or by the Linux kernel.

The format is documented in the Linux Kernel documentation [1].

Each tagged memory range (listed in /proc/<pid>/smaps) gets dumped to its
own PT_AARCH64_MEMTAG_MTE segment. A section named ".memtag" is created for each
of those segments when reading the core file back.

To save a little bit of space, given MTE tags only take 4 bits, the memory tags
are stored packed as 2 tags per byte.

When reading the data back, the tags are unpacked.

I've added a new testcase to exercise the feature.

Build-tested with --enable-targets=all and regression tested on aarch64-linux
Ubuntu 20.04.

[1] Documentation/arm64/memory-tagging-extension.rst (Core Dump Support)
---
 gdb/Makefile.in                              |   1 +
 gdb/NEWS                                     |  10 ++
 gdb/aarch64-linux-tdep.c                     | 167 +++++++++++++++++++
 gdb/arch/aarch64-mte-linux.c                 |  56 +++++++
 gdb/arch/aarch64-mte-linux.h                 |  10 ++
 gdb/corelow.c                                |  62 +++++++
 gdb/defs.h                                   |   3 +-
 gdb/doc/gdb.texinfo                          |  19 +++
 gdb/gcore.c                                  |  83 ++++++++-
 gdb/gdbarch-components.py                    |  35 ++++
 gdb/gdbarch-gen.h                            |  26 +++
 gdb/gdbarch.c                                |  96 +++++++++++
 gdb/linux-tdep.c                             |  39 ++++-
 gdb/memtag.c                                 |  61 +++++++
 gdb/memtag.h                                 |  50 ++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c   |  93 +++++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp | 107 ++++++++++++
 17 files changed, 910 insertions(+), 8 deletions(-)
 create mode 100644 gdb/memtag.c
 create mode 100644 gdb/memtag.h
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 418094775a5..fac9364bea4 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1120,6 +1120,7 @@ COMMON_SFILES = \
 	memattr.c \
 	memory-map.c \
 	memrange.c \
+	memtag.c \
 	minidebug.c \
 	minsyms.c \
 	mipsread.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index 982f4a1a18c..3d925dc3663 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,16 @@
 
 *** Changes since GDB 12
 
+* GDB now supports dumping memory tag data for AArch64 MTE.  It also supports
+  reading memory tag data for AArch64 MTE from core files generated by
+  the gcore command or the Linux kernel.
+
+  When a process uses memory-mapped pages protected by memory tags (for
+  example, AArch64 MTE), this additional information will be recorded in
+  the core file in the event of a crash or if GDB generates a core file
+  from the current process state.  GDB will show this additional information
+  automatically, or through one of the memory-tag subcommands.
+
 * GDB now supports hardware watchpoints on FreeBSD/Aarch64.
 
 * Remove support for building against Python 2, it is now only possible to
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 55094b3d88b..12d98e71796 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -53,6 +53,9 @@
 
 #include "gdbsupport/selftest.h"
 
+#include "elf/common.h"
+#include "elf/aarch64.h"
+
 /* Signal frame handling.
 
       +------------+  ^
@@ -1781,6 +1784,155 @@ aarch64_linux_report_signal_info (struct gdbarch *gdbarch,
     }
 }
 
+/* AArch64 Linux implementation of the gdbarch_create_memtag_section hook.  */
+
+static asection *
+aarch64_linux_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd,
+				     CORE_ADDR address, size_t size)
+{
+  gdb_assert (obfd != nullptr);
+  gdb_assert (size > 0);
+
+  /* Create the section and associated program header.  */
+  asection *mte_section = bfd_make_section_anyway (obfd, "memtag");
+
+  if (mte_section == nullptr)
+    return nullptr;
+
+  bfd_set_section_vma (mte_section, address);
+  /* The size of the memory range covered by the memory tags.  We reuse the
+     section's rawsize field for this purpose.  */
+  mte_section->rawsize = size;
+  /* Tags are stored packed as 2 tags per byte.  */
+  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
+  /* Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
+     refuse to write data to this section.  */
+  bfd_set_section_flags (mte_section, SEC_HAS_CONTENTS);
+
+  /* Store program header information.  */
+  bfd_record_phdr (obfd, PT_AARCH64_MEMTAG_MTE, 1, 0, 0, 0, 0, 0, 1,
+		   &mte_section);
+
+  return mte_section;
+}
+
+/* Maximum number of tags to request.  */
+#define MAX_TAGS_TO_TRANSFER 1024
+
+/* AArch64 Linux implementation of the gdbarch_fill_memtag_section hook.  */
+
+static bool
+aarch64_linux_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
+{
+  /* We only handle MTE tags for now.  */
+
+  size_t segment_size = osec->rawsize;
+  CORE_ADDR start_address = bfd_section_vma (osec);
+  CORE_ADDR end_address = start_address + segment_size;
+
+  /* Figure out how many tags we need to store in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (start_address, segment_size,
+						  AARCH64_MTE_GRANULE_SIZE);
+
+  /* If there are no tag granules to fetch, just return.  */
+  if (granules == 0)
+    return true;
+
+  CORE_ADDR address = start_address;
+
+  /* Vector of tags.  */
+  gdb::byte_vector tags;
+
+  while (granules > 0)
+    {
+      /* Transfer tags in chunks.  */
+      gdb::byte_vector tags_read;
+      size_t xfer_len
+	= (granules >= MAX_TAGS_TO_TRANSFER)?
+	  MAX_TAGS_TO_TRANSFER * AARCH64_MTE_GRANULE_SIZE :
+	  granules * AARCH64_MTE_GRANULE_SIZE;
+
+      if (!target_fetch_memtags (address, xfer_len, tags_read,
+				 static_cast<int> (memtag_type::allocation)))
+	{
+	  warning (_("Failed to read MTE tags from memory range [%s,%s)."),
+		     phex_nz (start_address, sizeof (start_address)),
+		     phex_nz (end_address, sizeof (end_address)));
+	  return false;
+	}
+
+      /* Transfer over the tags that have been read.  */
+      tags.insert (tags.end (), tags_read.begin (), tags_read.end ());
+
+      /* Adjust the remaining granules and starting address.  */
+      granules -= tags_read.size ();
+      address += tags_read.size () * AARCH64_MTE_GRANULE_SIZE;
+    }
+
+  /* Pack the MTE tag bits.  */
+  aarch64_mte_pack_tags (tags);
+
+  if (!bfd_set_section_contents (osec->owner, osec, tags.data (),
+				 0, tags.size ()))
+    {
+      warning (_("Failed to write %s bytes of corefile memory "
+		 "tag content (%s)."),
+	       pulongest (tags.size ()),
+	       bfd_errmsg (bfd_get_error ()));
+    }
+  return true;
+}
+
+/* AArch64 Linux implementation of the gdbarch_decode_memtag_section
+   hook.  Decode a memory tag section and return the requested tags.
+
+   The section is guaranteed to cover the [ADDRESS, ADDRESS + length)
+   range.  */
+
+static gdb::byte_vector
+aarch64_linux_decode_memtag_section (struct gdbarch *gdbarch,
+				     bfd_section *section,
+				     int type,
+				     CORE_ADDR address, size_t length)
+{
+  gdb_assert (section != nullptr);
+
+  /* The requested address must not be less than section->vma.  */
+  gdb_assert (section->vma <= address);
+
+  /* Figure out how many tags we need to fetch in this memory range.  */
+  size_t granules = aarch64_mte_get_tag_granules (address, length,
+						  AARCH64_MTE_GRANULE_SIZE);
+  /* Sanity check.  */
+  gdb_assert (granules > 0);
+
+  /* Fetch the total number of tags in the range [VMA, address + length).  */
+  size_t granules_from_vma
+    = aarch64_mte_get_tag_granules (section->vma,
+				    address - section->vma + length,
+				    AARCH64_MTE_GRANULE_SIZE);
+
+  /* Adjust the tags vector to contain the exact number of packed bytes.  */
+  gdb::byte_vector tags (((granules - 1) >> 1) + 1);
+
+  /* Figure out the starting offset into the packed tags data.  */
+  file_ptr offset = ((granules_from_vma - granules) >> 1);
+
+  if (!bfd_get_section_contents (section->owner, section, tags.data (),
+				 offset, tags.size ()))
+    error (_("Couldn't read contents from memtag section."));
+
+  /* At this point, the tags are packed 2 per byte.  Unpack them before
+     returning.  */
+  bool skip_first = ((granules_from_vma - granules) % 2) != 0;
+  aarch64_mte_unpack_tags (tags, skip_first);
+
+  /* Resize to the exact number of tags that was requested.  */
+  tags.resize (granules);
+
+  return tags;
+}
+
 static void
 aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -1864,6 +2016,21 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
       set_gdbarch_report_signal_info (gdbarch,
 				      aarch64_linux_report_signal_info);
+
+      /* Core file helpers.  */
+
+      /* Core file helper to create a memory tag section for a particular
+	 PT_LOAD segment.  */
+      set_gdbarch_create_memtag_section
+	(gdbarch, aarch64_linux_create_memtag_section);
+
+      /* Core file helper to fill a memory tag section with tag data.  */
+      set_gdbarch_fill_memtag_section
+	(gdbarch, aarch64_linux_fill_memtag_section);
+
+      /* Core file helper to decode a memory tag section.  */
+      set_gdbarch_decode_memtag_section (gdbarch,
+					 aarch64_linux_decode_memtag_section);
     }
 
   /* Initialize the aarch64_linux_record_tdep.  */
diff --git a/gdb/arch/aarch64-mte-linux.c b/gdb/arch/aarch64-mte-linux.c
index fc7a8cc00f7..3af6f364e91 100644
--- a/gdb/arch/aarch64-mte-linux.c
+++ b/gdb/arch/aarch64-mte-linux.c
@@ -21,6 +21,62 @@
 
 /* See arch/aarch64-mte-linux.h */
 
+void
+aarch64_mte_pack_tags (gdb::byte_vector &tags)
+{
+  /* Nothing to pack?  */
+  if (tags.empty ())
+    return;
+
+  /* If the tags vector has an odd number of elements, add another
+     zeroed-out element to make it even.  This facilitates packing.  */
+  if ((tags.size () % 2) != 0)
+    tags.emplace_back (0);
+
+  for (int unpacked = 0, packed = 0; unpacked < tags.size ();
+       unpacked += 2, packed++)
+    tags[packed] = (tags[unpacked + 1] << 4) | tags[unpacked];
+
+  /* Now we have half the size.  */
+  tags.resize (tags.size () / 2);
+}
+
+/* See arch/aarch64-mte-linux.h */
+
+void
+aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first)
+{
+  /* Nothing to unpack?  */
+  if (tags.empty ())
+    return;
+
+  /* An unpacked MTE tags vector will have twice the number of elements
+     compared to an unpacked one.  */
+  gdb::byte_vector unpacked_tags (tags.size () * 2);
+
+  int unpacked = 0, packed = 0;
+
+  if (skip_first)
+    {
+      /* We are not interested in the first unpacked element, just discard
+	 it.  */
+      unpacked_tags[unpacked] = (tags[packed] >> 4) & 0xf;
+      unpacked++;
+      packed++;
+    }
+
+  for (; packed < tags.size (); unpacked += 2, packed++)
+    {
+      unpacked_tags[unpacked] = tags[packed] & 0xf;
+      unpacked_tags[unpacked + 1] = (tags[packed] >> 4) & 0xf;
+    }
+
+  /* Update the original tags vector.  */
+  tags = std::move (unpacked_tags);
+}
+
+/* See arch/aarch64-mte-linux.h */
+
 size_t
 aarch64_mte_get_tag_granules (CORE_ADDR addr, size_t len, size_t granule_size)
 {
diff --git a/gdb/arch/aarch64-mte-linux.h b/gdb/arch/aarch64-mte-linux.h
index d158926feff..8a145b447aa 100644
--- a/gdb/arch/aarch64-mte-linux.h
+++ b/gdb/arch/aarch64-mte-linux.h
@@ -32,6 +32,7 @@
 
 /* We have one tag per 16 bytes of memory.  */
 #define AARCH64_MTE_GRANULE_SIZE 16
+#define AARCH64_MTE_TAG_BIT_SIZE 4
 #define AARCH64_MTE_LOGICAL_TAG_START_BIT 56
 #define AARCH64_MTE_LOGICAL_MAX_VALUE 0xf
 
@@ -71,4 +72,13 @@ extern CORE_ADDR aarch64_mte_set_ltag (CORE_ADDR address, CORE_ADDR tag);
    It is always possible to get the logical tag.  */
 extern CORE_ADDR aarch64_mte_get_ltag (CORE_ADDR address);
 
+/* Given a TAGS vector containing 1 MTE tag per byte, pack the data as
+   2 tags per byte and resize the vector.  */
+void aarch64_mte_pack_tags (gdb::byte_vector &tags);
+
+/* Given a TAGS vector containing 2 MTE tags per byte, unpack the data as
+   1 tag per byte and resize the vector.  If SKIP_FIRST is TRUE, skip the
+   first unpacked element.  Otherwise leave it in the unpacked vector.  */
+void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
+
 #endif /* ARCH_AARCH64_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 8c33fb7ebb2..8b8994f80db 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -52,6 +52,7 @@
 #include <unordered_set>
 #include "gdbcmd.h"
 #include "xml-tdesc.h"
+#include "memtag.h"
 
 #ifndef O_LARGEFILE
 #define O_LARGEFILE 0
@@ -101,6 +102,13 @@ class core_target final : public process_stratum_target
 
   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;
+
   /* A few helpers.  */
 
   /* Getter, see variable definition.  */
@@ -1162,6 +1170,60 @@ core_target::info_proc (const char *args, enum info_proc_what 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 (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)
+{
+  struct gdbarch *gdbarch = target_gdbarch ();
+
+  /* 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 (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;
+}
+
 /* Get a pointer to the current core target.  If not connected to a
    core target, return NULL.  */
 
diff --git a/gdb/defs.h b/gdb/defs.h
index 99bfdd526ff..51a7576a56a 100644
--- a/gdb/defs.h
+++ b/gdb/defs.h
@@ -344,7 +344,8 @@ extern const char *pc_prefix (CORE_ADDR);
 
 typedef int (*find_memory_region_ftype) (CORE_ADDR addr, unsigned long size,
 					 int read, int write, int exec,
-					 int modified, void *data);
+					 int modified, bool memory_tagged,
+					 void *data);
 
 /* * Possible lvalue types.  Like enum language, this should be in
    value.h, but needs to be here for the same reason.  */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 38ad2ac32b0..36f10f20cfb 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25555,6 +25555,25 @@ options that can be controlled at runtime and emulates the @code{prctl}
 option @code{PR_SET_TAGGED_ADDR_CTRL}.  For further information, see the
 documentation in the Linux kernel.
 
+@value{GDBN} supports dumping memory tag data to core files through the
+@command{gcore} command and reading memory tag data from core files generated
+by the @command{gcore} command or the Linux kernel.
+
+When a process uses memory-mapped pages protected by memory tags (for
+example, AArch64 MTE), this additional information will be recorded in
+the core file in the event of a crash or if @value{GDBN} generates a core file
+from the current process state.
+
+The memory tag data will be used so developers can display the memory
+tags from a particular memory region (using the @samp{m} modifier to the
+@command{x} command, using the @command{print} command or using the various
+@command{memory-tag} subcommands.
+
+In the case of a crash, @value{GDBN} will attempt to retrieve the memory tag
+information automatically from the core file, and will show one of the above
+messages depending on whether the synchronous or asynchronous mode is selected.
+@xref{Memory Tagging}. @xref{Memory}.
+
 @node i386
 @subsection x86 Architecture-specific Issues
 
diff --git a/gdb/gcore.c b/gdb/gcore.c
index fdb22b72a07..b81ef81ab84 100644
--- a/gdb/gcore.c
+++ b/gdb/gcore.c
@@ -349,6 +349,12 @@ make_output_phdrs (bfd *obfd, asection *osec)
   int p_flags = 0;
   int p_type = 0;
 
+  /* Memory tag segments have already been handled by the architecture, as
+     those contain arch-specific information.  If we have one of those, just
+     return.  */
+  if (startswith (bfd_section_name (osec), "memtag"))
+    return;
+
   /* FIXME: these constants may only be applicable for ELF.  */
   if (startswith (bfd_section_name (osec), "load"))
     p_type = PT_LOAD;
@@ -371,7 +377,8 @@ make_output_phdrs (bfd *obfd, asection *osec)
 
 static int
 gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
-		       int write, int exec, int modified, void *data)
+		       int write, int exec, int modified, bool memory_tagged,
+		       void *data)
 {
   bfd *obfd = (bfd *) data;
   asection *osec;
@@ -454,6 +461,45 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, int read,
   return 0;
 }
 
+/* gdbarch_find_memory_region callback for creating a memory tag section.
+   DATA is 'bfd *' for the core file GDB is creating.  */
+
+static int
+gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size,
+				      int read, int write, int exec,
+				      int modified, bool memory_tagged,
+				      void *data)
+{
+  /* Are there memory tags in this particular memory map entry?  */
+  if (!memory_tagged)
+    return 0;
+
+  bfd *obfd = (bfd *) data;
+
+  /* Ask the architecture to create a memory tag section for this particular
+     memory map entry.  It will be populated with contents later, as we can't
+     start writing the contents before we have all the sections sorted out.  */
+  asection *memtag_section
+    = gdbarch_create_memtag_section (target_gdbarch (), obfd, vaddr, size);
+
+  if (memtag_section == nullptr)
+    {
+      warning (_("Couldn't make gcore memory tag segment: %s"),
+	       bfd_errmsg (bfd_get_error ()));
+      return 1;
+    }
+
+  if (info_verbose)
+    {
+      gdb_printf (gdb_stdout, "Saved memory tag segment, %s bytes "
+			      "at %s\n",
+		  plongest (bfd_section_size (memtag_section)),
+		  paddress (target_gdbarch (), vaddr));
+    }
+
+  return 0;
+}
+
 int
 objfile_find_memory_regions (struct target_ops *self,
 			     find_memory_region_ftype func, void *obfd)
@@ -483,6 +529,7 @@ objfile_find_memory_regions (struct target_ops *self,
 			   (flags & SEC_READONLY) == 0, /* Writable.  */
 			   (flags & SEC_CODE) != 0, /* Executable.  */
 			   1, /* MODIFIED is unknown, pass it as true.  */
+			   false, /* No memory tags in the object file.  */
 			   obfd);
 	    if (ret != 0)
 	      return ret;
@@ -496,6 +543,7 @@ objfile_find_memory_regions (struct target_ops *self,
 	     1, /* Stack section will be writable.  */
 	     0, /* Stack section will not be executable.  */
 	     1, /* Stack section will be modified.  */
+	     false, /* No memory tags in the object file.  */
 	     obfd);
 
   /* Make a heap segment.  */
@@ -506,6 +554,7 @@ objfile_find_memory_regions (struct target_ops *self,
 	     1, /* Heap section will be writable.  */
 	     0, /* Heap section will not be executable.  */
 	     1, /* Heap section will be modified.  */
+	     false, /* No memory tags in the object file.  */
 	     obfd);
 
   return 0;
@@ -555,6 +604,20 @@ gcore_copy_callback (bfd *obfd, asection *osec)
     }
 }
 
+/* Callback to copy contents to a particular memory tag section.  */
+
+static void
+gcore_copy_memtag_section_callback (bfd *obfd, asection *osec)
+{
+  /* We are only interested in "memtag" sections.  */
+  if (!startswith (bfd_section_name (osec), "memtag"))
+    return;
+
+  /* Fill the section with memory tag contents.  */
+  if (!gdbarch_fill_memtag_section (target_gdbarch (), osec))
+    error (_("Failed to fill memory tag section for core file."));
+}
+
 static int
 gcore_memory_sections (bfd *obfd)
 {
@@ -567,13 +630,27 @@ gcore_memory_sections (bfd *obfd)
 	return 0;			/* FIXME: error return/msg?  */
     }
 
+  /* Take care of dumping memory tags, if there are any.  */
+  if (!gdbarch_find_memory_regions_p (target_gdbarch ())
+      || gdbarch_find_memory_regions (target_gdbarch (),
+				      gcore_create_memtag_section_callback,
+				      obfd) != 0)
+    {
+      if (target_find_memory_regions (gcore_create_memtag_section_callback,
+				      obfd) != 0)
+	return 0;
+    }
+
   /* Record phdrs for section-to-segment mapping.  */
   for (asection *sect : gdb_bfd_sections (obfd))
     make_output_phdrs (obfd, sect);
 
-  /* Copy memory region contents.  */
+  /* Copy memory region and memory tag contents.  */
   for (asection *sect : gdb_bfd_sections (obfd))
-    gcore_copy_callback (obfd, sect);
+    {
+      gcore_copy_callback (obfd, sect);
+      gcore_copy_memtag_section_callback (obfd, sect);
+    }
 
   return 1;
 }
diff --git a/gdb/gdbarch-components.py b/gdb/gdbarch-components.py
index e8f20c83ff0..6fa1b7591db 100644
--- a/gdb/gdbarch-components.py
+++ b/gdb/gdbarch-components.py
@@ -1522,6 +1522,41 @@ Find core file memory regions
     invalid=True,
 )
 
+Method(
+    comment="""
+Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file
+""",
+    type="asection *",
+    name="create_memtag_section",
+    params=[("bfd *", "obfd"), ("CORE_ADDR", "address"), ("size_t", "size")],
+    predicate=True,
+    invalid=True,
+)
+
+Method(
+    comment="""
+Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data
+""",
+    type="bool",
+    name="fill_memtag_section",
+    params=[("asection *", "osec")],
+    predicate=True,
+    invalid=True,
+)
+
+Method(
+    comment="""
+Decode a memory tag SECTION and return the tags of type TYPE contained in
+the memory range [ADDRESS, ADDRESS + LENGTH).
+If no tags were found, return an empty vector.
+""",
+    type="gdb::byte_vector",
+    name="decode_memtag_section",
+    params=[("bfd_section *", "section"), ("int", "type"), ("CORE_ADDR", "address"), ("size_t", "length")],
+    predicate=True,
+    invalid=True,
+)
+
 Method(
     comment="""
 Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 882b9057b1a..1d19f51f21d 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -874,6 +874,32 @@ typedef int (gdbarch_find_memory_regions_ftype) (struct gdbarch *gdbarch, find_m
 extern int gdbarch_find_memory_regions (struct gdbarch *gdbarch, find_memory_region_ftype func, void *data);
 extern void set_gdbarch_find_memory_regions (struct gdbarch *gdbarch, gdbarch_find_memory_regions_ftype *find_memory_regions);
 
+/* Given a bfd OBFD, segment ADDRESS and SIZE, create a memory tag section to be dumped to a core file */
+
+extern bool gdbarch_create_memtag_section_p (struct gdbarch *gdbarch);
+
+typedef asection * (gdbarch_create_memtag_section_ftype) (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
+extern asection * gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size);
+extern void set_gdbarch_create_memtag_section (struct gdbarch *gdbarch, gdbarch_create_memtag_section_ftype *create_memtag_section);
+
+/* Given a memory tag section OSEC, fill OSEC's contents with the appropriate tag data */
+
+extern bool gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch);
+
+typedef bool (gdbarch_fill_memtag_section_ftype) (struct gdbarch *gdbarch, asection *osec);
+extern bool gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec);
+extern void set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch, gdbarch_fill_memtag_section_ftype *fill_memtag_section);
+
+/* Decode a memory tag SECTION and return the tags of type TYPE contained in
+   the memory range [ADDRESS, ADDRESS + LENGTH).
+   If no tags were found, return an empty vector. */
+
+extern bool gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch);
+
+typedef gdb::byte_vector (gdbarch_decode_memtag_section_ftype) (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
+extern gdb::byte_vector gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length);
+extern void set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch, gdbarch_decode_memtag_section_ftype *decode_memtag_section);
+
 /* Read offset OFFSET of TARGET_OBJECT_LIBRARIES formatted shared libraries list from
    core file into buffer READBUF with length LEN.  Return the number of bytes read
    (zero indicates failure).
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index a588bdef61a..f5dbacb14e7 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -171,6 +171,9 @@ struct gdbarch
   gdbarch_iterate_over_regset_sections_ftype *iterate_over_regset_sections;
   gdbarch_make_corefile_notes_ftype *make_corefile_notes;
   gdbarch_find_memory_regions_ftype *find_memory_regions;
+  gdbarch_create_memtag_section_ftype *create_memtag_section;
+  gdbarch_fill_memtag_section_ftype *fill_memtag_section;
+  gdbarch_decode_memtag_section_ftype *decode_memtag_section;
   gdbarch_core_xfer_shared_libraries_ftype *core_xfer_shared_libraries;
   gdbarch_core_xfer_shared_libraries_aix_ftype *core_xfer_shared_libraries_aix;
   gdbarch_core_pid_to_str_ftype *core_pid_to_str;
@@ -527,6 +530,9 @@ verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of iterate_over_regset_sections, has predicate.  */
   /* Skip verify of make_corefile_notes, has predicate.  */
   /* Skip verify of find_memory_regions, has predicate.  */
+  /* Skip verify of create_memtag_section, has predicate.  */
+  /* Skip verify of fill_memtag_section, has predicate.  */
+  /* Skip verify of decode_memtag_section, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries, has predicate.  */
   /* Skip verify of core_xfer_shared_libraries_aix, has predicate.  */
   /* Skip verify of core_pid_to_str, has predicate.  */
@@ -1096,6 +1102,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
   gdb_printf (file,
                       "gdbarch_dump: find_memory_regions = <%s>\n",
                       host_address_to_string (gdbarch->find_memory_regions));
+  gdb_printf (file,
+                      "gdbarch_dump: gdbarch_create_memtag_section_p() = %d\n",
+                      gdbarch_create_memtag_section_p (gdbarch));
+  gdb_printf (file,
+                      "gdbarch_dump: create_memtag_section = <%s>\n",
+                      host_address_to_string (gdbarch->create_memtag_section));
+  gdb_printf (file,
+                      "gdbarch_dump: gdbarch_fill_memtag_section_p() = %d\n",
+                      gdbarch_fill_memtag_section_p (gdbarch));
+  gdb_printf (file,
+                      "gdbarch_dump: fill_memtag_section = <%s>\n",
+                      host_address_to_string (gdbarch->fill_memtag_section));
+  gdb_printf (file,
+                      "gdbarch_dump: gdbarch_decode_memtag_section_p() = %d\n",
+                      gdbarch_decode_memtag_section_p (gdbarch));
+  gdb_printf (file,
+                      "gdbarch_dump: decode_memtag_section = <%s>\n",
+                      host_address_to_string (gdbarch->decode_memtag_section));
   gdb_printf (file,
                       "gdbarch_dump: gdbarch_core_xfer_shared_libraries_p() = %d\n",
                       gdbarch_core_xfer_shared_libraries_p (gdbarch));
@@ -3744,6 +3768,78 @@ set_gdbarch_find_memory_regions (struct gdbarch *gdbarch,
   gdbarch->find_memory_regions = find_memory_regions;
 }
 
+bool
+gdbarch_create_memtag_section_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->create_memtag_section != NULL;
+}
+
+asection *
+gdbarch_create_memtag_section (struct gdbarch *gdbarch, bfd *obfd, CORE_ADDR address, size_t size)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->create_memtag_section != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_create_memtag_section called\n");
+  return gdbarch->create_memtag_section (gdbarch, obfd, address, size);
+}
+
+void
+set_gdbarch_create_memtag_section (struct gdbarch *gdbarch,
+                                   gdbarch_create_memtag_section_ftype create_memtag_section)
+{
+  gdbarch->create_memtag_section = create_memtag_section;
+}
+
+bool
+gdbarch_fill_memtag_section_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->fill_memtag_section != NULL;
+}
+
+bool
+gdbarch_fill_memtag_section (struct gdbarch *gdbarch, asection *osec)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->fill_memtag_section != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_fill_memtag_section called\n");
+  return gdbarch->fill_memtag_section (gdbarch, osec);
+}
+
+void
+set_gdbarch_fill_memtag_section (struct gdbarch *gdbarch,
+                                 gdbarch_fill_memtag_section_ftype fill_memtag_section)
+{
+  gdbarch->fill_memtag_section = fill_memtag_section;
+}
+
+bool
+gdbarch_decode_memtag_section_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->decode_memtag_section != NULL;
+}
+
+gdb::byte_vector
+gdbarch_decode_memtag_section (struct gdbarch *gdbarch, bfd_section *section, int type, CORE_ADDR address, size_t length)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->decode_memtag_section != NULL);
+  if (gdbarch_debug >= 2)
+    gdb_printf (gdb_stdlog, "gdbarch_decode_memtag_section called\n");
+  return gdbarch->decode_memtag_section (gdbarch, section, type, address, length);
+}
+
+void
+set_gdbarch_decode_memtag_section (struct gdbarch *gdbarch,
+                                   gdbarch_decode_memtag_section_ftype decode_memtag_section)
+{
+  gdbarch->decode_memtag_section = decode_memtag_section;
+}
+
 bool
 gdbarch_core_xfer_shared_libraries_p (struct gdbarch *gdbarch)
 {
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 4e728a06e7e..8a83ed320cf 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -42,6 +42,7 @@
 #include "gcore.h"
 #include "gcore-elf.h"
 #include "solib-svr4.h"
+#include "memtag.h"
 
 #include <ctype.h>
 #include <unordered_map>
@@ -1320,6 +1321,7 @@ typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
 					    ULONGEST offset, ULONGEST inode,
 					    int read, int write,
 					    int exec, int modified,
+					    bool memory_tagged,
 					    const char *filename,
 					    void *data);
 
@@ -1470,10 +1472,11 @@ parse_smaps_data (const char *data,
   return smaps;
 }
 
-/* See linux-tdep.h.  */
+/* Helper that checks if an address is in a memory tag page for a live
+   process.  */
 
-bool
-linux_address_in_memtag_page (CORE_ADDR address)
+static bool
+linux_process_address_in_memtag_page (CORE_ADDR address)
 {
   if (current_inferior ()->fake_pid_p)
     return false;
@@ -1505,6 +1508,30 @@ linux_address_in_memtag_page (CORE_ADDR address)
   return false;
 }
 
+/* Helper that checks if an address is in a memory tag page for a core file
+   process.  */
+
+static bool
+linux_core_file_address_in_memtag_page (CORE_ADDR address)
+{
+  if (core_bfd == nullptr)
+    return false;
+
+  memtag_section_info info;
+  return get_next_core_memtag_section (core_bfd, nullptr, address, info);
+}
+
+/* See linux-tdep.h.  */
+
+bool
+linux_address_in_memtag_page (CORE_ADDR address)
+{
+  if (!target_has_execution ())
+    return linux_core_file_address_in_memtag_page (address);
+
+  return linux_process_address_in_memtag_page (address);
+}
+
 /* List memory regions in the inferior for a corefile.  */
 
 static int
@@ -1593,6 +1620,7 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
 		map.offset, map.inode, map.read, map.write, map.exec,
 		1, /* MODIFIED is true because we want to dump
 		      the mapping.  */
+		map.vmflags.memory_tagging != 0,
 		map.filename.c_str (), obfd);
 	}
     }
@@ -1621,12 +1649,14 @@ static int
 linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
 				 ULONGEST offset, ULONGEST inode,
 				 int read, int write, int exec, int modified,
+				 bool memory_tagged,
 				 const char *filename, void *arg)
 {
   struct linux_find_memory_regions_data *data
     = (struct linux_find_memory_regions_data *) arg;
 
-  return data->func (vaddr, size, read, write, exec, modified, data->obfd);
+  return data->func (vaddr, size, read, write, exec, modified, memory_tagged,
+		     data->obfd);
 }
 
 /* A variant of linux_find_memory_regions_full that is suitable as the
@@ -1675,6 +1705,7 @@ static int
 linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
 			      ULONGEST offset, ULONGEST inode,
 			      int read, int write, int exec, int modified,
+			      bool memory_tagged,
 			      const char *filename, void *data)
 {
   struct linux_make_mappings_data *map_data
diff --git a/gdb/memtag.c b/gdb/memtag.c
new file mode 100644
index 00000000000..af86137c49d
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,61 @@
+/* GDB generic memory tagging functions.
+
+   Copyright (C) 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 "memtag.h"
+#include "bfd.h"
+
+/* See memtag.h */
+
+bool
+get_next_core_memtag_section (bfd *abfd, asection *section,
+			      CORE_ADDR address, memtag_section_info &info)
+{
+  /* If the caller provided no SECTION to start from, search from the
+     beginning.  */
+  if (section == nullptr)
+    section = bfd_get_section_by_name (abfd, "memtag");
+
+  /* Go through all the memtag sections and figure out if ADDRESS
+     falls within one of the memory ranges that contain tags.  */
+  while (section != nullptr)
+    {
+      size_t memtag_range_size = section->rawsize;
+      size_t tags_size = bfd_section_size (section);
+
+      /* Empty memory range and empty tag dump should not happen.  */
+      gdb_assert (memtag_range_size != 0);
+      gdb_assert (tags_size != 0);
+
+      CORE_ADDR start_address = bfd_section_vma (section);
+      CORE_ADDR end_address = start_address + memtag_range_size;
+
+      /* Is the address within [start_address, end_address)?  */
+      if (address >= start_address
+	  && address < end_address)
+	{
+	  info.start_address = start_address;
+	  info.end_address = end_address;
+	  info.memtag_section = section;
+	  return true;
+	}
+      section = bfd_get_next_section_by_name (abfd, section);
+    }
+  return false;
+}
diff --git a/gdb/memtag.h b/gdb/memtag.h
new file mode 100644
index 00000000000..fe908c1e5e3
--- /dev/null
+++ b/gdb/memtag.h
@@ -0,0 +1,50 @@
+/* GDB generic memory tagging definitions.
+   Copyright (C) 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/>.  */
+
+#ifndef MEMTAG_H
+#define MEMTAG_H
+
+#include "bfd.h"
+
+struct memtag_section_info
+{
+  /* The start address of the tagged memory range.  */
+  CORE_ADDR start_address;
+  /* The final address of the tagged memory range.  */
+  CORE_ADDR end_address;
+  /* The section containing tags for the memory range
+     [start_address, end_address).  */
+  asection *memtag_section;
+};
+
+/* Helper function to walk through memory tag sections in a core file.
+
+   Return TRUE if there is a "memtag" section containing ADDRESS.  Return FALSE
+   otherwise.
+
+   If SECTION is provided, search from that section onwards. If SECTION is
+   nullptr, then start a new search.
+
+   If a "memtag" section containing ADDRESS is found, fill INFO with data
+   about such section.  Otherwise leave it unchanged.  */
+
+bool get_next_core_memtag_section (bfd *abfd, asection *section,
+				   CORE_ADDR address,
+				   memtag_section_info &info);
+
+#endif /* MEMTAG_H */
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
new file mode 100644
index 00000000000..b20ebcff424
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.c
@@ -0,0 +1,93 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2021 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Exercise AArch64's Memory Tagging Extension with tagged pointers.  */
+
+/* This test was based on the documentation for the AArch64 Memory Tagging
+   Extension from the Linux Kernel, found in the sources in
+   Documentation/arm64/memory-tagging-extension.rst.  */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+
+/* From arch/arm64/include/uapi/asm/hwcap.h */
+#define HWCAP2_MTE              (1 << 18)
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#define PROT_MTE  0x20
+
+/* From include/uapi/linux/prctl.h */
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_GET_TAGGED_ADDR_CTRL 56
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+
+void
+access_memory (unsigned char *tagged_ptr)
+{
+  tagged_ptr[0] = 'a';
+}
+
+int
+main (int argc, char **argv)
+{
+  unsigned char *tagged_ptr;
+  unsigned long page_sz = sysconf (_SC_PAGESIZE);
+  unsigned long hwcap2 = getauxval(AT_HWCAP2);
+
+  /* Bail out if MTE is not supported.  */
+  if (!(hwcap2 & HWCAP2_MTE))
+    return 1;
+
+  /* Enable the tagged address ABI, synchronous MTE tag check faults and
+     allow all non-zero tags in the randomly generated set.  */
+  if (prctl (PR_SET_TAGGED_ADDR_CTRL,
+	     PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Create a mapping that will have PROT_MTE set.  */
+  tagged_ptr = mmap (0, page_sz, PROT_READ | PROT_WRITE,
+		     MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (tagged_ptr == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Enable MTE on the above anonymous mmap.  */
+  if (mprotect (tagged_ptr, page_sz, PROT_READ | PROT_WRITE | PROT_MTE))
+    {
+      perror ("mprotect () failed");
+      return 1;
+    }
+
+  access_memory (tagged_ptr);
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
new file mode 100644
index 00000000000..8a19c4b449e
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-gcore.exp
@@ -0,0 +1,107 @@
+# Copyright (C) 2018-2021 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test generating and reading a core file with MTE memory tags.
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto_main] {
+    untested "could not run to main"
+    return -1
+}
+
+# Targets that don't support memory tagging should not execute the
+# runtime memory tagging tests.
+if {![supports_memtag]} {
+    unsupported "memory tagging unsupported"
+    return -1
+}
+
+gdb_breakpoint "access_memory"
+
+if [gdb_continue "access_memory"] {
+    return -1
+}
+
+# Set each tag granule to a different tag value, from 0x0 to 0xf.
+set atag_msg "Allocation tag\\(s\\) updated successfully\."
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%02x" $i]
+    gdb_test "memory-tag set-allocation-tag &tagged_ptr\[$index\] 1 $tag" \
+	     $atag_msg \
+	     "set memory tag of &tagged_ptr\[$index\] to $tag"
+}
+
+# Run until a crash and confirm GDB displays memory tag violation
+# information.
+gdb_test "continue" \
+    [multi_line \
+	"Program received signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag $hex" \
+	"Logical tag $hex\." \
+	"$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "display tag violation information for live process"
+
+# Generate the core file.
+set core_filename [standard_output_file "$testfile.core"]
+set core_generated [gdb_gcore_cmd "$core_filename" "generate core file"]
+
+if { !$core_generated } {
+    return -1
+}
+
+clean_restart $binfile
+
+# Load the core file and make sure we see the tag violation fault
+# information.
+gdb_test "core $core_filename" \
+    [multi_line \
+	"Core was generated by.*\." \
+	"Program terminated with signal SIGSEGV, Segmentation fault" \
+	"Memory tag violation while accessing address $hex" \
+	"Allocation tag 0xf" \
+	"Logical tag 0x0\." \
+	"#0.*$hex in access_memory \\(.*\\) at .*" \
+	".*tagged_ptr\\\[0\\\] = 'a';"] \
+	 "core file shows tag violation information"
+
+# Make sure we have the tag_ctl register.
+gdb_test "info register tag_ctl" \
+	 "tag_ctl.*$hex.*${::decimal}" \
+	 "tag_ctl is available"
+
+# Check if the tag granules have the expected values.  If they do, that
+# means the core file saved the tags properly and GDB has read them
+# correctly.
+for {set i 15} {$i >= 0} {incr i -1} {
+    set index [expr [expr 15 - $i] * 16]
+    set tag [format "%x" $i]
+    gdb_test "memory-tag print-allocation-tag &tagged_ptr\[$index\]" \
+	     "= 0x$tag" \
+	     "memory tag of &tagged_ptr\[$index\] is correct"
+}
-- 
2.25.1


^ permalink raw reply	[flat|nested] 47+ messages in thread

end of thread, other threads:[~2022-07-11 10:13 UTC | newest]

Thread overview: 47+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-18 20:20 [PATCH] [AArch64] MTE corefile support Luis Machado
2021-05-19 10:01 ` David Spickett
2021-05-19 11:11   ` Luis Machado
2021-05-19 12:13 ` Eli Zaretskii
2021-05-21 15:12 ` Alan Hayward
2021-05-21 15:30   ` Luis Machado
2021-05-21 17:20     ` John Baldwin
2021-05-24 13:41       ` Luis Machado
2021-05-24  8:07     ` Alan Hayward
2021-05-24 12:45       ` Luis Machado
2021-05-26 14:08 ` [PATCH,v2] " Luis Machado
2021-05-29  3:14   ` Simon Marchi
2021-05-31 14:12     ` Luis Machado
2021-05-31 14:49       ` Simon Marchi
2021-05-31 14:56         ` Luis Machado
2021-05-31 14:15   ` [PATCH,v3][AArch64] " Luis Machado
2021-05-31 16:44 ` [PATCH,v4][AArch64] " Luis Machado
2021-06-01 17:45 ` [PATCH,v5][AArch64] " Luis Machado
2021-06-15 14:10   ` [Ping][PATCH,v5][AArch64] " Luis Machado
2021-06-24 14:00   ` [PATCH,v5][AArch64] " Alan Hayward
2021-06-24 14:37     ` Luis Machado
2021-06-24 15:18       ` Alan Hayward
2021-07-01 13:50   ` [PING][PATCH,v5][AArch64] " Luis Machado
2021-07-11 14:22     ` Joel Brobecker
2021-07-14 13:07       ` Catalin Marinas
2021-07-29  2:26         ` Simon Marchi
2021-07-29 16:03           ` John Baldwin
2021-07-29 18:10           ` Catalin Marinas
2021-07-29 18:20             ` Simon Marchi
2021-08-01 15:44               ` Joel Brobecker
2021-08-02 12:06                 ` Luis Machado
2021-07-19 19:05   ` Luis Machado
2021-07-27 16:10   ` Luis Machado
2022-03-31 14:03 [AArch64] " Luis Machado
2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
2022-05-12 10:36   ` Luis Machado
2022-05-18 12:46   ` Luis Machado
2022-05-18 13:58     ` John Baldwin
2022-05-23  9:50       ` Luis Machado
2022-05-23  9:49     ` Luis Machado
2022-06-06  9:28   ` Luis Machado
2022-06-06  9:42     ` Kuan-Ying Lee
2022-06-06  9:47       ` Luis Machado
2022-06-06  9:54         ` Kuan-Ying Lee
2022-06-06 10:49     ` Eli Zaretskii
2022-06-22  9:04   ` Luis Machado
2022-06-27 14:51   ` Pedro Alves
2022-07-11 10:13     ` Luis Machado

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).