Ptrace support for Aarch64 SVE
[deliverable/binutils-gdb.git] / gdb / nat / aarch64-sve-linux-ptrace.c
index 3a1dbae7099552907b71cb4efe8feb0e032dde49..a2f9261ba6f8ad9aaaacd165d28dce6cb5e3e544 100644 (file)
 #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.  */
 
-unsigned long
+uint64_t
 aarch64_sve_get_vq (int tid)
 {
   struct iovec iovec;
@@ -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,266 @@ 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;
+  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 (int 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 (int 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 (int 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 (int 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 (const 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;
+
+  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.  */
+
+      bool has_sve_state = false;
+      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 (int 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)
+           break;
+       }
+
+      if (!has_sve_state)
+       for (int 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)
+             break;
+         }
+
+      if (!has_sve_state)
+         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)
+       {
+         /* 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 (int 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 (int 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 (int 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 (int 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));
+
+}
This page took 0.025896 seconds and 4 git commands to generate.