[v7,16/18,gdb/aarch64] sme: Core file support for Linux

Message ID 20230918212651.660141-17-luis.machado@arm.com
State New
Headers
Series SME support for AArch64 gdb/gdbserver on Linux |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Testing passed

Commit Message

Luis Machado Sept. 18, 2023, 9:26 p.m. UTC
  Updates in v5:

- Fixed spurious 4-byte gap between FPCR and the two reserved 4-byte
  fields when collecting an inactive sve regset.

--

This patch enables dumping SME state via gdb's gcore command and also
enables gdb to read SME state from a core file generated by the Linux
Kernel.

Regression-tested on aarch64-linux Ubuntu 22.04/20.04.

Reviewed-by: Thiago Jung Bauermann <thiago.bauermann@linaro.org>
---
 gdb/aarch64-linux-tdep.c          | 542 ++++++++++++++++++++++++++++--
 gdb/arch/aarch64-scalable-linux.c |  34 ++
 gdb/arch/aarch64-scalable-linux.h |  15 +
 3 files changed, 558 insertions(+), 33 deletions(-)
  

Patch

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 21ac7ebdc56..21efa42b2e6 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -57,6 +57,10 @@ 
 
 #include "elf/common.h"
 #include "elf/aarch64.h"
+#include "arch/aarch64-insn.h"
+
+/* For std::pow */
+#include <cmath>
 
 /* Signal frame handling.
 
@@ -741,50 +745,55 @@  const struct regset aarch64_linux_fpregset =
 
 #define SVE_HEADER_FLAG_SVE		1
 
-/* Get VQ value from SVE section in the core dump.  */
+/* Get the vector quotient (VQ) or streaming vector quotient (SVQ) value
+   from the section named SECTION_NAME.
+
+   Return non-zero if successful and 0 otherwise.  */
 
 static uint64_t
-aarch64_linux_core_read_vq (struct gdbarch *gdbarch, bfd *abfd)
+aarch64_linux_core_read_vq (struct gdbarch *gdbarch, bfd *abfd,
+			    const char *section_name)
 {
-  gdb_byte header[SVE_HEADER_SIZE];
-  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
-  asection *sve_section = bfd_get_section_by_name (abfd, ".reg-aarch-sve");
+  gdb_assert (section_name != nullptr);
 
-  if (sve_section == nullptr)
+  asection *section = bfd_get_section_by_name (abfd, section_name);
+
+  if (section == nullptr)
     {
       /* No SVE state.  */
       return 0;
     }
 
-  size_t size = bfd_section_size (sve_section);
+  size_t size = bfd_section_size (section);
 
   /* Check extended state size.  */
   if (size < SVE_HEADER_SIZE)
     {
-      warning (_("'.reg-aarch-sve' section in core file too small."));
+      warning (_("'%s' core file section is too small. "
+		 "Expected %s bytes, got %s bytes"), section_name,
+		 pulongest (SVE_HEADER_SIZE), pulongest (size));
       return 0;
     }
 
-  if (!bfd_get_section_contents (abfd, sve_section, header, 0, SVE_HEADER_SIZE))
+  gdb_byte header[SVE_HEADER_SIZE];
+
+  if (!bfd_get_section_contents (abfd, section, header, 0, SVE_HEADER_SIZE))
     {
       warning (_("Couldn't read sve header from "
-		 "'.reg-aarch-sve' section in core file."));
+		 "'%s' core file section."), section_name);
       return 0;
     }
 
-  uint64_t vl = extract_unsigned_integer (header + SVE_HEADER_VL_OFFSET,
-					  SVE_HEADER_VL_LENGTH, byte_order);
-  uint64_t vq = sve_vq_from_vl (vl);
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  uint64_t vq
+    = sve_vq_from_vl (extract_unsigned_integer (header + SVE_HEADER_VL_OFFSET,
+						SVE_HEADER_VL_LENGTH,
+						byte_order));
 
-  if (vq > AARCH64_MAX_SVE_VQ)
-    {
-      warning (_("SVE Vector length in core file not supported by this version"
-		 " of GDB.  (VQ=%s)"), pulongest (vq));
-      return 0;
-    }
-  else if (vq == 0)
+  if (vq > AARCH64_MAX_SVE_VQ || vq == 0)
     {
-      warning (_("SVE Vector length in core file is invalid. (VQ=%s"),
+      warning (_("SVE/SSVE vector length in core file is invalid."
+		 " (max vq=%d) (detected vq=%s)"), AARCH64_MAX_SVE_VQ,
 	       pulongest (vq));
       return 0;
     }
@@ -792,14 +801,53 @@  aarch64_linux_core_read_vq (struct gdbarch *gdbarch, bfd *abfd)
   return vq;
 }
 
+/* Get the vector quotient (VQ) value from CORE_BFD's sections.
+
+   Return non-zero if successful and 0 otherwise.  */
+
+static uint64_t
+aarch64_linux_core_read_vq_from_sections (struct gdbarch *gdbarch,
+					  bfd *core_bfd)
+{
+  /* First check if we have a SSVE section.  If so, check if it is active.  */
+  asection *section = bfd_get_section_by_name (core_bfd, ".reg-aarch-ssve");
+
+  if (section != nullptr)
+    {
+      /* We've found a SSVE section, so now fetch its data.  */
+      gdb_byte header[SVE_HEADER_SIZE];
+
+      if (bfd_get_section_contents (core_bfd, section, header, 0,
+				    SVE_HEADER_SIZE))
+	{
+	  /* Check if the SSVE section has SVE contents.  */
+	  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+	  uint16_t flags
+	    = extract_unsigned_integer (header + SVE_HEADER_FLAGS_OFFSET,
+					SVE_HEADER_FLAGS_LENGTH, byte_order);
+
+	  if (flags & SVE_HEADER_FLAG_SVE)
+	    {
+	      /* The SSVE state is active, so return the vector length from the
+		 the SSVE section.  */
+	      return aarch64_linux_core_read_vq (gdbarch, core_bfd,
+						 ".reg-aarch-ssve");
+	    }
+	}
+    }
+
+  /* No valid SSVE section.  Return the vq from the SVE section (if any).  */
+  return aarch64_linux_core_read_vq (gdbarch, core_bfd, ".reg-aarch-sve");
+}
+
 /* Supply register REGNUM from BUF to REGCACHE, using the register map
    in REGSET.  If REGNUM is -1, do this for all registers in REGSET.
-   If BUF is NULL, set the registers to "unavailable" status.  */
+   If BUF is nullptr, set the registers to "unavailable" status.  */
 
 static void
-aarch64_linux_supply_sve_regset (const struct regset *regset,
-				 struct regcache *regcache,
-				 int regnum, const void *buf, size_t size)
+supply_sve_regset (const struct regset *regset,
+		   struct regcache *regcache,
+		   int regnum, const void *buf, size_t size)
 {
   gdb_byte *header = (gdb_byte *) buf;
   struct gdbarch *gdbarch = regcache->arch ();
@@ -851,14 +899,89 @@  aarch64_linux_supply_sve_regset (const struct regset *regset,
     }
 }
 
+/* Collect an inactive SVE register set state.  This is equivalent to a
+   fpsimd layout.
+
+   Collect the data from REGCACHE to BUF, using the register
+   map in REGSET.  */
+
+static void
+collect_inactive_sve_regset (const struct regcache *regcache,
+			     void *buf, size_t size, int vg_regnum)
+{
+  gdb_byte *header = (gdb_byte *) buf;
+  struct gdbarch *gdbarch = regcache->arch ();
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+  gdb_assert (buf != nullptr);
+  gdb_assert (size >= SVE_CORE_DUMMY_SIZE);
+
+  /* Zero out everything first.  */
+  memset ((gdb_byte *) buf, 0, SVE_CORE_DUMMY_SIZE);
+
+  /* BUF starts with a SVE header prior to the register dump.  */
+
+  /* Dump the default size of an empty SVE payload.  */
+  uint32_t real_size = SVE_CORE_DUMMY_SIZE;
+  store_unsigned_integer (header + SVE_HEADER_SIZE_OFFSET,
+			  SVE_HEADER_SIZE_LENGTH, byte_order, real_size);
+
+  /* Dump a dummy max size.  */
+  uint32_t max_size = SVE_CORE_DUMMY_MAX_SIZE;
+  store_unsigned_integer (header + SVE_HEADER_MAX_SIZE_OFFSET,
+			  SVE_HEADER_MAX_SIZE_LENGTH, byte_order, max_size);
+
+  /* Dump the vector length.  */
+  ULONGEST vg = 0;
+  regcache->raw_collect (vg_regnum, &vg);
+  uint16_t vl = sve_vl_from_vg (vg);
+  store_unsigned_integer (header + SVE_HEADER_VL_OFFSET, SVE_HEADER_VL_LENGTH,
+			  byte_order, vl);
+
+  /* Dump the standard maximum vector length.  */
+  uint16_t max_vl = SVE_CORE_DUMMY_MAX_VL;
+  store_unsigned_integer (header + SVE_HEADER_MAX_VL_OFFSET,
+			  SVE_HEADER_MAX_VL_LENGTH, byte_order,
+			  max_vl);
+
+  /* The rest of the fields are zero.  */
+  uint16_t flags = SVE_CORE_DUMMY_FLAGS;
+  store_unsigned_integer (header + SVE_HEADER_FLAGS_OFFSET,
+			  SVE_HEADER_FLAGS_LENGTH, byte_order,
+			  flags);
+  uint16_t reserved = SVE_CORE_DUMMY_RESERVED;
+  store_unsigned_integer (header + SVE_HEADER_RESERVED_OFFSET,
+			  SVE_HEADER_RESERVED_LENGTH, byte_order, reserved);
+
+  /* We are done with the header part of it.  Now dump the register state
+     in the FPSIMD format.  */
+
+  /* Dump the first 128 bits of each of the Z registers.  */
+  header += AARCH64_SVE_CONTEXT_REGS_OFFSET;
+  for (int i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+    regcache->raw_collect_part (AARCH64_SVE_Z0_REGNUM + i, 0, V_REGISTER_SIZE,
+				header + V_REGISTER_SIZE * i);
+
+  /* Dump FPSR and FPCR.  */
+  header += 32 * V_REGISTER_SIZE;
+  regcache->raw_collect (AARCH64_FPSR_REGNUM, header);
+  regcache->raw_collect (AARCH64_FPCR_REGNUM, header + 4);
+
+  /* Dump two reserved empty fields of 4 bytes.  */
+  header += 8;
+  memset (header, 0, 8);
+
+  /* We should have a FPSIMD-formatted register dump now.  */
+}
+
 /* Collect register REGNUM from REGCACHE to BUF, using the register
    map in REGSET.  If REGNUM is -1, do this for all registers in
    REGSET.  */
 
 static void
-aarch64_linux_collect_sve_regset (const struct regset *regset,
-				  const struct regcache *regcache,
-				  int regnum, void *buf, size_t size)
+collect_sve_regset (const struct regset *regset,
+		    const struct regcache *regcache,
+		    int regnum, void *buf, size_t size)
 {
   gdb_byte *header = (gdb_byte *) buf;
   struct gdbarch *gdbarch = regcache->arch ();
@@ -873,24 +996,318 @@  aarch64_linux_collect_sve_regset (const struct regset *regset,
 
   store_unsigned_integer (header + SVE_HEADER_SIZE_OFFSET,
 			  SVE_HEADER_SIZE_LENGTH, byte_order, size);
+  uint32_t max_size = SVE_CORE_DUMMY_MAX_SIZE;
   store_unsigned_integer (header + SVE_HEADER_MAX_SIZE_OFFSET,
-			  SVE_HEADER_MAX_SIZE_LENGTH, byte_order, size);
+			  SVE_HEADER_MAX_SIZE_LENGTH, byte_order, max_size);
   store_unsigned_integer (header + SVE_HEADER_VL_OFFSET, SVE_HEADER_VL_LENGTH,
 			  byte_order, sve_vl_from_vq (vq));
+  uint16_t max_vl = SVE_CORE_DUMMY_MAX_VL;
   store_unsigned_integer (header + SVE_HEADER_MAX_VL_OFFSET,
 			  SVE_HEADER_MAX_VL_LENGTH, byte_order,
-			  sve_vl_from_vq (vq));
+			  max_vl);
+  uint16_t flags = SVE_HEADER_FLAG_SVE;
   store_unsigned_integer (header + SVE_HEADER_FLAGS_OFFSET,
 			  SVE_HEADER_FLAGS_LENGTH, byte_order,
-			  SVE_HEADER_FLAG_SVE);
+			  flags);
+  uint16_t reserved = SVE_CORE_DUMMY_RESERVED;
   store_unsigned_integer (header + SVE_HEADER_RESERVED_OFFSET,
-			  SVE_HEADER_RESERVED_LENGTH, byte_order, 0);
+			  SVE_HEADER_RESERVED_LENGTH, byte_order, reserved);
 
   /* The SVE register dump follows.  */
   regcache->collect_regset (regset, regnum, (gdb_byte *) buf + SVE_HEADER_SIZE,
 			    size - SVE_HEADER_SIZE);
 }
 
+/* Supply register REGNUM from BUF to REGCACHE, using the register map
+   in REGSET.  If REGNUM is -1, do this for all registers in REGSET.
+   If BUF is NULL, set the registers to "unavailable" status.  */
+
+static void
+aarch64_linux_supply_sve_regset (const struct regset *regset,
+				 struct regcache *regcache,
+				 int regnum, const void *buf, size_t size)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+
+  if (tdep->has_sme ())
+    {
+      ULONGEST svcr = 0;
+      regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+
+      /* Is streaming mode enabled?  */
+      if (svcr & SVCR_SM_BIT)
+	/* If so, don't load SVE data from the SVE section.  The data to be
+	   used is in the SSVE section.  */
+	return;
+    }
+  /* If streaming mode is not enabled, load the SVE regcache data from the SVE
+     section.  */
+  supply_sve_regset (regset, regcache, regnum, buf, size);
+}
+
+/* Collect register REGNUM from REGCACHE to BUF, using the register
+   map in REGSET.  If REGNUM is -1, do this for all registers in
+   REGSET.  */
+
+static void
+aarch64_linux_collect_sve_regset (const struct regset *regset,
+				  const struct regcache *regcache,
+				  int regnum, void *buf, size_t size)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+  bool streaming_mode = false;
+
+  if (tdep->has_sme ())
+    {
+      ULONGEST svcr = 0;
+      regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+
+      /* Is streaming mode enabled?  */
+      if (svcr & SVCR_SM_BIT)
+	{
+	  /* If so, don't dump SVE regcache data to the SVE section.  The SVE
+	     data should be dumped to the SSVE section.  Dump an empty SVE
+	     block instead.  */
+	  streaming_mode = true;
+	}
+    }
+
+  /* If streaming mode is not enabled or there is no SME support, dump the
+     SVE regcache data to the SVE section.  */
+
+  /* Check if we have an active SVE state (non-zero Z/P/FFR registers).
+     If so, then we need to dump registers in the SVE format.
+
+     Otherwise we should dump the registers in the FPSIMD format.  */
+  if (sve_state_is_empty (regcache) || streaming_mode)
+    collect_inactive_sve_regset (regcache, buf, size, AARCH64_SVE_VG_REGNUM);
+  else
+    collect_sve_regset (regset, regcache, regnum, buf, size);
+}
+
+/* Supply register REGNUM from BUF to REGCACHE, using the register map
+   in REGSET.  If REGNUM is -1, do this for all registers in REGSET.
+   If BUF is NULL, set the registers to "unavailable" status.  */
+
+static void
+aarch64_linux_supply_ssve_regset (const struct regset *regset,
+				  struct regcache *regcache,
+				  int regnum, const void *buf, size_t size)
+{
+  gdb_byte *header = (gdb_byte *) buf;
+  struct gdbarch *gdbarch = regcache->arch ();
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+
+  uint16_t flags = extract_unsigned_integer (header + SVE_HEADER_FLAGS_OFFSET,
+					     SVE_HEADER_FLAGS_LENGTH,
+					     byte_order);
+
+  /* Since SVCR's bits are inferred from the data we have in the header of the
+     SSVE section, we need to initialize it to zero first, so that it doesn't
+     carry garbage data.  */
+  ULONGEST svcr = 0;
+  regcache->raw_supply (tdep->sme_svcr_regnum, &svcr);
+
+  /* Is streaming mode enabled?  */
+  if (flags & SVE_HEADER_FLAG_SVE)
+    {
+      /* Streaming mode is active, so flip the SM bit.  */
+      svcr = SVCR_SM_BIT;
+      regcache->raw_supply (tdep->sme_svcr_regnum, &svcr);
+
+      /* Fetch the SVE data from the SSVE section.  */
+      supply_sve_regset (regset, regcache, regnum, buf, size);
+    }
+}
+
+/* Collect register REGNUM from REGCACHE to BUF, using the register
+   map in REGSET.  If REGNUM is -1, do this for all registers in
+   REGSET.  */
+
+static void
+aarch64_linux_collect_ssve_regset (const struct regset *regset,
+				   const struct regcache *regcache,
+				   int regnum, void *buf, size_t size)
+{
+  struct gdbarch *gdbarch = regcache->arch ();
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+  ULONGEST svcr = 0;
+  regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+
+  /* Is streaming mode enabled?  */
+  if (svcr & SVCR_SM_BIT)
+    {
+      /* If so, dump SVE regcache data to the SSVE section.  */
+      collect_sve_regset (regset, regcache, regnum, buf, size);
+    }
+  else
+    {
+      /* Otherwise dump an empty SVE block to the SSVE section with the
+	 streaming vector length.  */
+      collect_inactive_sve_regset (regcache, buf, size, tdep->sme_svg_regnum);
+    }
+}
+
+/* Supply register REGNUM from BUF to REGCACHE, using the register map
+   in REGSET.  If REGNUM is -1, do this for all registers in REGSET.
+   If BUF is NULL, set the registers to "unavailable" status.  */
+
+static void
+aarch64_linux_supply_za_regset (const struct regset *regset,
+				struct regcache *regcache,
+				int regnum, const void *buf, size_t size)
+{
+  gdb_byte *header = (gdb_byte *) buf;
+  struct gdbarch *gdbarch = regcache->arch ();
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+  /* Handle an empty buffer.  */
+  if (buf == nullptr)
+    return regcache->supply_regset (regset, regnum, nullptr, size);
+
+  if (size < SVE_HEADER_SIZE)
+    error (_("ZA state header size (%s) invalid.  Should be at least %s."),
+	   pulongest (size), pulongest (SVE_HEADER_SIZE));
+
+  /* The ZA register note in a core file can have a couple of states:
+
+     1 - Just the header without the payload.  This means that there is no
+	 ZA data, and we should populate only SVCR and SVG registers on GDB's
+	 side.  The ZA data should be marked as unavailable.
+
+     2 - The header with an additional data payload.  This means there is
+	 actual ZA data, and we should populate ZA, SVCR and SVG.  */
+
+  aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+
+  /* Populate SVG.  */
+  ULONGEST svg
+    = sve_vg_from_vl (extract_unsigned_integer (header + SVE_HEADER_VL_OFFSET,
+						SVE_HEADER_VL_LENGTH,
+						byte_order));
+  regcache->raw_supply (tdep->sme_svg_regnum, &svg);
+
+  size_t data_size
+    = extract_unsigned_integer (header + SVE_HEADER_SIZE_OFFSET,
+				SVE_HEADER_SIZE_LENGTH, byte_order)
+      - SVE_HEADER_SIZE;
+
+  /* Populate SVCR.  */
+  bool has_za_payload = (data_size > 0);
+  ULONGEST svcr;
+  regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+
+  /* If we have a ZA payload, enable bit 2 of SVCR, otherwise clear it.  This
+     register gets updated by the SVE/SSVE-handling functions as well, as they
+     report the SM bit 1.  */
+  if (has_za_payload)
+    svcr |= SVCR_ZA_BIT;
+  else
+    svcr &= ~SVCR_ZA_BIT;
+
+  /* Update SVCR in the register buffer.  */
+  regcache->raw_supply (tdep->sme_svcr_regnum, &svcr);
+
+  /* Populate the register cache with ZA register contents, if we have any.  */
+  buf = has_za_payload ? (gdb_byte *) buf + SVE_HEADER_SIZE : nullptr;
+
+  size_t za_bytes = std::pow (sve_vl_from_vg (svg), 2);
+
+  /* Update ZA in the register buffer.  */
+  if (has_za_payload)
+    {
+      /* Check that the payload size is sane.  */
+      if (size < SVE_HEADER_SIZE + za_bytes)
+	{
+	  error (_("ZA header + payload size (%s) invalid.  Should be at "
+		   "least %s."),
+		 pulongest (size), pulongest (SVE_HEADER_SIZE + za_bytes));
+	}
+
+      regcache->raw_supply (tdep->sme_za_regnum, buf);
+    }
+  else
+    {
+      gdb_byte za_zeroed[za_bytes];
+      memset (za_zeroed, 0, za_bytes);
+      regcache->raw_supply (tdep->sme_za_regnum, za_zeroed);
+    }
+}
+
+/* Collect register REGNUM from REGCACHE to BUF, using the register
+   map in REGSET.  If REGNUM is -1, do this for all registers in
+   REGSET.  */
+
+static void
+aarch64_linux_collect_za_regset (const struct regset *regset,
+				 const struct regcache *regcache,
+				 int regnum, void *buf, size_t size)
+{
+  gdb_assert (buf != nullptr);
+
+  /* Sanity check the dump size.  */
+  gdb_assert (size >= SVE_HEADER_SIZE);
+
+  /* The ZA register note in a core file can have a couple of states:
+
+     1 - Just the header without the payload.  This means that there is no
+	 ZA data, and we should dump just the header.
+
+     2 - The header with an additional data payload.  This means there is
+	 actual ZA data, and we should dump both the header and the ZA data
+	 payload.  */
+
+  aarch64_gdbarch_tdep *tdep
+    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
+
+  /* Determine if we have ZA state from the SVCR register ZA bit.  */
+  ULONGEST svcr;
+  regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+
+  /* Check the ZA payload.  */
+  bool has_za_payload = (svcr & SVCR_ZA_BIT) != 0;
+  size = has_za_payload ? size : SVE_HEADER_SIZE;
+
+  /* Write the size and max_size fields.  */
+  gdb_byte *header = (gdb_byte *) buf;
+  enum bfd_endian byte_order = gdbarch_byte_order (regcache->arch ());
+  store_unsigned_integer (header + SVE_HEADER_SIZE_OFFSET,
+			  SVE_HEADER_SIZE_LENGTH, byte_order, size);
+
+  uint32_t max_size
+    = SVE_HEADER_SIZE + std::pow (sve_vl_from_vq (tdep->sme_svq), 2);
+  store_unsigned_integer (header + SVE_HEADER_MAX_SIZE_OFFSET,
+			  SVE_HEADER_MAX_SIZE_LENGTH, byte_order, max_size);
+
+  /* Output the other fields of the ZA header (vl, max_vl, flags and
+     reserved).  */
+  uint64_t svq = tdep->sme_svq;
+  store_unsigned_integer (header + SVE_HEADER_VL_OFFSET, SVE_HEADER_VL_LENGTH,
+			  byte_order, sve_vl_from_vq (svq));
+
+  uint16_t max_vl = SVE_CORE_DUMMY_MAX_VL;
+  store_unsigned_integer (header + SVE_HEADER_MAX_VL_OFFSET,
+			  SVE_HEADER_MAX_VL_LENGTH, byte_order,
+			  max_vl);
+
+  uint16_t flags = SVE_CORE_DUMMY_FLAGS;
+  store_unsigned_integer (header + SVE_HEADER_FLAGS_OFFSET,
+			  SVE_HEADER_FLAGS_LENGTH, byte_order, flags);
+
+  uint16_t reserved = SVE_CORE_DUMMY_RESERVED;
+  store_unsigned_integer (header + SVE_HEADER_RESERVED_OFFSET,
+			  SVE_HEADER_RESERVED_LENGTH, byte_order, reserved);
+
+  buf = has_za_payload ? (gdb_byte *) buf + SVE_HEADER_SIZE : nullptr;
+
+  /* Dump the register cache contents for the ZA register to the buffer.  */
+  regcache->collect_regset (regset, regnum, (gdb_byte *) buf,
+			    size - SVE_HEADER_SIZE);
+}
+
 /* Implement the "iterate_over_regset_sections" gdbarch method.  */
 
 static void
@@ -917,6 +1334,30 @@  aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
 	  { 0 }
 	};
 
+      const struct regset aarch64_linux_ssve_regset =
+	{
+	  sve_regmap,
+	  aarch64_linux_supply_ssve_regset, aarch64_linux_collect_ssve_regset,
+	  REGSET_VARIABLE_SIZE
+	};
+
+      /* If SME is supported in the core file, process the SSVE section first,
+	 and the SVE section last.  This is because we need information from
+	 the SSVE set to determine if streaming mode is active.  If streaming
+	 mode is active, we need to extract the data from the SSVE section.
+
+	 Otherwise, if streaming mode is not active, we fetch the data from the
+	 SVE section.  */
+      if (tdep->has_sme ())
+	{
+	  cb (".reg-aarch-ssve",
+	      SVE_HEADER_SIZE
+	      + regcache_map_entry_size (aarch64_linux_fpregmap),
+	      SVE_HEADER_SIZE + regcache_map_entry_size (sve_regmap),
+	      &aarch64_linux_ssve_regset, "SSVE registers", cb_data);
+	}
+
+      /* Handle the SVE register set.  */
       const struct regset aarch64_linux_sve_regset =
 	{
 	  sve_regmap,
@@ -933,6 +1374,29 @@  aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
     cb (".reg2", AARCH64_LINUX_SIZEOF_FPREGSET, AARCH64_LINUX_SIZEOF_FPREGSET,
 	&aarch64_linux_fpregset, NULL, cb_data);
 
+  if (tdep->has_sme ())
+    {
+      /* Setup the register set information for a ZA register set core
+	 dump.  */
+
+      /* Create this on the fly in order to handle the ZA register size.  */
+      const struct regcache_map_entry za_regmap[] =
+	{
+	  { 1, tdep->sme_za_regnum, (int) std::pow (sve_vl_from_vq (tdep->sme_svq), 2) }
+	};
+
+      const struct regset aarch64_linux_za_regset =
+	{
+	  za_regmap,
+	  aarch64_linux_supply_za_regset, aarch64_linux_collect_za_regset,
+	  REGSET_VARIABLE_SIZE
+	};
+
+      cb (".reg-aarch-za",
+	  SVE_HEADER_SIZE,
+	  SVE_HEADER_SIZE + std::pow (sve_vl_from_vq (tdep->sme_svq), 2),
+	  &aarch64_linux_za_regset, "ZA register", cb_data);
+    }
 
   if (tdep->has_pauth ())
     {
@@ -1011,7 +1475,16 @@  aarch64_linux_core_read_description (struct gdbarch *gdbarch,
   CORE_ADDR hwcap2 = linux_get_hwcap2 (auxv, target, gdbarch);
 
   aarch64_features features;
-  features.vq = aarch64_linux_core_read_vq (gdbarch, abfd);
+
+  /* We need to extract the SVE data from the .reg-aarch-sve section or the
+     .reg-aarch-ssve section depending on which one was active when the core
+     file was generated.
+
+     If the SSVE section contains SVE data, then it is considered active.
+     Otherwise the SVE section is considered active.  This guarantees we will
+     have the correct target description with the correct SVE vector
+     length.  */
+  features.vq = aarch64_linux_core_read_vq_from_sections (gdbarch, abfd);
   features.pauth = hwcap & AARCH64_HWCAP_PACA;
   features.mte = hwcap2 & HWCAP2_MTE;
 
@@ -1025,6 +1498,9 @@  aarch64_linux_core_read_description (struct gdbarch *gdbarch,
       features.tls = size / AARCH64_TLS_REGISTER_SIZE;
     }
 
+  features.svq
+    = aarch64_linux_core_read_vq (gdbarch, abfd, ".reg-aarch-za");
+
   return aarch64_read_description (features);
 }
 
diff --git a/gdb/arch/aarch64-scalable-linux.c b/gdb/arch/aarch64-scalable-linux.c
index 3803acfd9a8..2e4aa92e36f 100644
--- a/gdb/arch/aarch64-scalable-linux.c
+++ b/gdb/arch/aarch64-scalable-linux.c
@@ -19,3 +19,37 @@ 
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "arch/aarch64-scalable-linux.h"
+#include "arch/aarch64.h"
+#include "gdbsupport/byte-vector.h"
+#include "gdbsupport/common-regcache.h"
+
+/* See arch/aarch64-scalable-linux.h  */
+
+bool
+sve_state_is_empty (const struct reg_buffer_common *reg_buf)
+{
+  /* Instead of allocating a buffer with the size of the current vector
+     length, just use a buffer that is big enough for all cases.  */
+  gdb_byte zero_buffer[256];
+
+  /* Zero it out.  */
+  memset (zero_buffer, 0, 256);
+
+  /* Are any of the Z registers set (non-zero) after the first 128 bits?  */
+  for (int i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+    {
+      if (!reg_buf->raw_compare (AARCH64_SVE_Z0_REGNUM + i, zero_buffer,
+				 V_REGISTER_SIZE))
+	return false;
+    }
+
+  /* Are any of the P registers set (non-zero)?  */
+  for (int i = 0; i < AARCH64_SVE_P_REGS_NUM; i++)
+    {
+      if (!reg_buf->raw_compare (AARCH64_SVE_P0_REGNUM + i, zero_buffer, 0))
+	return false;
+    }
+
+  /* Is the FFR register set (non-zero)?  */
+  return reg_buf->raw_compare (AARCH64_SVE_FFR_REGNUM, zero_buffer, 0);
+}
diff --git a/gdb/arch/aarch64-scalable-linux.h b/gdb/arch/aarch64-scalable-linux.h
index df1741004ed..cb9d85a9d5d 100644
--- a/gdb/arch/aarch64-scalable-linux.h
+++ b/gdb/arch/aarch64-scalable-linux.h
@@ -22,6 +22,7 @@ 
 #define ARCH_AARCH64_SCALABLE_LINUX_H
 
 #include "gdbsupport/common-defs.h"
+#include "gdbsupport/common-regcache.h"
 
 /* Feature check for Scalable Matrix Extension.  */
 #ifndef HWCAP2_SME
@@ -35,4 +36,18 @@ 
 /* Mask including all valid SVCR bits.  */
 #define SVCR_BIT_MASK (SVCR_SM_BIT | SVCR_ZA_BIT)
 
+/* SVE/SSVE-related constants used for an empty SVE/SSVE register set
+   dumped to a core file.  When SME is supported, either the SVE state or
+   the SSVE state will be empty when it is dumped to a core file.  */
+#define SVE_CORE_DUMMY_SIZE 0x220
+#define SVE_CORE_DUMMY_MAX_SIZE 0x2240
+#define SVE_CORE_DUMMY_VL 0x10
+#define SVE_CORE_DUMMY_MAX_VL 0x100
+#define SVE_CORE_DUMMY_FLAGS 0x0
+#define SVE_CORE_DUMMY_RESERVED 0x0
+
+/* Return TRUE if the SVE state in the register cache REGCACHE
+   is empty (zero).  Return FALSE otherwise.  */
+extern bool sve_state_is_empty (const struct reg_buffer_common *reg_buf);
+
 #endif /* ARCH_AARCH64_SCALABLE_LINUX_H */