[PATCH] [AArch64,v5] MTE corefile support

Luis Machado luis.machado@arm.com
Mon Jul 11 10:57:52 GMT 2022


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



More information about the Gdb-patches mailing list