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

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

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

Dumping memory tags
-

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

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

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

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

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

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

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

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

Reading memory tags
-

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

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

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

gdb/ChangeLog:

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

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

gdb/doc/ChangeLog:

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

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

gdb/testsuite/ChangeLog:

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

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

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


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

end of thread, other threads:[~2021-08-02 12:06 UTC | newest]

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

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