[PATCH v2 05/10] Ptrace support for Aarch64 SVE

Alan Hayward
2018-06-06T15:17:26Z

Add support for reading and writing registers for Aarch64 SVE.
I've made this functionality common as it will be required for
gdbserver when gdbsever sve support is added.

We need to support the cases where the kernel only gives us a
fpsimd structure. This occurs when there is no active SVE state
in the kernel (for example, after starting a new process).

Added checks to make sure the vector length has not changed whilst
the process is running.

2018-06-06 Alan Hayward <alan.hayward@arm.com>

gdb/
* aarch64-linux-nat.c (fetch_sveregs_from_thread): New function.
(store_sveregs_to_thread): Likewise.
(aarch64_linux_fetch_inferior_registers): Check for SVE.
(aarch64_linux_store_inferior_registers): Likewise.
* nat/aarch64-sve-linux-ptrace.c (aarch64_sve_get_sveregs): New
function.
(aarch64_sve_regs_copy_to_regcache): Likewise.
(aarch64_sve_regs_copy_from_regcache): Likewise.
* nat/aarch64-sve-linux-ptrace.h (aarch64_sve_get_sveregs): New
declaration.
(aarch64_sve_regs_copy_to_regcache): Likewise.
(aarch64_sve_regs_copy_from_regcache): Likewise.
(sve_context): Structure from Linux headers.
(SVE_SIG_ZREGS_SIZE): Define from Linux headers.
(SVE_SIG_ZREG_SIZE): Likewise.
(SVE_SIG_PREG_SIZE): Likewise.
(SVE_SIG_FFR_SIZE): Likewise.
(SVE_SIG_REGS_OFFSET): Likewise.
(SVE_SIG_ZREGS_OFFSET): Likewise.
(SVE_SIG_ZREG_OFFSET): Likewise.
(SVE_SIG_ZREGS_SIZE): Likewise.
(SVE_SIG_PREGS_OFFSET): Likewise.
(SVE_SIG_PREG_OFFSET): Likewise.
(SVE_SIG_PREGS_SIZE): Likewise.
(SVE_SIG_FFR_OFFSET): Likewise.
(SVE_SIG_REGS_SIZE): Likewise.
(SVE_SIG_CONTEXT_SIZE): Likewise.
(SVE_PT_REGS_MASK): Likewise.
(SVE_PT_REGS_FPSIMD): Likewise.
(SVE_PT_REGS_SVE): Likewise.
(SVE_PT_VL_INHERIT): Likewise.
(SVE_PT_VL_ONEXEC): Likewise.
(SVE_PT_REGS_OFFSET): Likewise.
(SVE_PT_FPSIMD_OFFSET): Likewise.
(SVE_PT_FPSIMD_SIZE): Likewise.
(SVE_PT_SVE_ZREG_SIZE): Likewise.
(SVE_PT_SVE_PREG_SIZE): Likewise.
(SVE_PT_SVE_FFR_SIZE): Likewise.
(SVE_PT_SVE_FPSR_SIZE): Likewise.
(SVE_PT_SVE_FPCR_SIZE): Likewise.
(__SVE_SIG_TO_PT): Likewise.
(SVE_PT_SVE_OFFSET): Likewise.
(SVE_PT_SVE_ZREGS_OFFSET): Likewise.
(SVE_PT_SVE_ZREG_OFFSET): Likewise.
(SVE_PT_SVE_ZREGS_SIZE): Likewise.
(SVE_PT_SVE_PREGS_OFFSET): Likewise.
(SVE_PT_SVE_PREG_OFFSET): Likewise.
(SVE_PT_SVE_PREGS_SIZE): Likewise.
(SVE_PT_SVE_FFR_OFFSET): Likewise.
(SVE_PT_SVE_FPSR_OFFSET): Likewise.
(SVE_PT_SVE_FPCR_OFFSET): Likewise.
(SVE_PT_SVE_SIZE): Likewise.
(SVE_PT_SIZE): Likewise.
(HAS_SVE_STATE): New define.

gdbserver
* Makefile.in: Add aarch64-sve-linux-ptrace.c.
---
gdb/aarch64-linux-nat.c | 54 +++++++-
gdb/gdbserver/Makefile.in | 1 +
gdb/nat/aarch64-sve-linux-ptrace.c | 271 ++++++++++++++++++++++++++++++++++++-
gdb/nat/aarch64-sve-linux-ptrace.h | 21 +++
4 files changed, 344 insertions(+), 3 deletions(-)

diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c
index 1e4f937dc9..4db7d0d977 100644
--- a/gdb/aarch64-linux-nat.c
+++ b/gdb/aarch64-linux-nat.c
@@ -384,19 +384,62 @@ store_fpregs_to_thread (const struct regcache *regcache)
}
}

+/* Fill GDB's register array with the sve register values
+ from the current thread. */
+
+static void
+fetch_sveregs_from_thread (struct regcache *regcache)
+{
+ std::unique_ptr<gdb_byte> base
+ = aarch64_sve_get_sveregs (ptid_get_lwp (regcache->ptid ()));
+ aarch64_sve_regs_copy_to_reg_buf (regcache, base.get ());
+}
+
+/* Store to the current thread the valid sve register
+ values in the GDB's register array. */
+
+static void
+store_sveregs_to_thread (struct regcache *regcache)
+{
+ int ret;
+ struct iovec iovec;
+ int tid = ptid_get_lwp (regcache->ptid ());
+
+ /* Obtain a dump of SVE registers from ptrace. */
+ std::unique_ptr<gdb_byte> base = aarch64_sve_get_sveregs (tid);
+
+ /* Overwrite with regcache state. */
+ aarch64_sve_regs_copy_from_reg_buf (regcache, base.get ());
+
+ /* Write back to the kernel. */
+ iovec.iov_base = base.get ();
+ iovec.iov_len = ((struct user_sve_header *) base.get ())->size;
+ ret = ptrace (PTRACE_SETREGSET, tid, NT_ARM_SVE, &iovec);
+
+ if (ret < 0)
+ perror_with_name (_("Unable to store sve registers"));
+}
+
/* Implement the "fetch_registers" target_ops method. */

void
aarch64_linux_nat_target::fetch_registers (struct regcache *regcache,
int regno)
{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (regcache->arch ());
+
if (regno == -1)
{
fetch_gregs_from_thread (regcache);
- fetch_fpregs_from_thread (regcache);
+ if (tdep->has_sve ())
+ fetch_sveregs_from_thread (regcache);
+ else
+ fetch_fpregs_from_thread (regcache);
}
else if (regno < AARCH64_V0_REGNUM)
fetch_gregs_from_thread (regcache);
+ else if (tdep->has_sve ())
+ fetch_sveregs_from_thread (regcache);
else
fetch_fpregs_from_thread (regcache);
}
@@ -407,13 +450,20 @@ void
aarch64_linux_nat_target::store_registers (struct regcache *regcache,
int regno)
{
+ struct gdbarch_tdep *tdep = gdbarch_tdep (regcache->arch ());
+
if (regno == -1)
{
store_gregs_to_thread (regcache);
- store_fpregs_to_thread (regcache);
+ if (tdep->has_sve ())
+ store_sveregs_to_thread (regcache);
+ else
+ store_fpregs_to_thread (regcache);
}
else if (regno < AARCH64_V0_REGNUM)
store_gregs_to_thread (regcache);
+ else if (tdep->has_sve ())
+ store_sveregs_to_thread (regcache);
else
store_fpregs_to_thread (regcache);
}
diff --git a/gdb/gdbserver/Makefile.in b/gdb/gdbserver/Makefile.in
index 675faa4364..f924e6a7f9 100644
--- a/gdb/gdbserver/Makefile.in
+++ b/gdb/gdbserver/Makefile.in
@@ -219,6 +219,7 @@ SFILES = \
$(srcdir)/common/tdesc.c \
$(srcdir)/common/vec.c \
$(srcdir)/common/xml-utils.c \
+ $(srcdir)/nat/aarch64-sve-linux-ptrace.c \
$(srcdir)/nat/linux-btrace.c \
$(srcdir)/nat/linux-namespaces.c \
$(srcdir)/nat/linux-osdata.c \
diff --git a/gdb/nat/aarch64-sve-linux-ptrace.c b/gdb/nat/aarch64-sve-linux-ptrace.c
index 119656b886..2ab055421a 100644
--- a/gdb/nat/aarch64-sve-linux-ptrace.c
+++ b/gdb/nat/aarch64-sve-linux-ptrace.c
@@ -24,6 +24,10 @@
#include "elf/common.h"
#include "aarch64-sve-linux-ptrace.h"
#include "arch/aarch64.h"
+#include "common-regcache.h"
+#include "common/byte-vector.h"
+
+static bool vq_change_warned = false;

/* See nat/aarch64-sve-linux-ptrace.h. */

@@ -46,7 +50,7 @@ aarch64_sve_get_vq (int tid)
return 0;
}

- long vq = sve_vq_from_vl (header.vl);
+ uint64_t vq = sve_vq_from_vl (header.vl);

if (!sve_vl_valid (header.vl))
{
@@ -56,3 +60,268 @@ aarch64_sve_get_vq (int tid)

return vq;
}
+
+/* See nat/aarch64-sve-linux-ptrace.h. */
+
+std::unique_ptr<gdb_byte>
+aarch64_sve_get_sveregs (int tid)
+{
+ struct iovec iovec;
+ struct user_sve_header header;
+ uint64_t vq = aarch64_sve_get_vq (tid);
+
+ if (vq == 0)
+ perror_with_name (_("Unable to fetch sve register header"));
+
+ /* A ptrace call with NT_ARM_SVE will return a header followed by either a
+ dump of all the SVE and FP registers, or an fpsimd structure (identical to
+ the one returned by NT_FPREGSET) if the kernel has not yet executed any
+ SVE code. Make sure we allocate enough space for a full SVE dump. */
+
+ iovec.iov_len = SVE_PT_SIZE (vq, SVE_PT_REGS_SVE);
+ std::unique_ptr<gdb_byte> buf (new gdb_byte[iovec.iov_len]);
+ iovec.iov_base = buf.get ();
+
+ if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_SVE, &iovec) < 0)
+ perror_with_name (_("Unable to fetch sve registers"));
+
+ return buf;
+}
+
+/* See nat/aarch64-sve-linux-ptrace.h. */
+
+void
+aarch64_sve_regs_copy_to_reg_buf (struct reg_buffer_common *reg_buf,
+ const void *buf)
+{
+ char *base = (char*) buf;
+ int i;
+ struct user_sve_header *header = (struct user_sve_header *) buf;
+ uint64_t vq, vg_reg_buf = 0;
+
+ vq = sve_vq_from_vl (header->vl);
+
+ /* Sanity check the data in the header. */
+ if (!sve_vl_valid (header->vl)
+ || SVE_PT_SIZE (vq, header->flags) != header->size)
+ error (_("Invalid SVE header from kernel."));
+
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_SVE_VG_REGNUM))
+ reg_buf->raw_collect (AARCH64_SVE_VG_REGNUM, &vg_reg_buf);
+
+ if (vg_reg_buf == 0)
+ {
+ /* VG has not been set. */
+ vg_reg_buf = sve_vg_from_vl (header->vl);
+ reg_buf->raw_supply (AARCH64_SVE_VG_REGNUM, &vg_reg_buf);
+ }
+ else if (vg_reg_buf != sve_vg_from_vl (header->vl) && !vq_change_warned)
+ {
+ /* Vector length on the running process has changed. GDB currently does
+ not support this and will result in GDB showing incorrect partially
+ incorrect data for the vector registers. Warn once and continue. We
+ do not expect many programs to exhibit this behaviour. To fix this
+ we need to spot the change earlier and generate a new target
+ descriptor. */
+ warning (_("SVE Vector length has changed (%ld to %d). "
+ "Vector registers may show incorrect data."),
+ vg_reg_buf, sve_vg_from_vl (header->vl));
+ vq_change_warned = true;
+ }
+
+ if (HAS_SVE_STATE (*header))
+ {
+ /* The register dump contains a set of SVE registers. */
+
+ for (i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+ reg_buf->raw_supply (AARCH64_SVE_Z0_REGNUM + i,
+ base + SVE_PT_SVE_ZREG_OFFSET (vq, i));
+
+ for (i = 0; i < AARCH64_SVE_P_REGS_NUM; i++)
+ reg_buf->raw_supply (AARCH64_SVE_P0_REGNUM + i,
+ base + SVE_PT_SVE_PREG_OFFSET (vq, i));
+
+ reg_buf->raw_supply (AARCH64_SVE_FFR_REGNUM,
+ base + SVE_PT_SVE_FFR_OFFSET (vq));
+ reg_buf->raw_supply (AARCH64_FPSR_REGNUM,
+ base + SVE_PT_SVE_FPSR_OFFSET (vq));
+ reg_buf->raw_supply (AARCH64_FPCR_REGNUM,
+ base + SVE_PT_SVE_FPCR_OFFSET (vq));
+ }
+ else
+ {
+ /* There is no SVE state yet - the register dump contains a fpsimd
+ structure instead. These registers still exist in the hardware, but
+ the kernel has not yet initialised them, and so they will be null. */
+
+ char *zero_reg = (char *) alloca (SVE_PT_SVE_ZREG_SIZE (vq));
+ struct user_fpsimd_state *fpsimd
+ = (struct user_fpsimd_state *)(base + SVE_PT_FPSIMD_OFFSET);
+
+ /* Copy across the V registers from fpsimd structure to the Z registers,
+ ensuring the non overlapping state is set to null. */
+
+ memset (zero_reg, 0, SVE_PT_SVE_ZREG_SIZE (vq));
+
+ for (i = 0; i <= AARCH64_SVE_Z_REGS_NUM; i++)
+ {
+ memcpy (zero_reg, &fpsimd->vregs[i], sizeof (__int128_t));
+ reg_buf->raw_supply (AARCH64_SVE_Z0_REGNUM + i, zero_reg);
+ }
+
+ reg_buf->raw_supply (AARCH64_FPSR_REGNUM, &fpsimd->fpsr);
+ reg_buf->raw_supply (AARCH64_FPCR_REGNUM, &fpsimd->fpcr);
+
+ /* Clear the SVE only registers. */
+
+ for (i = 0; i <= AARCH64_SVE_P_REGS_NUM; i++)
+ reg_buf->raw_supply (AARCH64_SVE_P0_REGNUM + i, zero_reg);
+
+ reg_buf->raw_supply (AARCH64_SVE_FFR_REGNUM, zero_reg);
+ }
+}
+
+/* See nat/aarch64-sve-linux-ptrace.h. */
+
+void
+aarch64_sve_regs_copy_from_reg_buf (struct reg_buffer_common *reg_buf,
+ void *buf)
+{
+ struct user_sve_header *header = (struct user_sve_header *) buf;
+ char *base = (char*) buf;
+ uint64_t vq, vg_reg_buf = 0;
+ int i;
+
+ vq = sve_vq_from_vl (header->vl);
+
+ /* Sanity check the data in the header. */
+ if (!sve_vl_valid (header->vl)
+ || SVE_PT_SIZE (vq, header->flags) != header->size)
+ error (_("Invalid SVE header from kernel."));
+
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_SVE_VG_REGNUM))
+ reg_buf->raw_collect (AARCH64_SVE_VG_REGNUM, &vg_reg_buf);
+
+ if (vg_reg_buf != 0 && vg_reg_buf != sve_vg_from_vl (header->vl))
+ {
+ /* Vector length on the running process has changed. GDB currently does
+ not support this and will result in GDB writing invalid data back to
+ the vector registers. Error and exit. We do not expect many programs
+ to exhibit this behaviour. To fix this we need to spot the change
+ earlier and generate a new target descriptor. */
+ error (_("SVE Vector length has changed (%ld to %d). "
+ "Cannot write back registers."),
+ vg_reg_buf, sve_vg_from_vl (header->vl));
+ }
+
+ if (!HAS_SVE_STATE (*header))
+ {
+ /* There is no SVE state yet - the register dump contains a fpsimd
+ structure instead. Where possible we want to write the reg_buf data
+ back to the kernel using the fpsimd structure. However, if we cannot
+ then we'll need to reformat the fpsimd into a full SVE structure,
+ resulting in the initialization of SVE state written back to the
+ kernel, which is why we try to avoid it. */
+
+ int has_sve_state = 0;
+ char *zero_reg = (char *) alloca (SVE_PT_SVE_ZREG_SIZE (vq));
+ struct user_fpsimd_state *fpsimd
+ = (struct user_fpsimd_state *)(base + SVE_PT_FPSIMD_OFFSET);
+
+ memset (zero_reg, 0, SVE_PT_SVE_ZREG_SIZE (vq));
+
+ /* Check in the reg_buf if any of the Z registers are set after the
+ first 128 bits, or if any of the other SVE registers are set. */
+
+ for (i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+ {
+ has_sve_state |= reg_buf->raw_compare (AARCH64_SVE_Z0_REGNUM + i,
+ zero_reg, sizeof (__int128_t));
+ if (has_sve_state != 0)
+ break;
+ }
+
+ if (has_sve_state == 0)
+ for (i = 0; i < AARCH64_SVE_P_REGS_NUM; i++)
+ {
+ has_sve_state |= reg_buf->raw_compare (AARCH64_SVE_P0_REGNUM + i,
+ zero_reg, 0);
+ if (has_sve_state != 0)
+ break;
+ }
+
+ if (has_sve_state == 0)
+ has_sve_state |= reg_buf->raw_compare (AARCH64_SVE_FFR_REGNUM,
+ zero_reg, 0);
+
+ /* If no SVE state exists, then use the existing fpsimd structure to
+ write out state and return. */
+
+ if (has_sve_state == 0)
+ {
+ /* The collects of the Z registers will overflow the size of a vreg.
+ There is enough space in the structure to allow for this, but we
+ cannot overflow into the next register as we might not be
+ collecting every register. */
+
+ for (i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+ {
+ if (REG_VALID
+ == reg_buf->get_register_status (AARCH64_SVE_Z0_REGNUM + i))
+ {
+ reg_buf->raw_collect (AARCH64_SVE_Z0_REGNUM + i, zero_reg);
+ memcpy (&fpsimd->vregs[i], zero_reg, sizeof (__int128_t));
+ }
+ }
+
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_FPSR_REGNUM))
+ reg_buf->raw_collect (AARCH64_FPSR_REGNUM, &fpsimd->fpsr);
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_FPCR_REGNUM))
+ reg_buf->raw_collect (AARCH64_FPCR_REGNUM, &fpsimd->fpcr);
+
+ return;
+ }
+
+ /* Otherwise, reformat the fpsimd structure into a full SVE set, by
+ expanding the V registers (working backwards so we don't splat
+ registers before they are copied) and using null for everything else.
+ Note that enough space for a full SVE dump was originally allocated
+ for base. */
+
+ header->flags |= SVE_PT_REGS_SVE;
+ header->size = SVE_PT_SIZE (vq, SVE_PT_REGS_SVE);
+
+ memcpy (base + SVE_PT_SVE_FPSR_OFFSET (vq), &fpsimd->fpsr,
+ sizeof (uint32_t));
+ memcpy (base + SVE_PT_SVE_FPCR_OFFSET (vq), &fpsimd->fpcr,
+ sizeof (uint32_t));
+
+ for (i = AARCH64_SVE_Z_REGS_NUM; i >= 0 ; i--)
+ {
+ memcpy (base + SVE_PT_SVE_ZREG_OFFSET (vq, i), &fpsimd->vregs[i],
+ sizeof (__int128_t));
+ }
+ }
+
+ /* Replace the kernel values with those from reg_buf. */
+
+ for (i = 0; i < AARCH64_SVE_Z_REGS_NUM; i++)
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_SVE_Z0_REGNUM + i))
+ reg_buf->raw_collect (AARCH64_SVE_Z0_REGNUM + i,
+ base + SVE_PT_SVE_ZREG_OFFSET (vq, i));
+
+ for (i = 0; i < AARCH64_SVE_P_REGS_NUM; i++)
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_SVE_P0_REGNUM + i))
+ reg_buf->raw_collect (AARCH64_SVE_P0_REGNUM + i,
+ base + SVE_PT_SVE_PREG_OFFSET (vq, i));
+
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_SVE_FFR_REGNUM))
+ reg_buf->raw_collect (AARCH64_SVE_FFR_REGNUM,
+ base + SVE_PT_SVE_FFR_OFFSET (vq));
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_FPSR_REGNUM))
+ reg_buf->raw_collect (AARCH64_FPSR_REGNUM,
+ base + SVE_PT_SVE_FPSR_OFFSET (vq));
+ if (REG_VALID == reg_buf->get_register_status (AARCH64_FPCR_REGNUM))
+ reg_buf->raw_collect (AARCH64_FPCR_REGNUM,
+ base + SVE_PT_SVE_FPCR_OFFSET (vq));
+}
diff --git a/gdb/nat/aarch64-sve-linux-ptrace.h b/gdb/nat/aarch64-sve-linux-ptrace.h
index 2d6f5714c0..1296fa42d4 100644
--- a/gdb/nat/aarch64-sve-linux-ptrace.h
+++ b/gdb/nat/aarch64-sve-linux-ptrace.h
@@ -33,9 +33,30 @@
#include "aarch64-linux-ptrace.h"
#endif

+/* Indicates whether a SVE ptrace header is followed by SVE registers or a
+ fpsimd structure. */
+
+#define HAS_SVE_STATE(header) ((header).flags && SVE_PT_REGS_SVE)
+
/* Read VQ for the given tid using ptrace. If SVE is not supported then zero
is returned (on a system that supports SVE, then VQ cannot be zero). */

uint64_t aarch64_sve_get_vq (int tid);

+/* Read the current SVE register set using ptrace, allocating space as
+ required. */
+
+extern std::unique_ptr<gdb_byte> aarch64_sve_get_sveregs (int tid);
+
+/* Put the registers from linux structure buf into register buffer. */
+
+extern void aarch64_sve_regs_copy_to_reg_buf (struct reg_buffer_common *reg_buf,
+ const void *buf);
+
+/* Put the registers from register buffer into linux structure buf. */
+
+extern void
+aarch64_sve_regs_copy_from_reg_buf (struct reg_buffer_common *reg_buf,
+ void *buf);
+
#endif /* aarch64-sve-linux-ptrace.h */
--
2.15.1 (Apple Git-101)