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

Luis Machado via Gdb-patches
2023-06-30T13:50:27Z

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 */
+#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 */
--
2.25.1