public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [AArch64] MTE corefile support
@ 2022-03-31 14:03 Luis Machado
  2022-04-21 15:20 ` [PATCH, v2] " Luis Machado
                   ` (3 more replies)
  0 siblings, 4 replies; 28+ messages in thread
From: Luis Machado @ 2022-03-31 14:03 UTC (permalink / raw)
  To: gdb-patches

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_ARM_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                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 161 +++++++++++++++++++
 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                          |   4 +
 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                                 |  63 ++++++++
 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, 885 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 aecab41eeb8..55f0a23fbac 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1116,6 +1116,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 e10062752d0..7c49b7cea84 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** 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.
+
 * 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..3133f6d1bf3 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.
 
       +------------+  ^
@@ -1781,6 +1783,150 @@ 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;
+
+  mte_section->has_memory_tags = 1;
+  bfd_set_section_vma (mte_section, address);
+  /* Tags are stored packed as 2 tags per byte.  */
+  bfd_set_section_size (mte_section, (size / AARCH64_MTE_GRANULE_SIZE) / 2);
+  mte_section->memory_tags_range_size = size;
+
+  /* Store program header information.  */
+  bfd_record_phdr (obfd, PT_ARM_MEMTAG_MTE, 0, 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->memory_tags_range_size;
+  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 %ld bytes of corefile memory "
+		 "tag content (%s)."),
+	       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 +2010,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 a23dc81c5af..612c79f9e27 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 3fddbe29a6d..629f6c7ab1b 100644
--- a/gdb/defs.h
+++ b/gdb/defs.h
@@ -347,7 +347,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 729f9d79a93..0aeacc5fd27 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25413,6 +25413,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/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 c820ddae764..5feb04d0c09 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 7a8721328ab..1282746d88b 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 ff404f47727..a50a8b51cd8 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..75cd01d202f
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,63 @@
+/* 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)
+    {
+      gdb_assert (section->has_memory_tags);
+
+      size_t memtag_range_size = section->memory_tags_range_size;
+      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] 28+ messages in thread

* [PATCH, v2] [AArch64] MTE corefile support
  2022-03-31 14:03 [AArch64] MTE corefile support Luis Machado
@ 2022-04-21 15:20 ` Luis Machado
  2022-04-21 15:52   ` Eli Zaretskii
  2022-04-22 13:27 ` [PATCH 1/2] " Luis Machado
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 28+ messages in thread
From: Luis Machado @ 2022-04-21 15:20 UTC (permalink / raw)
  To: gdb-patches

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_ARM_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                                     |   4 +
 gdb/aarch64-linux-tdep.c                     | 166 +++++++++++++++++++
 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                          |   4 +
 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, 888 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 c8e140b5544..bc3aac587be 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1118,6 +1118,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 760cb2b7abc..885b3929d84 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,10 @@
 
 *** 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.
+
 * 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..ebde9c3f167 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.
 
       +------------+  ^
@@ -1781,6 +1783,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_ARM_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 %ld bytes of corefile memory "
+		 "tag content (%s)."),
+	       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 +2015,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 c1e9b09e833..780e03c73b3 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25545,6 +25545,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/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] 28+ messages in thread

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-21 15:20 ` [PATCH, v2] " Luis Machado
@ 2022-04-21 15:52   ` Eli Zaretskii
  2022-04-22  8:12     ` Luis Machado
  0 siblings, 1 reply; 28+ messages in thread
From: Eli Zaretskii @ 2022-04-21 15:52 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches

> Date: Thu, 21 Apr 2022 16:20:40 +0100
> NoDisclaimer: true
> From: Luis Machado via Gdb-patches <gdb-patches@sourceware.org>
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 760cb2b7abc..885b3929d84 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -3,6 +3,10 @@
>  
>  *** 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.
> +

I think this should say something about the significance of this
feature.  Otherwise it is completely unclear why would GDB want to
support that.

Same comment for the addition to the manual.

Thanks.

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

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-21 15:52   ` Eli Zaretskii
@ 2022-04-22  8:12     ` Luis Machado
  2022-04-22  8:30       ` Eli Zaretskii
  0 siblings, 1 reply; 28+ messages in thread
From: Luis Machado @ 2022-04-22  8:12 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

Hi Eli,

On 4/21/22 16:52, Eli Zaretskii wrote:
>> Date: Thu, 21 Apr 2022 16:20:40 +0100
>> NoDisclaimer: true
>> From: Luis Machado via Gdb-patches <gdb-patches@sourceware.org>
>>
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index 760cb2b7abc..885b3929d84 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -3,6 +3,10 @@
>>   
>>   *** 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.
>> +
> 
> I think this should say something about the significance of this
> feature.  Otherwise it is completely unclear why would GDB want to
> support that.
> 
> Same comment for the addition to the manual.
> 
> Thanks.

It is a bit short. How about the following additional paragraph?

"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.

The memory tag data will be used so developers can display the memory 
tags from a particular memory region, and will also be used to show a 
detailed message about a crash that happened due to a memory tag violation."

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

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-22  8:12     ` Luis Machado
@ 2022-04-22  8:30       ` Eli Zaretskii
  2022-04-22  8:37         ` Luis Machado
  0 siblings, 1 reply; 28+ messages in thread
From: Eli Zaretskii @ 2022-04-22  8:30 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches

> Date: Fri, 22 Apr 2022 09:12:44 +0100
> Cc: gdb-patches@sourceware.org
> From: Luis Machado <luis.machado@arm.com>
> 
> >>   *** 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.
> >> +
> > 
> > I think this should say something about the significance of this
> > feature.  Otherwise it is completely unclear why would GDB want to
> > support that.
> > 
> > Same comment for the addition to the manual.
> > 
> > Thanks.
> 
> It is a bit short. How about the following additional paragraph?
> 
> "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.
> 
> The memory tag data will be used so developers can display the memory 
> tags from a particular memory region, and will also be used to show a 
> detailed message about a crash that happened due to a memory tag violation."

This is fine, but can what's described in the last paragraph be done
by GDB commands, or does it require external tools?  In the former
case, we should mention those GDB commands; in the latter case I'd
settle for mentioning the fact that external tools are required.

Thanks.

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

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-22  8:30       ` Eli Zaretskii
@ 2022-04-22  8:37         ` Luis Machado
  2022-04-22  8:43           ` Eli Zaretskii
  0 siblings, 1 reply; 28+ messages in thread
From: Luis Machado @ 2022-04-22  8:37 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 4/22/22 09:30, Eli Zaretskii wrote:
>> Date: Fri, 22 Apr 2022 09:12:44 +0100
>> Cc: gdb-patches@sourceware.org
>> From: Luis Machado <luis.machado@arm.com>
>>
>>>>    *** 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.
>>>> +
>>>
>>> I think this should say something about the significance of this
>>> feature.  Otherwise it is completely unclear why would GDB want to
>>> support that.
>>>
>>> Same comment for the addition to the manual.
>>>
>>> Thanks.
>>
>> It is a bit short. How about the following additional paragraph?
>>
>> "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.
>>
>> The memory tag data will be used so developers can display the memory
>> tags from a particular memory region, and will also be used to show a
>> detailed message about a crash that happened due to a memory tag violation."
> 
> This is fine, but can what's described in the last paragraph be done
> by GDB commands, or does it require external tools?  In the former
> case, we should mention those GDB commands; in the latter case I'd
> settle for mentioning the fact that external tools are required.

No external tools are required. GDB already supports displaying memory 
tags automatically if it detects the process is using those. We can 
explicitly mention the 'm' modifier for the x command and the memory-tag 
sub-commands.

Would this be more appropriate for the manual entry? Or do you think it 
makes sense to mention it in the NEWS as well?

> 
> Thanks.


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

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-22  8:37         ` Luis Machado
@ 2022-04-22  8:43           ` Eli Zaretskii
  2022-04-22  8:44             ` Luis Machado
  0 siblings, 1 reply; 28+ messages in thread
From: Eli Zaretskii @ 2022-04-22  8:43 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches

> Authentication-Results-Original: dkim=none (message not signed)
>  header.d=none;dmarc=none action=none header.from=arm.com;
> Date: Fri, 22 Apr 2022 09:37:47 +0100
> Cc: gdb-patches@sourceware.org
> From: Luis Machado <luis.machado@arm.com>
> NoDisclaimer: true
> Original-Authentication-Results: dkim=none (message not signed)
>  header.d=none;dmarc=none action=none header.from=arm.com;
> 
> On 4/22/22 09:30, Eli Zaretskii wrote:
> >> Date: Fri, 22 Apr 2022 09:12:44 +0100
> >> Cc: gdb-patches@sourceware.org
> >> From: Luis Machado <luis.machado@arm.com>
> >>
> >>>>    *** 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.
> >>>> +
> >>>
> >>> I think this should say something about the significance of this
> >>> feature.  Otherwise it is completely unclear why would GDB want to
> >>> support that.
> >>>
> >>> Same comment for the addition to the manual.
> >>>
> >>> Thanks.
> >>
> >> It is a bit short. How about the following additional paragraph?
> >>
> >> "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.
> >>
> >> The memory tag data will be used so developers can display the memory
> >> tags from a particular memory region, and will also be used to show a
> >> detailed message about a crash that happened due to a memory tag violation."
> > 
> > This is fine, but can what's described in the last paragraph be done
> > by GDB commands, or does it require external tools?  In the former
> > case, we should mention those GDB commands; in the latter case I'd
> > settle for mentioning the fact that external tools are required.
> 
> No external tools are required. GDB already supports displaying memory 
> tags automatically if it detects the process is using those. We can 
> explicitly mention the 'm' modifier for the x command and the memory-tag 
> sub-commands.
> 
> Would this be more appropriate for the manual entry? Or do you think it 
> makes sense to mention it in the NEWS as well?

It's okay to only mention that in the manual.  NEWS should just say
that GDB can display those tags automatically.

Thanks.

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

* Re: [PATCH, v2] [AArch64] MTE corefile support
  2022-04-22  8:43           ` Eli Zaretskii
@ 2022-04-22  8:44             ` Luis Machado
  0 siblings, 0 replies; 28+ messages in thread
From: Luis Machado @ 2022-04-22  8:44 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

On 4/22/22 09:43, Eli Zaretskii wrote:
>> Authentication-Results-Original: dkim=none (message not signed)
>>   header.d=none;dmarc=none action=none header.from=arm.com;
>> Date: Fri, 22 Apr 2022 09:37:47 +0100
>> Cc: gdb-patches@sourceware.org
>> From: Luis Machado <luis.machado@arm.com>
>> NoDisclaimer: true
>> Original-Authentication-Results: dkim=none (message not signed)
>>   header.d=none;dmarc=none action=none header.from=arm.com;
>>
>> On 4/22/22 09:30, Eli Zaretskii wrote:
>>>> Date: Fri, 22 Apr 2022 09:12:44 +0100
>>>> Cc: gdb-patches@sourceware.org
>>>> From: Luis Machado <luis.machado@arm.com>
>>>>
>>>>>>     *** 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.
>>>>>> +
>>>>>
>>>>> I think this should say something about the significance of this
>>>>> feature.  Otherwise it is completely unclear why would GDB want to
>>>>> support that.
>>>>>
>>>>> Same comment for the addition to the manual.
>>>>>
>>>>> Thanks.
>>>>
>>>> It is a bit short. How about the following additional paragraph?
>>>>
>>>> "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.
>>>>
>>>> The memory tag data will be used so developers can display the memory
>>>> tags from a particular memory region, and will also be used to show a
>>>> detailed message about a crash that happened due to a memory tag violation."
>>>
>>> This is fine, but can what's described in the last paragraph be done
>>> by GDB commands, or does it require external tools?  In the former
>>> case, we should mention those GDB commands; in the latter case I'd
>>> settle for mentioning the fact that external tools are required.
>>
>> No external tools are required. GDB already supports displaying memory
>> tags automatically if it detects the process is using those. We can
>> explicitly mention the 'm' modifier for the x command and the memory-tag
>> sub-commands.
>>
>> Would this be more appropriate for the manual entry? Or do you think it
>> makes sense to mention it in the NEWS as well?
> 
> It's okay to only mention that in the manual.  NEWS should just say
> that GDB can display those tags automatically.
> 
> Thanks.

Thanks. I'll update these entries and will submit a v3.

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

* [PATCH 1/2] [AArch64] MTE corefile support
  2022-03-31 14:03 [AArch64] MTE corefile support Luis Machado
  2022-04-21 15:20 ` [PATCH, v2] " Luis Machado
@ 2022-04-22 13:27 ` Luis Machado
  2022-04-22 13:33   ` Eli Zaretskii
  2022-04-22 13:30 ` [PATCH, v3] " Luis Machado
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
  3 siblings, 1 reply; 28+ messages in thread
From: Luis Machado @ 2022-04-22 13:27 UTC (permalink / raw)
  To: gdb-patches

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_ARM_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                     | 166 +++++++++++++++++++
 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                          |  18 ++
 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, 908 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 ec0e55dd803..a11054d6a5c 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1119,6 +1119,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 760cb2b7abc..d6818d54972 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..ebde9c3f167 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.
 
       +------------+  ^
@@ -1781,6 +1783,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_ARM_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 %ld bytes of corefile memory "
+		 "tag content (%s)."),
+	       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 +2015,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 c1e9b09e833..d9021f1c56d 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25545,6 +25545,24 @@ 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{GDB} 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{GDB} 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.
+
 @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 2022 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-2022 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] 28+ messages in thread

* [PATCH, v3] [AArch64] MTE corefile support
  2022-03-31 14:03 [AArch64] MTE corefile support Luis Machado
  2022-04-21 15:20 ` [PATCH, v2] " Luis Machado
  2022-04-22 13:27 ` [PATCH 1/2] " Luis Machado
@ 2022-04-22 13:30 ` Luis Machado
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
  3 siblings, 0 replies; 28+ messages in thread
From: Luis Machado @ 2022-04-22 13:30 UTC (permalink / raw)
  To: gdb-patches

Apologies for double posting, but the subject line for the previous submission
was off and misleading.

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_ARM_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                     | 166 +++++++++++++++++++
 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                          |  18 ++
 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, 908 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 ec0e55dd803..a11054d6a5c 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1119,6 +1119,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 760cb2b7abc..d6818d54972 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..ebde9c3f167 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.
 
       +------------+  ^
@@ -1781,6 +1783,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_ARM_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 %ld bytes of corefile memory "
+		 "tag content (%s)."),
+	       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 +2015,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 c1e9b09e833..d9021f1c56d 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25545,6 +25545,24 @@ 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{GDB} 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{GDB} 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.
+
 @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 2022 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-2022 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] 28+ messages in thread

* Re: [PATCH 1/2] [AArch64] MTE corefile support
  2022-04-22 13:27 ` [PATCH 1/2] " Luis Machado
@ 2022-04-22 13:33   ` Eli Zaretskii
  0 siblings, 0 replies; 28+ messages in thread
From: Eli Zaretskii @ 2022-04-22 13:33 UTC (permalink / raw)
  To: Luis Machado; +Cc: gdb-patches

> Date: Fri, 22 Apr 2022 14:27:15 +0100
> From: Luis Machado via Gdb-patches <gdb-patches@sourceware.org>
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 760cb2b7abc..d6818d54972 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.
> +

This part is OK.

> diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
> index c1e9b09e833..d9021f1c56d 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -25545,6 +25545,24 @@ 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{GDB} 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.

Please add here cross-references to where the respective commands are
described.

The documentation parts are okay with that nit fixed.

Thanks.

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

* [PATCH, v4] [AArch64] MTE corefile support
  2022-03-31 14:03 [AArch64] MTE corefile support Luis Machado
                   ` (2 preceding siblings ...)
  2022-04-22 13:30 ` [PATCH, v3] " Luis Machado
@ 2022-05-03 21:56 ` Luis Machado
  2022-05-12 10:36   ` Luis Machado
                     ` (5 more replies)
  3 siblings, 6 replies; 28+ 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] 28+ 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
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 28+ 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] 28+ 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
                     ` (3 subsequent siblings)
  5 siblings, 2 replies; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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
                     ` (2 subsequent siblings)
  5 siblings, 2 replies; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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; 28+ 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] 28+ 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
  2022-07-11 10:57   ` [PATCH] [AArch64,v5] " Luis Machado
  5 siblings, 0 replies; 28+ 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] 28+ 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
  2022-07-11 10:57   ` [PATCH] [AArch64,v5] " Luis Machado
  5 siblings, 1 reply; 28+ 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] 28+ 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; 28+ 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] 28+ messages in thread

* [PATCH] [AArch64,v5] MTE corefile support
  2022-05-03 21:56 ` [PATCH, v4] " Luis Machado
                     ` (4 preceding siblings ...)
  2022-06-27 14:51   ` Pedro Alves
@ 2022-07-11 10:57   ` Luis Machado
  2022-07-18 13:54     ` Pedro Alves
  5 siblings, 1 reply; 28+ messages in thread
From: Luis Machado @ 2022-07-11 10:57 UTC (permalink / raw)
  To: gdb-patches; +Cc: pedro

v5:

- Addressed review comments.
- Turned assertion into warning.
- Updated testcase to also test native core file reading.

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                    | 171 ++++++++++++++++++++
 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                                |  68 ++++++++
 gdb/memtag.h                                |  50 ++++++
 gdb/testsuite/gdb.arch/aarch64-mte-core.c   | 152 +++++++++++++++++
 gdb/testsuite/gdb.arch/aarch64-mte-core.exp | 165 +++++++++++++++++++
 17 files changed, 1038 insertions(+), 8 deletions(-)
 create mode 100644 gdb/memtag.c
 create mode 100644 gdb/memtag.h
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-core.c
 create mode 100644 gdb/testsuite/gdb.arch/aarch64-mte-core.exp

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 911daa2607b..57c29a78b7a 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1122,6 +1122,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 1178a37017e..c3449077cb8 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.
+
 * "info breakpoints" now displays enabled breakpoint locations of
   disabled breakpoints as in the "y-" state.  For example:
 
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 453692df2f4..077ee08ee83 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.
 
       +------------+  ^
@@ -1803,6 +1806,159 @@ 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.
+
+     Make sure the section's flags has SEC_HAS_CONTENTS, otherwise BFD will
+     refuse to write data to this section.  */
+  asection *mte_section
+    = bfd_make_section_anyway_with_flags (obfd, "memtag", SEC_HAS_CONTENTS);
+
+  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;
+
+  /* Fetch the number of tags we need to save.  */
+  size_t tags_count
+    = aarch64_mte_get_tag_granules (address, size, AARCH64_MTE_GRANULE_SIZE);
+  /* Tags are stored packed as 2 tags per byte.  */
+  bfd_set_section_size (mte_section, (tags_count + 1) >> 1);
+  /* 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)
 {
@@ -1886,6 +2042,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 fed91bbf01f..0771ef728af 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.  */
+extern 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.  */
+extern void aarch64_mte_unpack_tags (gdb::byte_vector &tags, bool skip_first);
+
 #endif /* ARCH_AARCH64_MTE_LINUX_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 8c33fb7ebb2..230cc3d913d 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 7a4e337d15b..0f494bd9d73 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -25751,6 +25751,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 fc10e8600ba..201dba8e1e1 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 ddcb4c55615..0504962e50d 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 68ef0480219..5d14aec1455 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..ca645694bb8
--- /dev/null
+++ b/gdb/memtag.c
@@ -0,0 +1,68 @@
+/* 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 or empty tag dump should not happen.  Warn about
+	 it but keep going through the sections.  */
+      if (memtag_range_size == 0 || tags_size == 0)
+	{
+	  warning (_("Found memtag section with empty memory "
+		     "range or empty tag dump"));
+	  continue;
+	}
+      else
+	{
+	  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-core.c b/gdb/testsuite/gdb.arch/aarch64-mte-core.c
new file mode 100644
index 00000000000..4e9a6e3010b
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-core.c
@@ -0,0 +1,152 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2022 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 corefile support.  We allocate
+   multiple memory mappings with PROT_MTE and assign tag values for all the
+   existing MTE granules.  */
+
+/* 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 */
+#ifndef HWCAP2_MTE
+#define HWCAP2_MTE              (1 << 18)
+#endif
+
+/* From arch/arm64/include/uapi/asm/mman.h */
+#ifndef PROT_MTE
+#define PROT_MTE  0x20
+#endif
+
+#ifndef PR_SET_TAGGED_ADDR_CTRL
+#define PR_SET_TAGGED_ADDR_CTRL 55
+#define PR_TAGGED_ADDR_ENABLE	(1UL << 0)
+#endif
+
+/* From include/uapi/linux/prctl.h */
+#ifndef PR_MTE_TCF_SHIFT
+#define PR_MTE_TCF_SHIFT	1
+#define PR_MTE_TCF_SYNC		(1UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TCF_ASYNC	(2UL << PR_MTE_TCF_SHIFT)
+#define PR_MTE_TAG_SHIFT	3
+#define PR_MTE_TAG_MASK		(0xffffUL << PR_MTE_TAG_SHIFT)
+#endif
+
+#ifdef ASYNC
+#define TCF_MODE PR_MTE_TCF_ASYNC
+#else
+#define TCF_MODE PR_MTE_TCF_SYNC
+#endif
+
+#define NMAPS 5
+
+/* We store the pointers and sizes of the memory maps we requested.  Each
+   of them has a different size.  */
+unsigned char *mmap_pointers[NMAPS];
+
+/* Set the allocation tag on the destination address.  */
+#define set_tag(tagged_addr) do {				  \
+  asm volatile("stg %0, [%0]" : : "r" (tagged_addr) : "memory");  \
+} while (0)
+
+
+uintptr_t
+set_logical_tag (uintptr_t ptr, unsigned char tag)
+{
+  ptr &= ~0xFF00000000000000ULL;
+  ptr |= ((uintptr_t) tag << 56);
+  return ptr;
+}
+
+void
+fill_map_with_tags (unsigned char *ptr, size_t size, unsigned char *tag)
+{
+  for (size_t start = 0; start < size; start += 16)
+    {
+      set_tag (set_logical_tag (((uintptr_t)ptr + start) & ~(0xFULL), *tag));
+      *tag = (*tag + 1) % 16;
+    }
+}
+
+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 | TCF_MODE
+	     | (0xfffe << PR_MTE_TAG_SHIFT),
+	     0, 0, 0))
+    {
+      perror ("prctl () failed");
+      return 1;
+    }
+
+  /* Map a big area of NMAPS * 2 pages.  */
+  unsigned char *big_map = mmap (0, NMAPS * 2 * page_sz, PROT_NONE,
+				 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+  if (big_map == MAP_FAILED)
+    {
+      perror ("mmap () failed");
+      return 1;
+    }
+
+  /* Start with a tag of 0x1 so we can crash later.  */
+  unsigned char tag = 1;
+
+  /* From that big area of NMAPS * 2 pages, go through each page and protect
+     alternating pages.  This should prevent the kernel from merging different
+     mmap's and force the creation of multiple individual MTE-protected entries
+     in /proc/<pid>/smaps.  */
+  for (int i = 0; i < NMAPS; i++)
+    {
+      mmap_pointers[i] = big_map + (i * 2 * page_sz);
+
+      /* Enable MTE on alternating pages.  */
+      if (mprotect (mmap_pointers[i], page_sz,
+		    PROT_READ | PROT_WRITE | PROT_MTE))
+	{
+	  perror ("mprotect () failed");
+	  return 1;
+	}
+
+      fill_map_with_tags (mmap_pointers[i], page_sz, &tag);
+    }
+
+  /* The following line causes a crash on purpose.  */
+  *mmap_pointers[0] = 0x4;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.arch/aarch64-mte-core.exp b/gdb/testsuite/gdb.arch/aarch64-mte-core.exp
new file mode 100644
index 00000000000..304de920080
--- /dev/null
+++ b/gdb/testsuite/gdb.arch/aarch64-mte-core.exp
@@ -0,0 +1,165 @@
+# Copyright (C) 2018-2022 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.
+
+proc test_mte_core_file { core_filename mode } {
+    # Load the core file and make sure we see the tag violation fault
+    # information.
+    if {$mode == "sync"} {
+	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 ${::hex}" \
+		"Logical tag ${::hex}\." \
+		"#0.*${::hex} in main \\(.*\\) at .*" \
+		".*mmap_pointers\\\[0\\\] = 0x4;"] \
+		"core file shows $mode memory tag violation"
+    } else {
+	gdb_test "core $core_filename" \
+	    [multi_line \
+		"Core was generated by.*\." \
+		"Program terminated with signal SIGSEGV, Segmentation fault" \
+		"Memory tag violation" \
+		"Fault address unavailable\." \
+		"#0  ${::hex} in .* from .*"] \
+		"core file shows $mode memory tag violation"
+    }
+
+    # Make sure we have the tag_ctl register.
+    gdb_test "info register tag_ctl" \
+	"tag_ctl.*${::hex}.*${::decimal}" \
+	"tag_ctl is available"
+
+    # In ASYNC mode, there is nothing left to test, as the program stops at
+    # a place where further source code inspection is not possible.
+    if {$mode == "async"} {
+	return
+    }
+
+    # First, figure out the page size.
+    set page_size [get_valueof "" "page_sz" "0" \
+			       "fetch value of page size"]
+
+    # Get the number of maps for the test
+    set nmaps [get_valueof "" "NMAPS" "0" \
+			   "fetch number of maps"]
+    set tag 1
+
+    # Iterate over all of the MTE-protected memory mappings and make sure
+    # GDB retrieves the correct allocation tags for each one.  If the tag
+    # has the expected value, that means the core file was generated correctly
+    # and that GDB read the contents correctly.
+    for {set i 0} {$i < $nmaps} {incr i} {
+	for {set offset 0} {$offset < $page_size} {set offset [expr $offset + 16]} {
+	    set hex_tag [format "%x" $tag]
+	    gdb_test "memory-tag print-allocation-tag mmap_pointers\[$i\] + $offset" \
+		"= 0x$hex_tag" \
+		"mmap_ponters\[$i\] + $offset contains expected tag"
+	    # Update the expected tag.  The test writes tags in sequential
+	    # order.
+	    set tag [expr ($tag + 1) % 16]
+	}
+    }
+}
+
+if {![is_aarch64_target]} {
+    verbose "Skipping ${gdb_test_file_name}."
+    return
+}
+
+set compile_flags {"debug" "macros" "additional_flags=-march=armv8.5-a+memtag"}
+
+foreach_with_prefix mode {"sync" "async"} {
+
+    if {$mode == "async"} {
+	lappend compile_flags "additional_flags=-DASYNC"
+    }
+
+    standard_testfile
+    set executable "${testfile}-${mode}"
+    if {[prepare_for_testing "failed to prepare" ${executable} ${srcfile} ${compile_flags}]} {
+	return -1
+    }
+    set binfile [standard_output_file ${executable}]
+
+    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
+    }
+
+    # Run until a crash and confirm GDB displays memory tag violation
+    # information.
+    if {$mode == "sync"} {
+	gdb_test "continue" \
+	    [multi_line \
+		"Program received signal SIGSEGV, Segmentation fault" \
+		"Memory tag violation while accessing address ${::hex}" \
+		"Allocation tag 0x1" \
+		"Logical tag 0x0\." \
+		"${::hex} in main \\(.*\\) at .*" \
+		".*mmap_pointers\\\[0\\\] = 0x4;"] \
+		"run to memory $mode tag violation"
+    } else {
+	gdb_test "continue" \
+	    [multi_line \
+		"Program received signal SIGSEGV, Segmentation fault" \
+		"Memory tag violation" \
+		"Fault address unavailable\." \
+		"${::hex} in .* from .*"] \
+		"run to memory $mode tag violation"
+    }
+
+    # Generate the gcore core file.
+    set gcore_filename [standard_output_file "${executable}.gcore"]
+    set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"]
+
+    # Generate a native core file.
+    set core_filename [core_find ${binfile}]
+    set core_generated [expr {$core_filename != ""}]
+
+    # At this point we have a couple core files, the gcore one generated by GDB
+    # and the native one generated by the Linux Kernel.  Make sure GDB can read
+    # both correctly.
+
+    if {$gcore_generated} {
+	clean_restart ${binfile}
+	with_test_prefix "gcore corefile" {
+	    test_mte_core_file $gcore_filename $mode
+	}
+    } else {
+	fail "gcore corefile not generated"
+    }
+
+    if {$core_generated} {
+	clean_restart ${binfile}
+	with_test_prefix "native corefile" {
+	    test_mte_core_file $core_filename $mode
+	}
+    } else {
+	untested "native corefile not generated"
+    }
+}
-- 
2.25.1


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

* Re: [PATCH] [AArch64,v5] MTE corefile support
  2022-07-11 10:57   ` [PATCH] [AArch64,v5] " Luis Machado
@ 2022-07-18 13:54     ` Pedro Alves
  2022-07-19 14:25       ` Luis Machado
  0 siblings, 1 reply; 28+ messages in thread
From: Pedro Alves @ 2022-07-18 13:54 UTC (permalink / raw)
  To: Luis Machado, gdb-patches

Hi!

This version looks good to me.  Just one nit in the testcase.  No need to repost for this.

On 2022-07-11 11:57 a.m., Luis Machado wrote:
> +foreach_with_prefix mode {"sync" "async"} {
> +
> +    if {$mode == "async"} {
> +	lappend compile_flags "additional_flags=-DASYNC"
> +    }
> +
> +    standard_testfile
> +    set executable "${testfile}-${mode}"
> +    if {[prepare_for_testing "failed to prepare" ${executable} ${srcfile} ${compile_flags}]} {
> +	return -1

If we hit this return while testing "sync", then "async" won't be tested either.  Similarly
for the other returns below.  To avoid scenarios like this (and in the future if the code evolves
to have more early returns), it's IMO better/safer to move the body of the foreach_with_prefix
to a procedure, like:

 proc test {mode} {
     global srcfile testfile
 
     if {$mode == "async"} {
 	lappend compile_flags "additional_flags=-DASYNC"
     }
 
     standard_testfile
     set executable "${testfile}-${mode}"
     if {[prepare_for_testing "failed to prepare" ${executable} ${srcfile} ${compile_flags}]} {
 	return -1
 
     ...
 }

 foreach_with_prefix mode {"sync" "async"} {
    test $mode
 }

Pedro Alves

> +    }
> +    set binfile [standard_output_file ${executable}]
> +
> +    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
> +    }


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

* Re: [PATCH] [AArch64,v5] MTE corefile support
  2022-07-18 13:54     ` Pedro Alves
@ 2022-07-19 14:25       ` Luis Machado
  0 siblings, 0 replies; 28+ messages in thread
From: Luis Machado @ 2022-07-19 14:25 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Hi,

On 7/18/22 14:54, Pedro Alves wrote:
> Hi!
> 
> This version looks good to me.  Just one nit in the testcase.  No need to repost for this.
> 
> On 2022-07-11 11:57 a.m., Luis Machado wrote:
>> +foreach_with_prefix mode {"sync" "async"} {
>> +
>> +    if {$mode == "async"} {
>> +	lappend compile_flags "additional_flags=-DASYNC"
>> +    }
>> +
>> +    standard_testfile
>> +    set executable "${testfile}-${mode}"
>> +    if {[prepare_for_testing "failed to prepare" ${executable} ${srcfile} ${compile_flags}]} {
>> +	return -1
> 
> If we hit this return while testing "sync", then "async" won't be tested either.  Similarly
> for the other returns below.  To avoid scenarios like this (and in the future if the code evolves
> to have more early returns), it's IMO better/safer to move the body of the foreach_with_prefix
> to a procedure, like:
> 
>   proc test {mode} {
>       global srcfile testfile
>   
>       if {$mode == "async"} {
>   	lappend compile_flags "additional_flags=-DASYNC"
>       }
>   
>       standard_testfile
>       set executable "${testfile}-${mode}"
>       if {[prepare_for_testing "failed to prepare" ${executable} ${srcfile} ${compile_flags}]} {
>   	return -1
>   
>       ...
>   }
> 
>   foreach_with_prefix mode {"sync" "async"} {
>      test $mode
>   }

Thanks. That made it more organized. I've made the changes and pushed both the GDB and the binutils
changes.

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

end of thread, other threads:[~2022-07-19 14:26 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-31 14:03 [AArch64] MTE corefile support Luis Machado
2022-04-21 15:20 ` [PATCH, v2] " Luis Machado
2022-04-21 15:52   ` Eli Zaretskii
2022-04-22  8:12     ` Luis Machado
2022-04-22  8:30       ` Eli Zaretskii
2022-04-22  8:37         ` Luis Machado
2022-04-22  8:43           ` Eli Zaretskii
2022-04-22  8:44             ` Luis Machado
2022-04-22 13:27 ` [PATCH 1/2] " Luis Machado
2022-04-22 13:33   ` Eli Zaretskii
2022-04-22 13:30 ` [PATCH, v3] " 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
2022-07-11 10:57   ` [PATCH] [AArch64,v5] " Luis Machado
2022-07-18 13:54     ` Pedro Alves
2022-07-19 14:25       ` 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).