Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h =================================================================== --- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -14,11 +14,25 @@ #include "Plugins/Process/Linux/NativeRegisterContextLinux.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" +#include + +#ifndef SVE_PT_REGS_SVE +#define INCLUDE_LINUX_PTRACE_DEFINITIONS_FOR_SVE_ARM64 +#include "Plugins/Process/Linux/LinuxPTraceDefines_arm64sve.h" +#endif + namespace lldb_private { namespace process_linux { class NativeProcessLinux; +enum class SVE_STATE { + SVE_STATE_UNKNOWN, + SVE_STATE_DISABLED, + SVE_STATE_FPSIMD, + SVE_STATE_FULL +}; + class NativeRegisterContextLinux_arm64 : public NativeRegisterContextLinux { public: NativeRegisterContextLinux_arm64(const ArchSpec &target_arch, @@ -97,11 +111,19 @@ private: bool m_gpr_is_valid; bool m_fpu_is_valid; + bool m_sve_buffer_is_valid; + + bool m_sve_header_is_valid; RegisterInfoPOSIX_arm64::GPR m_gpr_arm64; // 64-bit general purpose registers. RegisterInfoPOSIX_arm64::FPU m_fpr; // floating-point registers including extended register sets. + + mutable SVE_STATE m_sve_state; + struct user_sve_header m_sve_header; + std::vector m_sve_ptrace_payload; + // Debug register info for hardware breakpoints and watchpoints management. struct DREG { lldb::addr_t address; // Breakpoint/watchpoint address value. @@ -123,6 +145,28 @@ bool IsFPR(unsigned reg) const; + Status ReadAllSVE(); + + Status WriteAllSVE(); + + Status ReadSVEHeader(); + + Status WriteSVEHeader(); + + bool IsSVE(unsigned reg) const; + + uint64_t GetSVERegVG() { return m_sve_header.vl / 8; } + + void SetSVERegVG(uint64_t vg) { m_sve_header.vl = vg * 8; } + + void *GetSVEHeader() { return &m_sve_header; } + + void *GetSVEBuffer(); + + size_t GetSVEHeaderSize() { return sizeof(m_sve_header); } + + size_t GetSVEBufferSize() { return m_sve_ptrace_payload.size(); } + Status ReadHardwareDebugInfo(); Status WriteHardwareDebugRegs(int hwbType); @@ -130,6 +174,10 @@ uint32_t CalculateFprOffset(const RegisterInfo *reg_info) const; RegisterInfoPOSIX_arm64 &GetRegisterInfo() const; + + void ConfigureRegisterContext(); + + uint32_t CalculateSVEOffset(uint32_t reg_num) const; }; } // namespace process_linux Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp =================================================================== --- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -21,14 +21,18 @@ #include "Plugins/Process/Linux/NativeProcessLinux.h" #include "Plugins/Process/Linux/Procfs.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" +#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" +#include "Plugins/Process/Utility/lldb-arm64-register-enums.h" // System includes - They have to be included after framework includes because // they define some macros which collide with variable names in other modules #include // NT_PRSTATUS and NT_FPREGSET definition #include -// user_hwdebug_state definition -#include + +#ifndef NT_ARM_SVE +#define NT_ARM_SVE 0x405 /* ARM Scalable Vector Extension */ +#endif #define REG_CONTEXT_SIZE (GetGPRSize() + GetFPRSize()) @@ -59,14 +63,21 @@ ::memset(&m_gpr_arm64, 0, sizeof(m_gpr_arm64)); ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs)); ::memset(&m_hbr_regs, 0, sizeof(m_hbr_regs)); + ::memset(&m_sve_header, 0, sizeof(m_sve_header)); // 16 is just a maximum value, query hardware for actual watchpoint count m_max_hwp_supported = 16; m_max_hbp_supported = 16; + m_refresh_hwdebug_info = true; m_gpr_is_valid = false; m_fpu_is_valid = false; + m_sve_buffer_is_valid = false; + m_sve_header_is_valid = false; + + // SVE is not enabled until we query user_sve_header + m_sve_state = SVE_STATE::SVE_STATE_UNKNOWN; } RegisterInfoPOSIX_arm64 & @@ -107,8 +118,13 @@ ? reg_info->name : ""); + // Update SVE registers in case there is change in configuration + ConfigureRegisterContext(); + uint8_t *src; uint32_t offset; + uint64_t sve_vg; + std::vector sve_reg_non_live; if (IsGPR(reg)) { if (!m_gpr_is_valid) { @@ -122,15 +138,70 @@ src = (uint8_t *)GetGPRBuffer() + offset; } else if (IsFPR(reg)) { - if (!m_fpu_is_valid) { + if (m_sve_state == SVE_STATE::SVE_STATE_DISABLED) { + // SVE is disabled take legacy route for FPU register access + if (!m_fpu_is_valid) { - error = ReadFPR(); - if (error.Fail()) - return error; + error = ReadFPR(); + if (error.Fail()) + return error; + } + offset = CalculateFprOffset(reg_info); + assert(offset < GetFPRSize()); + src = (uint8_t *)GetFPRBuffer() + offset; + } else { + // SVE enabled, we will read and cache SVE ptrace data + if (!m_sve_buffer_is_valid) { + error = ReadAllSVE(); + if (error.Fail()) + return error; + } + // Extract SVE Z register value register number for this reg_info + uint32_t sve_reg_num = LLDB_INVALID_REGNUM; + if (reg_info->value_regs && + reg_info->value_regs[0] != LLDB_INVALID_REGNUM) + sve_reg_num = reg_info->value_regs[0]; + else if (reg == fpu_fpcr_arm64 || reg == fpu_fpsr_arm64) + sve_reg_num = reg; + if (sve_reg_num != LLDB_INVALID_REGNUM) { + offset = CalculateSVEOffset(sve_reg_num); + assert(offset < GetSVEBufferSize()); + src = (uint8_t *)GetSVEBuffer() + offset; + } + } + } else if (GetRegisterInfo().IsSVERegVG(reg)) { + + sve_vg = GetSVERegVG(); + src = (uint8_t *)&sve_vg; + + } else if (IsSVE(reg)) { + if (m_sve_state == SVE_STATE::SVE_STATE_DISABLED) { + return Status("SVE disabled or not supported"); + } else { + // SVE enabled, we will read and cache SVE ptrace data + if (!m_sve_buffer_is_valid) { + error = ReadAllSVE(); + if (error.Fail()) + return error; + } + if (m_sve_state == SVE_STATE::SVE_STATE_FPSIMD) { + sve_reg_non_live.resize(reg_info->byte_size, 0); + // In FPSIMD state SVE payload mirrors legacy fpsimd struct and so just + // copy 16 bytes of v register to the start of z register. All other + // SVE register will be set to zero. + if (GetRegisterInfo().IsSVEZReg(reg)) { + offset = CalculateSVEOffset(reg); + assert(offset < GetSVEBufferSize()); + ::memcpy(sve_reg_non_live.data(), (uint8_t *)GetSVEBuffer() + offset, + 16); + } + src = sve_reg_non_live.data(); + } else if (m_sve_state == SVE_STATE::SVE_STATE_FULL) { + offset = CalculateSVEOffset(reg); + assert(offset < GetSVEBufferSize()); + src = (uint8_t *)GetSVEBuffer() + offset; + } } - offset = CalculateFprOffset(reg_info); - assert(offset < GetFPRSize()); - src = (uint8_t *)GetFPRBuffer() + offset; } else return Status("failed - register wasn't recognized to be a GPR or an FPR, " "write strategy unknown"); @@ -155,8 +226,12 @@ ? reg_info->name : ""); + // Update SVE registers in case there is change in configuration + ConfigureRegisterContext(); + uint8_t *dst; uint32_t offset; + std::vector sve_reg_non_live; if (IsGPR(reg)) { if (!m_gpr_is_valid) { @@ -173,20 +248,96 @@ return WriteGPR(); } else if (IsFPR(reg)) { - if (!m_fpu_is_valid) { - error = ReadFPR(); - if (error.Fail()) - return error; + if (m_sve_state == SVE_STATE::SVE_STATE_DISABLED) { + // SVE is disabled take legacy route for FPU register access + if (!m_fpu_is_valid) { + error = ReadFPR(); + if (error.Fail()) + return error; + } + offset = CalculateFprOffset(reg_info); + assert(offset < GetFPRSize()); + dst = (uint8_t *)GetFPRBuffer() + offset; + + ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + + return WriteFPR(); + } else { + // SVE enabled, we will read and cache SVE ptrace data + if (!m_sve_buffer_is_valid) { + error = ReadAllSVE(); + if (error.Fail()) + return error; + } + // Extract SVE Z register value register number for this reg_info + uint32_t sve_reg_num = LLDB_INVALID_REGNUM; + if (reg_info->value_regs && + reg_info->value_regs[0] != LLDB_INVALID_REGNUM) + sve_reg_num = reg_info->value_regs[0]; + else if (reg == fpu_fpcr_arm64 || reg == fpu_fpsr_arm64) + sve_reg_num = reg; + if (sve_reg_num != LLDB_INVALID_REGNUM) { + offset = CalculateSVEOffset(sve_reg_num); + assert(offset < GetSVEBufferSize()); + dst = (uint8_t *)GetSVEBuffer() + offset; + ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + return WriteAllSVE(); + } } - offset = CalculateFprOffset(reg_info); - assert(offset < GetFPRSize()); - dst = (uint8_t *)GetFPRBuffer() + offset; - - ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + } else if (GetRegisterInfo().IsSVERegVG(reg)) { + return Status("SVE state change operation not supported"); + } else if (IsSVE(reg)) { + if (m_sve_state == SVE_STATE::SVE_STATE_DISABLED) { + return Status("SVE disabled or not supported"); + } else { + // Target has SVE enabled, we will read and cache SVE ptrace data + if (!m_sve_buffer_is_valid) { + error = ReadAllSVE(); + if (error.Fail()) + return error; + } - return WriteFPR(); + // If target supports SVE but currently in FPSIMD mode. + if (m_sve_state == SVE_STATE::SVE_STATE_FPSIMD) { + // Here we will check if writing this SVE register enables + // SVE_STATE_FULL + bool set_sve_state_full = false; + const uint8_t *reg_bytes = (const uint8_t *)reg_value.GetBytes(); + if (GetRegisterInfo().IsSVEZReg(reg)) { + for (uint32_t i = 16; i < reg_info->byte_size; i++) { + if (reg_bytes[i]) { + set_sve_state_full = true; + break; + } + } + } else if (GetRegisterInfo().IsSVEPReg(reg) || reg == sve_ffr_arm64) { + for (uint32_t i = 0; i < reg_info->byte_size; i++) { + if (reg_bytes[i]) { + set_sve_state_full = true; + break; + } + } + } + if (set_sve_state_full) { + return Status("SVE state change operation not supported"); + } else if (!set_sve_state_full && GetRegisterInfo().IsSVEZReg(reg)) { + // We are writing a Z register which is zero beyond 16 bytes so copy + // first 16 bytes only as SVE payload mirrors legacy fpsimd structure + offset = CalculateSVEOffset(reg); + assert(offset < GetSVEBufferSize()); + dst = (uint8_t *)GetSVEBuffer() + offset; + ::memcpy(dst, reg_value.GetBytes(), 16); + return WriteAllSVE(); + } + } else if (m_sve_state == SVE_STATE::SVE_STATE_FULL) { + offset = CalculateSVEOffset(reg); + assert(offset < GetSVEBufferSize()); + dst = (uint8_t *)GetSVEBuffer() + offset; + ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + return WriteAllSVE(); + } + } } - return error; } @@ -271,6 +422,13 @@ return false; } +bool NativeRegisterContextLinux_arm64::IsSVE(unsigned reg) const { + if (GetRegisterInfo().GetRegisterSetFromRegisterIndex(reg) == + RegisterInfoPOSIX_arm64::SVERegSet) + return true; + return false; +} + uint32_t NativeRegisterContextLinux_arm64::NumSupportedHardwareBreakpoints() { Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); @@ -816,6 +974,92 @@ void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() { m_gpr_is_valid = false; m_fpu_is_valid = false; + m_sve_buffer_is_valid = false; + m_sve_header_is_valid = false; +} + +Status NativeRegisterContextLinux_arm64::ReadSVEHeader() { + Status error; + + struct iovec ioVec; + + ioVec.iov_base = GetSVEHeader(); + ioVec.iov_len = GetSVEHeaderSize(); + + error = ReadRegisterSet(&ioVec, GetSVEHeaderSize(), NT_ARM_SVE); + + m_sve_header_is_valid = true; + + return error; +} + +Status NativeRegisterContextLinux_arm64::WriteSVEHeader() { + Status error; + + struct iovec ioVec; + + ioVec.iov_base = GetSVEHeader(); + ioVec.iov_len = GetSVEHeaderSize(); + + m_sve_buffer_is_valid = false; + m_sve_header_is_valid = false; + m_fpu_is_valid = false; + + return WriteRegisterSet(&ioVec, GetSVEHeaderSize(), NT_ARM_SVE); +} + +Status NativeRegisterContextLinux_arm64::ReadAllSVE() { + Status error; + + struct iovec ioVec; + + ioVec.iov_base = GetSVEBuffer(); + ioVec.iov_len = GetSVEBufferSize(); + + error = ReadRegisterSet(&ioVec, GetSVEBufferSize(), NT_ARM_SVE); + + if (error.Success()) + m_sve_buffer_is_valid = true; + + return error; +} + +Status NativeRegisterContextLinux_arm64::WriteAllSVE() { + Status error; + + struct iovec ioVec; + + ioVec.iov_base = GetSVEBuffer(); + ioVec.iov_len = GetSVEBufferSize(); + + m_sve_buffer_is_valid = false; + m_sve_header_is_valid = false; + m_fpu_is_valid = false; + + return WriteRegisterSet(&ioVec, GetSVEBufferSize(), NT_ARM_SVE); +} + +void NativeRegisterContextLinux_arm64::ConfigureRegisterContext() { + // Read SVE configuration data and configure register infos. + if (!m_sve_header_is_valid && m_sve_state != SVE_STATE::SVE_STATE_DISABLED) { + Status error = ReadSVEHeader(); + if (!error.Success() && m_sve_state == SVE_STATE::SVE_STATE_UNKNOWN) { + m_sve_state = SVE_STATE::SVE_STATE_DISABLED; + GetRegisterInfo().ConfigureVectorRegisterInfos( + RegisterInfoPOSIX_arm64::eVectorQuadwordAArch64); + } else { + if ((m_sve_header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD) + m_sve_state = SVE_STATE::SVE_STATE_FPSIMD; + else if ((m_sve_header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_SVE) + m_sve_state = SVE_STATE::SVE_STATE_FULL; + + uint32_t vq = RegisterInfoPOSIX_arm64::eVectorQuadwordAArch64SVE; + if (sve_vl_valid(m_sve_header.vl)) + vq = sve_vq_from_vl(m_sve_header.vl); + GetRegisterInfo().ConfigureVectorRegisterInfos(vq); + m_sve_ptrace_payload.resize(SVE_PT_SIZE(vq, SVE_PT_REGS_SVE)); + } + } } uint32_t NativeRegisterContextLinux_arm64::CalculateFprOffset( @@ -823,4 +1067,36 @@ return reg_info->byte_offset - GetGPRSize(); } +uint32_t +NativeRegisterContextLinux_arm64::CalculateSVEOffset(uint32_t reg_num) const { + if (m_sve_state == SVE_STATE::SVE_STATE_FPSIMD) { + if (GetRegisterInfo().IsSVEZReg(reg_num)) + return SVE_PT_FPSIMD_OFFSET + ((reg_num - sve_z0_arm64) * 16); + else if (reg_num == fpu_fpsr_arm64) + return SVE_PT_FPSIMD_OFFSET + (32 * 16); + else if (reg_num == fpu_fpcr_arm64) + return SVE_PT_FPSIMD_OFFSET + ((32 * 16) + 4); + } else if (m_sve_state == SVE_STATE::SVE_STATE_FULL) { + size_t vq = sve_vq_from_vl(m_sve_header.vl); + if (GetRegisterInfo().IsSVEZReg(reg_num)) + return SVE_PT_SVE_ZREG_OFFSET(vq, reg_num - sve_z0_arm64); + else if (GetRegisterInfo().IsSVEPReg(reg_num)) + return SVE_PT_SVE_PREG_OFFSET(vq, reg_num - sve_p0_arm64); + else if (reg_num == sve_ffr_arm64) + return SVE_PT_SVE_FFR_OFFSET(vq); + else if (reg_num == fpu_fpsr_arm64) + return SVE_PT_SVE_FPSR_OFFSET(vq); + else if (reg_num == fpu_fpcr_arm64) + return SVE_PT_SVE_FPCR_OFFSET(vq); + } + return 0; +} + +void *NativeRegisterContextLinux_arm64::GetSVEBuffer() { + if (m_sve_state == SVE_STATE::SVE_STATE_FPSIMD) + return m_sve_ptrace_payload.data() + SVE_PT_FPSIMD_OFFSET; + + return m_sve_ptrace_payload.data(); +} + #endif // defined (__arm64__) || defined (__aarch64__) Index: lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/Makefile =================================================================== --- /dev/null +++ lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/Makefile @@ -0,0 +1,5 @@ +C_SOURCES := main.c + +CFLAGS_EXTRAS := -march=armv8-a+sve + +include Makefile.rules Index: lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/TestSVERegisters.py =================================================================== --- /dev/null +++ lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/TestSVERegisters.py @@ -0,0 +1,142 @@ +""" +Test the AArch64 SVE registers. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class RegisterCommandsTestCase(TestBase): + + def check_sve_register_size(self, set, name, expected): + reg_value = set.GetChildMemberWithName(name) + self.assertTrue(reg_value.IsValid(), + 'Verify we have a register named "%s"' % (name)) + self.assertEqual(reg_value.GetByteSize(), expected, + 'Verify "%s" == %i' % (name, expected)) + + mydir = TestBase.compute_mydir(__file__) + + @skipIf + def test_sve_registers_configuration(self): + """Test AArch64 SVE registers size configuration.""" + self.build() + self.line = line_number('main.c', '// Set a break point here.') + + exe = self.getBuildArtifact("a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line( + self, "main.c", self.line, num_expected_locations=1) + self.runCmd("run", RUN_SUCCEEDED) + + self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT, + substrs=["stop reason = breakpoint 1."]) + + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + thread = process.GetThreadAtIndex(0) + currentFrame = thread.GetFrameAtIndex(0) + + has_sve = False + for registerSet in currentFrame.GetRegisters(): + if 'Scalable Vector Extension Registers' in registerSet.GetName(): + has_sve = True + + if not has_sve: + self.skipTest('SVE registers must be supported.') + + registerSets = process.GetThreadAtIndex( + 0).GetFrameAtIndex(0).GetRegisters() + + sve_registers = registerSets.GetValueAtIndex(2) + + vg_reg = sve_registers.GetChildMemberWithName("vg") + + vg_reg_value = sve_registers.GetChildMemberWithName( + "vg").GetValueAsUnsigned() + + z_reg_size = vg_reg_value * 8 + + p_reg_size = z_reg_size / 8 + + for i in range(32): + self.check_sve_register_size( + sve_registers, 'z%i' % (i), z_reg_size) + + for i in range(16): + self.check_sve_register_size( + sve_registers, 'p%i' % (i), p_reg_size) + + self.check_sve_register_size(sve_registers, 'ffr', p_reg_size) + + mydir = TestBase.compute_mydir(__file__) + @no_debug_info_test + @skipIf + def test_sve_registers_read_write(self): + """Test AArch64 SVE registers read and write.""" + self.build() + self.line = line_number('main.c', '// Set a break point here.') + + exe = self.getBuildArtifact("a.out") + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line( + self, "main.c", self.line, num_expected_locations=1) + self.runCmd("run", RUN_SUCCEEDED) + + self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT, + substrs=["stop reason = breakpoint 1."]) + + target = self.dbg.GetSelectedTarget() + process = target.GetProcess() + thread = process.GetThreadAtIndex(0) + currentFrame = thread.GetFrameAtIndex(0) + + has_sve = False + for registerSet in currentFrame.GetRegisters(): + if 'Scalable Vector Extension Registers' in registerSet.GetName(): + has_sve = True + + if not has_sve: + self.skipTest('SVE registers must be supported.') + + registerSets = process.GetThreadAtIndex( + 0).GetFrameAtIndex(0).GetRegisters() + + sve_registers = registerSets.GetValueAtIndex(2) + + vg_reg = sve_registers.GetChildMemberWithName("vg") + + vg_reg_value = sve_registers.GetChildMemberWithName( + "vg").GetValueAsUnsigned() + + z_reg_size = vg_reg_value * 8 + + p_reg_size = int(z_reg_size / 8) + + z_regs_value = '{' + \ + ' '.join(('0x9d' for _ in range(z_reg_size))) + '}' + + p_regs_value = '{' + \ + ' '.join(('0xee' for _ in range(p_reg_size))) + '}' + + for i in range(32): + self.runCmd('register write ' + 'z%i' % + (i) + " '" + z_regs_value + "'") + + for i in range(32): + self.expect("register read " + 'z%i' % (i), substrs=[z_regs_value]) + + for i in range(16): + self.runCmd('register write ' + 'p%i' % + (i) + " '" + p_regs_value + "'") + + for i in range(16): + self.expect("register read " + 'p%i' % (i), substrs=[p_regs_value]) + + self.runCmd('register write ' + 'ffr ' + "'" + p_regs_value + "'") + + self.expect("register read " + 'ffr', substrs=[p_regs_value]) Index: lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/main.c =================================================================== --- /dev/null +++ lldb/test/API/commands/register/register/aarch64_sve_registers/rw_access_static_config/main.c @@ -0,0 +1,5 @@ +int main() { + asm volatile("ptrue p0.s\n\t"); + asm volatile("fcpy z0.s, p0/m, #5.00000000\n\t"); + return 0; // Set a break point here. +} \ No newline at end of file