[v3,14/16,gdb/aarch64] sme: Core file support for Linux

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

Commit Message

Luis Machado June 30, 2023, 1:46 p.m. UTC
  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.
---
 gdb/aarch64-linux-tdep.c          | 532 ++++++++++++++++++++++++++++--
 gdb/arch/aarch64-scalable-linux.c |  34 ++
 gdb/arch/aarch64-scalable-linux.h |  15 +
 3 files changed, 548 insertions(+), 33 deletions(-)
  

Comments

Thiago Jung Bauermann Aug. 3, 2023, 12:18 a.m. UTC | #1
Luis Machado via Gdb-patches <gdb-patches@sourceware.org> writes:

> 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.
> ---
>  gdb/aarch64-linux-tdep.c          | 532 ++++++++++++++++++++++++++++--
>  gdb/arch/aarch64-scalable-linux.c |  34 ++
>  gdb/arch/aarch64-scalable-linux.h |  15 +
>  3 files changed, 548 insertions(+), 33 deletions(-)
>
> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
> index 7ce34ee6846..0bd75daa994 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::sqrt */

s/sqrt/pow/

> +#include <cmath>

> @@ -853,14 +901,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_HEADER_SIZE);

This assert should check for 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 is zero.  */

s/is/are/

> +  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);

Header needs to be incremented before writing FPCR, otherwise
raw_collect() will just overwrite FPSR.

> +
> +  /* Dump two reserved empty fields of 4 bytes.  */
> +  header += 8;
> +  memset (header, 0, 8);
> +
> +  /* We should have a FPSIMD-formatted register dump now.  */
> +}

> +/* 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);

Instead of relying on parsing the SSVE section before the SVE section to
ensure that the SVCR register is valid by the time we arrive here, isn't
it more robust to read the flags field from the SVE header in buf, as is
done in suply_sve_regset()? Also, it's what the SSE version does.

> +
> +      /* 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);

If regnum != -1, collect_inactive_sve_regset () will still dump the
entirety of the fpsimd regset. Shouldn't it get the regnum as an
argument and dump just the requested register?

Same comment in aarch64_linux_collect_ssve_regset ().

> +  else
> +    collect_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);

Same argument here about regnum != -1 as in
aarch64_linux_collect_sve_regset().

> +    }
> +}
> +
> +/* 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)
> +    warning (_("ZA state header size (%s) invalid.  Should be at least %s."),
> +	     pulongest (size), pulongest (SVE_HEADER_SIZE));

If the header is truncated, I don't think the rest of this function
works correctly. I'd suggest either changing the warning to an error, or
treating this case in the same way as the buf == nullptr case above.

> +
> +  /* 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;
> +
> +  /* Update ZA in the register buffer.  */
> +  if (has_za_payload)
> +    regcache->raw_supply (tdep->sme_za_regnum, buf);

Do we need to make sure that buf is big enough to supply the whole
contents of the ZA register? Otherwise, the memcpy() in raw_supply() will
copy garbage.

> +  else
> +    {
> +      size_t za_bytes = std::pow (sve_vl_from_vg (svg), 2);
> +      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);
> +
> +  if (size < SVE_HEADER_SIZE)
> +    warning (_("ZA state header size (%s) invalid.  Should be at least %s."),
> +	     pulongest (size), pulongest (SVE_HEADER_SIZE));

Here too, if size < SVE_HEADER_SIZE I don't think it makes sense to try
to continue. Perhaps put this condition in a gdb_assert()?

> +
> +  /* 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);

Calling collect with buf == nullptr will invalidate the regset. Is that
the intention?

> +}
  
Luis Machado Aug. 3, 2023, 11:37 a.m. UTC | #2
On 8/3/23 01:18, Thiago Jung Bauermann wrote:
> 
> Luis Machado via Gdb-patches <gdb-patches@sourceware.org> writes:
> 
>> 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.
>> ---
>>  gdb/aarch64-linux-tdep.c          | 532 ++++++++++++++++++++++++++++--
>>  gdb/arch/aarch64-scalable-linux.c |  34 ++
>>  gdb/arch/aarch64-scalable-linux.h |  15 +
>>  3 files changed, 548 insertions(+), 33 deletions(-)
>>
>> diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
>> index 7ce34ee6846..0bd75daa994 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::sqrt */
> 
> s/sqrt/pow/
> 

Fixed.


>> +#include <cmath>
> 
>> @@ -853,14 +901,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_HEADER_SIZE);
> 
> This assert should check for SVE_CORE_DUMMY_SIZE.
> 

Fixed.

>> +
>> +  /* 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 is zero.  */
> 
> s/is/are/
> 

Fixed.

>> +  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);
> 
> Header needs to be incremented before writing FPCR, otherwise
> raw_collect() will just overwrite FPSR.
> 

Fixed.

>> +
>> +  /* Dump two reserved empty fields of 4 bytes.  */
>> +  header += 8;
>> +  memset (header, 0, 8);
>> +
>> +  /* We should have a FPSIMD-formatted register dump now.  */
>> +}
> 
>> +/* 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);
> 
> Instead of relying on parsing the SSVE section before the SVE section to
> ensure that the SVCR register is valid by the time we arrive here, isn't
> it more robust to read the flags field from the SVE header in buf, as is
> done in suply_sve_regset()? Also, it's what the SSE version does.
>

Did you mean s/SSE/SME?

I'm not sure I understand. We need to process the SSVE entry first to make sure we have the right values for SVCR, and
the SVE entry may be inactive. Could you please expand on your idea?
 
>> +
>> +      /* 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);
> 
> If regnum != -1, collect_inactive_sve_regset () will still dump the
> entirety of the fpsimd regset. Shouldn't it get the regnum as an
> argument and dump just the requested register?
> 
> Same comment in aarch64_linux_collect_ssve_regset ().
> 

That is a bit strange, but I don't see this function being called with a specified regnum other than -1.

For instance, in gcore-elf.c:gcore_elf_build_thread_register_notes, we fetch all registers.

Also, in corelow.c:core_target::get_core_register_section, supply is also explicitly called with -1.

Maybe this was updated at some point, and we don't fetch single registers from core files anymore.

Do you see any paths calling this with regnum != -1?

>> +  else
>> +    collect_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);
> 
> Same argument here about regnum != -1 as in
> aarch64_linux_collect_sve_regset().
> 

See above.

>> +    }
>> +}
>> +
>> +/* 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)
>> +    warning (_("ZA state header size (%s) invalid.  Should be at least %s."),
>> +	     pulongest (size), pulongest (SVE_HEADER_SIZE));
> 
> If the header is truncated, I don't think the rest of this function
> works correctly. I'd suggest either changing the warning to an error, or
> treating this case in the same way as the buf == nullptr case above.
> 

Yeah, this should be an error. I think at some point I used to handle it more gracefully, but it
wasn't worth it.

>> +
>> +  /* 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;
>> +
>> +  /* Update ZA in the register buffer.  */
>> +  if (has_za_payload)
>> +    regcache->raw_supply (tdep->sme_za_regnum, buf);
> 
> Do we need to make sure that buf is big enough to supply the whole
> contents of the ZA register? Otherwise, the memcpy() in raw_supply() will
> copy garbage.
> 

Probably. Unlikely it would be wrong, but worth a check.

>> +  else
>> +    {
>> +      size_t za_bytes = std::pow (sve_vl_from_vg (svg), 2);
>> +      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);
>> +
>> +  if (size < SVE_HEADER_SIZE)
>> +    warning (_("ZA state header size (%s) invalid.  Should be at least %s."),
>> +	     pulongest (size), pulongest (SVE_HEADER_SIZE));
> 
> Here too, if size < SVE_HEADER_SIZE I don't think it makes sense to try
> to continue. Perhaps put this condition in a gdb_assert()?
> 

Agreed. Fixed.

>> +
>> +  /* 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);
> 
> Calling collect with buf == nullptr will invalidate the regset. Is that
> the intention?
> 

Yes. It will be invalidated and memset-ed to 0. Though that behavior might not be a strong
guarantee.

>> +}
>
  
Thiago Jung Bauermann Aug. 4, 2023, 8:45 p.m. UTC | #3
Luis Machado <luis.machado@arm.com> writes:

> On 8/3/23 01:18, Thiago Jung Bauermann wrote:
>> 
>> Luis Machado via Gdb-patches <gdb-patches@sourceware.org> writes:
>> 
>>> +  /* Dump two reserved empty fields of 4 bytes.  */
>>> +  header += 8;
>>> +  memset (header, 0, 8);
>>> +
>>> +  /* We should have a FPSIMD-formatted register dump now.  */
>>> +}
>> 
>>> +/* 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);
>> 
>> Instead of relying on parsing the SSVE section before the SVE section to
>> ensure that the SVCR register is valid by the time we arrive here, isn't
>> it more robust to read the flags field from the SVE header in buf, as is
>> done in suply_sve_regset()? Also, it's what the SSE version does.
>>
>
> Did you mean s/SSE/SME?
>
> I'm not sure I understand. We need to process the SSVE entry first to make sure we have
> the right values for SVCR, and
> the SVE entry may be inactive. Could you please expand on your idea?

I am assuming that if SVE is inactive, the buffer will at least have a
header with sane values such as the dummy one written by
collect_inactive_sve_regset. In this case, we could check whether the
flags field has the SVE bit set:

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 0bd75daa994c..ce9cc4e32000 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -1034,11 +1034,14 @@ aarch64_linux_supply_sve_regset (const struct regset *regset,
 
   if (tdep->has_sme ())
     {
-      ULONGEST svcr = 0;
-      regcache->raw_collect (tdep->sme_svcr_regnum, &svcr);
+      gdb_byte *header = (gdb_byte *) buf;
+      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);
 
       /* Is streaming mode enabled?  */
-      if (svcr & SVCR_SM_BIT)
+      if (flags & SVE_HEADER_FLAG_SVE)
 	/* If so, don't load SVE data from the SVE section.  The data to be
 	   used is in the SSVE section.  */
 	return;

But if we can't assume dummy values for the SVE header, then my idea
won't work.

>>> +
>>> +      /* 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);
>> 
>> If regnum != -1, collect_inactive_sve_regset () will still dump the
>> entirety of the fpsimd regset. Shouldn't it get the regnum as an
>> argument and dump just the requested register?
>> 
>> Same comment in aarch64_linux_collect_ssve_regset ().
>> 
>
> That is a bit strange, but I don't see this function being called with a specified regnum
> other than -1.
>
> For instance, in gcore-elf.c:gcore_elf_build_thread_register_notes, we fetch all
> registers.
>
> Also, in corelow.c:core_target::get_core_register_section, supply is also explicitly
> called with -1.
>
> Maybe this was updated at some point, and we don't fetch single registers from core files
> anymore.
>
> Do you see any paths calling this with regnum != -1?

Indeed you're right, I don't. I made this comment based on the
documentation of regset and regcache functions, and existing regcache
code that deals with regnum != -1.

I see a few targets that seems to expect calling regset->collect_regset
with regnum != -1 (e.g., fbsd_nat_target::store_register_set and
ppc-linux-nat.c's store_regset) but nothing in generic code or
aarch64-linux nat or tdep.

>>> +
>>> +  /* 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);
>> 
>> Calling collect with buf == nullptr will invalidate the regset. Is that
>> the intention?
>> 
>
> Yes. It will be invalidated and memset-ed to 0. Though that behavior might not be a strong
> guarantee.

Ok, thanks for the explanation.
  

Patch

diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index 7ce34ee6846..0bd75daa994 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::sqrt */
+#include <cmath>
 
 /* Signal frame handling.
 
@@ -743,50 +747,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;
     }
@@ -794,14 +803,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 ();
@@ -853,14 +901,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_HEADER_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 is 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);
+
+  /* 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 ();
@@ -875,24 +998,308 @@  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)
+    warning (_("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;
+
+  /* Update ZA in the register buffer.  */
+  if (has_za_payload)
+    regcache->raw_supply (tdep->sme_za_regnum, buf);
+  else
+    {
+      size_t za_bytes = std::pow (sve_vl_from_vg (svg), 2);
+      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);
+
+  if (size < SVE_HEADER_SIZE)
+    warning (_("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 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
@@ -919,6 +1326,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,
@@ -935,6 +1366,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 ())
     {
@@ -1013,7 +1467,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;
 
@@ -1027,6 +1490,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 */