diff options
Diffstat (limited to 'gdb/nat')
-rw-r--r-- | gdb/nat/aarch64-sve-linux-ptrace.c | 269 | ||||
-rw-r--r-- | gdb/nat/aarch64-sve-linux-ptrace.h | 21 |
2 files changed, 289 insertions, 1 deletions
diff --git a/gdb/nat/aarch64-sve-linux-ptrace.c b/gdb/nat/aarch64-sve-linux-ptrace.c index 119656b..a2f9261 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,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)); + +} diff --git a/gdb/nat/aarch64-sve-linux-ptrace.h b/gdb/nat/aarch64-sve-linux-ptrace.h index 7f5a81f..5a7186b 100644 --- a/gdb/nat/aarch64-sve-linux-ptrace.h +++ b/gdb/nat/aarch64-sve-linux-ptrace.h @@ -29,9 +29,30 @@ #include "aarch64-sve-linux-sigcontext.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 (const struct reg_buffer_common *reg_buf, + void *buf); + #endif /* aarch64-sve-linux-ptrace.h */ |