[Lldb-commits] [lldb] 5ee2dea - [lldb][AArch64][Linux] Add Floating Point Mode Register (#106695)

via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 25 02:28:00 PDT 2024


Author: David Spickett
Date: 2024-09-25T10:27:57+01:00
New Revision: 5ee2deac0c3b85deaeb0031b4030db99d266abdc

URL: https://github.com/llvm/llvm-project/commit/5ee2deac0c3b85deaeb0031b4030db99d266abdc
DIFF: https://github.com/llvm/llvm-project/commit/5ee2deac0c3b85deaeb0031b4030db99d266abdc.diff

LOG: [lldb][AArch64][Linux] Add Floating Point Mode Register (#106695)

Otherwise known as FEAT_FPMR. This register controls the behaviour of
floating point operations.

https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/FPMR--Floating-point-Mode-Register

As the current floating point register contexts are fixed size, this has
been placed in a new set. Linux kernel patches have landed already, so
you can cross check with those.

To simplify testing we're not going to do any floating point operations,
just read and write from the program and debugger to make sure each sees
the other's values correctly.

Added: 
    lldb/test/API/linux/aarch64/fpmr/Makefile
    lldb/test/API/linux/aarch64/fpmr/TestAArch64LinuxFPMR.py
    lldb/test/API/linux/aarch64/fpmr/main.c

Modified: 
    lldb/packages/Python/lldbsuite/test/lldbtest.py
    lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
    lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
    lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
    lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
    lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
    lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index df5a110cb5b309..c6b7ce84109c09 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1370,6 +1370,9 @@ def isAArch64PAuth(self):
             return True
         return self.isAArch64() and "paca" in self.getCPUInfo()
 
+    def isAArch64FPMR(self):
+        return self.isAArch64() and "fpmr" in self.getCPUInfo()
+
     def isAArch64Windows(self):
         """Returns true if the architecture is AArch64 and platform windows."""
         if self.getPlatform() == "windows":

diff  --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
index 1dd4fd41351333..6056f3001fed6e 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
@@ -60,10 +60,16 @@
 #define NT_ARM_TAGGED_ADDR_CTRL 0x409 /* Tagged address control register */
 #endif
 
+#ifndef NT_ARM_FPMR
+#define NT_ARM_FPMR 0x40e /* Floating point mode register */
+#endif
+
 #define HWCAP_PACA (1 << 30)
 
 #define HWCAP2_MTE (1 << 18)
 
+#define HWCAP2_FPMR (1UL << 48)
+
 using namespace lldb;
 using namespace lldb_private;
 using namespace lldb_private::process_linux;
@@ -139,8 +145,12 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
 
     std::optional<uint64_t> auxv_at_hwcap2 =
         process.GetAuxValue(AuxVector::AUXV_AT_HWCAP2);
-    if (auxv_at_hwcap2 && (*auxv_at_hwcap2 & HWCAP2_MTE))
-      opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE);
+    if (auxv_at_hwcap2) {
+      if (*auxv_at_hwcap2 & HWCAP2_MTE)
+        opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE);
+      if (*auxv_at_hwcap2 & HWCAP2_FPMR)
+        opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR);
+    }
 
     opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
 
@@ -186,6 +196,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   std::fill(m_zt_reg.begin(), m_zt_reg.end(), 0);
 
   m_mte_ctrl_reg = 0;
+  m_fpmr_reg = 0;
 
   // 16 is just a maximum value, query hardware for actual watchpoint count
   m_max_hwp_supported = 16;
@@ -201,6 +212,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   m_mte_ctrl_is_valid = false;
   m_tls_is_valid = false;
   m_zt_buffer_is_valid = false;
+  m_fpmr_is_valid = false;
 
   // SME adds the tpidr2 register
   m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs)
@@ -413,6 +425,14 @@ NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info,
       assert(offset < GetSMEPseudoBufferSize());
       src = (uint8_t *)GetSMEPseudoBuffer() + offset;
     }
+  } else if (IsFPMR(reg)) {
+    error = ReadFPMR();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset();
+    assert(offset < GetFPMRBufferSize());
+    src = (uint8_t *)GetFPMRBuffer() + offset;
   } else
     return Status::FromErrorString(
         "failed - register wasn't recognized to be a GPR or an FPR, "
@@ -626,6 +646,17 @@ Status NativeRegisterContextLinux_arm64::WriteRegister(
     } else
       return Status::FromErrorString(
           "Writing to SVG or SVCR is not supported.");
+  } else if (IsFPMR(reg)) {
+    error = ReadFPMR();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset();
+    assert(offset < GetFPMRBufferSize());
+    dst = (uint8_t *)GetFPMRBuffer() + offset;
+    ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
+
+    return WriteFPMR();
   }
 
   return Status::FromErrorString("Failed to write register value");
@@ -640,6 +671,7 @@ enum RegisterSetType : uint32_t {
   TLS,
   SME,  // ZA only, because SVCR and SVG are pseudo registers.
   SME2, // ZT only.
+  FPMR,
 };
 
 static uint8_t *AddRegisterSetType(uint8_t *dst,
@@ -720,6 +752,13 @@ NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) {
       return error;
   }
 
+  if (GetRegisterInfo().IsFPMRPresent()) {
+    cached_size += sizeof(RegisterSetType) + GetFPMRBufferSize();
+    error = ReadFPMR();
+    if (error.Fail())
+      return error;
+  }
+
   // tpidr is always present but tpidr2 depends on SME.
   cached_size += sizeof(RegisterSetType) + GetTLSBufferSize();
   error = ReadTLS();
@@ -823,6 +862,11 @@ Status NativeRegisterContextLinux_arm64::ReadAllRegisterValues(
                             GetMTEControlSize());
   }
 
+  if (GetRegisterInfo().IsFPMRPresent()) {
+    dst = AddSavedRegisters(dst, RegisterSetType::FPMR, GetFPMRBuffer(),
+                            GetFPMRBufferSize());
+  }
+
   dst = AddSavedRegisters(dst, RegisterSetType::TLS, GetTLSBuffer(),
                           GetTLSBufferSize());
 
@@ -971,6 +1015,11 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
           GetZTBuffer(), &src, GetZTBufferSize(), m_zt_buffer_is_valid,
           std::bind(&NativeRegisterContextLinux_arm64::WriteZT, this));
       break;
+    case RegisterSetType::FPMR:
+      error = RestoreRegisters(
+          GetFPMRBuffer(), &src, GetFPMRBufferSize(), m_fpmr_is_valid,
+          std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
+      break;
     }
 
     if (error.Fail())
@@ -1014,6 +1063,10 @@ bool NativeRegisterContextLinux_arm64::IsTLS(unsigned reg) const {
   return GetRegisterInfo().IsTLSReg(reg);
 }
 
+bool NativeRegisterContextLinux_arm64::IsFPMR(unsigned reg) const {
+  return GetRegisterInfo().IsFPMRReg(reg);
+}
+
 llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() {
   if (!m_refresh_hwdebug_info) {
     return llvm::Error::success();
@@ -1161,6 +1214,7 @@ void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() {
   m_mte_ctrl_is_valid = false;
   m_tls_is_valid = false;
   m_zt_buffer_is_valid = false;
+  m_fpmr_is_valid = false;
 
   // Update SVE and ZA registers in case there is change in configuration.
   ConfigureRegisterContext();
@@ -1440,6 +1494,40 @@ Status NativeRegisterContextLinux_arm64::WriteZT() {
   return WriteRegisterSet(&ioVec, GetZTBufferSize(), NT_ARM_ZT);
 }
 
+Status NativeRegisterContextLinux_arm64::ReadFPMR() {
+  Status error;
+
+  if (m_fpmr_is_valid)
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetFPMRBuffer();
+  ioVec.iov_len = GetFPMRBufferSize();
+
+  error = ReadRegisterSet(&ioVec, GetFPMRBufferSize(), NT_ARM_FPMR);
+
+  if (error.Success())
+    m_fpmr_is_valid = true;
+
+  return error;
+}
+
+Status NativeRegisterContextLinux_arm64::WriteFPMR() {
+  Status error;
+
+  error = ReadFPMR();
+  if (error.Fail())
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetFPMRBuffer();
+  ioVec.iov_len = GetFPMRBufferSize();
+
+  m_fpmr_is_valid = false;
+
+  return WriteRegisterSet(&ioVec, GetFPMRBufferSize(), NT_ARM_FPMR);
+}
+
 void NativeRegisterContextLinux_arm64::ConfigureRegisterContext() {
   // ConfigureRegisterContext gets called from InvalidateAllRegisters
   // on every stop and configures SVE vector length and whether we are in

diff  --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
index 6df7c3beefb824..16190b5492582b 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
@@ -84,6 +84,7 @@ class NativeRegisterContextLinux_arm64
   bool m_sve_buffer_is_valid;
   bool m_mte_ctrl_is_valid;
   bool m_zt_buffer_is_valid;
+  bool m_fpmr_is_valid;
 
   bool m_sve_header_is_valid;
   bool m_za_buffer_is_valid;
@@ -133,6 +134,8 @@ class NativeRegisterContextLinux_arm64
   // SME2's ZT is a 512 bit register.
   std::array<uint8_t, 64> m_zt_reg;
 
+  uint64_t m_fpmr_reg;
+
   bool IsGPR(unsigned reg) const;
 
   bool IsFPR(unsigned reg) const;
@@ -174,11 +177,16 @@ class NativeRegisterContextLinux_arm64
   // SVCR is a pseudo register and we do not allow writes to it.
   Status ReadSMEControl();
 
+  Status ReadFPMR();
+
+  Status WriteFPMR();
+
   bool IsSVE(unsigned reg) const;
   bool IsSME(unsigned reg) const;
   bool IsPAuth(unsigned reg) const;
   bool IsMTE(unsigned reg) const;
   bool IsTLS(unsigned reg) const;
+  bool IsFPMR(unsigned reg) const;
 
   uint64_t GetSVERegVG() { return m_sve_header.vl / 8; }
 
@@ -202,6 +210,8 @@ class NativeRegisterContextLinux_arm64
 
   void *GetSVEBuffer() { return m_sve_ptrace_payload.data(); }
 
+  void *GetFPMRBuffer() { return &m_fpmr_reg; }
+
   size_t GetSVEHeaderSize() { return sizeof(m_sve_header); }
 
   size_t GetPACMaskSize() { return sizeof(m_pac_mask); }
@@ -222,6 +232,8 @@ class NativeRegisterContextLinux_arm64
 
   size_t GetZTBufferSize() { return m_zt_reg.size(); }
 
+  size_t GetFPMRBufferSize() { return sizeof(m_fpmr_reg); }
+
   llvm::Error ReadHardwareDebugInfo() override;
 
   llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override;

diff  --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
index 50e25568f2ae01..575e9c8c81cbf5 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
@@ -59,6 +59,10 @@ bool RegisterContextPOSIX_arm64::IsMTE(unsigned reg) const {
   return m_register_info_up->IsMTEReg(reg);
 }
 
+bool RegisterContextPOSIX_arm64::IsFPMR(unsigned reg) const {
+  return m_register_info_up->IsFPMRReg(reg);
+}
+
 RegisterContextPOSIX_arm64::RegisterContextPOSIX_arm64(
     lldb_private::Thread &thread,
     std::unique_ptr<RegisterInfoPOSIX_arm64> register_info)

diff  --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
index b1226b25b4be10..35ad56c98a7aed 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
@@ -58,6 +58,7 @@ class RegisterContextPOSIX_arm64 : public lldb_private::RegisterContext {
   bool IsTLS(unsigned reg) const;
   bool IsSME(unsigned reg) const;
   bool IsMTE(unsigned reg) const;
+  bool IsFPMR(unsigned reg) const;
 
   bool IsSVEZ(unsigned reg) const { return m_register_info_up->IsSVEZReg(reg); }
   bool IsSVEP(unsigned reg) const { return m_register_info_up->IsSVEPReg(reg); }

diff  --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
index 9f5872e5de7e9f..f51a93e1b2dcbd 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
@@ -94,6 +94,9 @@ static lldb_private::RegisterInfo g_register_infos_sme2[] = {
     {"zt0", nullptr, 64, 0, lldb::eEncodingVector, lldb::eFormatVectorOfUInt8,
      KIND_ALL_INVALID, nullptr, nullptr, nullptr}};
 
+static lldb_private::RegisterInfo g_register_infos_fpmr[] = {
+    DEFINE_EXTENSION_REG(fpmr)};
+
 // Number of register sets provided by this context.
 enum {
   k_num_gpr_registers = gpr_w28 - gpr_x0 + 1,
@@ -105,6 +108,7 @@ enum {
   // SME2's ZT0 will also be added to this set if present. So this number is
   // only for SME1 registers.
   k_num_sme_register = 3,
+  k_num_fpmr_register = 1,
   k_num_register_sets_default = 2,
   k_num_register_sets = 3
 };
@@ -214,6 +218,9 @@ static const lldb_private::RegisterSet g_reg_set_mte_arm64 = {
 static const lldb_private::RegisterSet g_reg_set_sme_arm64 = {
     "Scalable Matrix Extension Registers", "sme", k_num_sme_register, nullptr};
 
+static const lldb_private::RegisterSet g_reg_set_fpmr_arm64 = {
+    "Floating Point Mode Register", "fpmr", k_num_fpmr_register, nullptr};
+
 RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
     const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets)
     : lldb_private::RegisterInfoAndSetInterface(target_arch),
@@ -263,6 +270,9 @@ RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
       if (m_opt_regsets.AnySet(eRegsetMaskSSVE))
         AddRegSetSME(m_opt_regsets.AnySet(eRegsetMaskZT));
 
+      if (m_opt_regsets.AllSet(eRegsetMaskFPMR))
+        AddRegSetFPMR();
+
       m_register_info_count = m_dynamic_reg_infos.size();
       m_register_info_p = m_dynamic_reg_infos.data();
       m_register_set_p = m_dynamic_reg_sets.data();
@@ -409,6 +419,21 @@ void RegisterInfoPOSIX_arm64::AddRegSetSME(bool has_zt) {
   m_dynamic_reg_infos[GetRegNumSVEVG()].invalidate_regs = vg_invalidates;
 }
 
+void RegisterInfoPOSIX_arm64::AddRegSetFPMR() {
+  uint32_t fpmr_regnum = m_dynamic_reg_infos.size();
+  m_fpmr_regnum_collection.push_back(fpmr_regnum);
+  m_dynamic_reg_infos.push_back(g_register_infos_fpmr[0]);
+  m_dynamic_reg_infos[fpmr_regnum].byte_offset =
+      m_dynamic_reg_infos[fpmr_regnum - 1].byte_offset +
+      m_dynamic_reg_infos[fpmr_regnum - 1].byte_size;
+  m_dynamic_reg_infos[fpmr_regnum].kinds[lldb::eRegisterKindLLDB] = fpmr_regnum;
+
+  m_per_regset_regnum_range[m_register_set_count] =
+      std::make_pair(fpmr_regnum, fpmr_regnum + 1);
+  m_dynamic_reg_sets.push_back(g_reg_set_fpmr_arm64);
+  m_dynamic_reg_sets.back().registers = m_fpmr_regnum_collection.data();
+}
+
 uint32_t RegisterInfoPOSIX_arm64::ConfigureVectorLengthSVE(uint32_t sve_vq) {
   // sve_vq contains SVE Quad vector length in context of AArch64 SVE.
   // SVE register infos if enabled cannot be disabled by selecting sve_vq = 0.
@@ -532,6 +557,10 @@ bool RegisterInfoPOSIX_arm64::IsSMEReg(unsigned reg) const {
   return llvm::is_contained(m_sme_regnum_collection, reg);
 }
 
+bool RegisterInfoPOSIX_arm64::IsFPMRReg(unsigned reg) const {
+  return llvm::is_contained(m_fpmr_regnum_collection, reg);
+}
+
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; }
 
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; }
@@ -561,3 +590,7 @@ uint32_t RegisterInfoPOSIX_arm64::GetTLSOffset() const {
 uint32_t RegisterInfoPOSIX_arm64::GetSMEOffset() const {
   return m_register_info_p[m_sme_regnum_collection[0]].byte_offset;
 }
+
+uint32_t RegisterInfoPOSIX_arm64::GetFPMROffset() const {
+  return m_register_info_p[m_fpmr_regnum_collection[0]].byte_offset;
+}
\ No newline at end of file

diff  --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
index 3b8171042c7326..16a951ef0935f0 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
@@ -32,6 +32,7 @@ class RegisterInfoPOSIX_arm64
     eRegsetMaskTLS = 16,
     eRegsetMaskZA = 32,
     eRegsetMaskZT = 64,
+    eRegsetMaskFPMR = 128,
     eRegsetMaskDynamic = ~1,
   };
 
@@ -110,6 +111,8 @@ class RegisterInfoPOSIX_arm64
 
   void AddRegSetSME(bool has_zt);
 
+  void AddRegSetFPMR();
+
   uint32_t ConfigureVectorLengthSVE(uint32_t sve_vq);
 
   void ConfigureVectorLengthZA(uint32_t za_vq);
@@ -128,6 +131,7 @@ class RegisterInfoPOSIX_arm64
   bool IsPAuthPresent() const { return m_opt_regsets.AnySet(eRegsetMaskPAuth); }
   bool IsMTEPresent() const { return m_opt_regsets.AnySet(eRegsetMaskMTE); }
   bool IsTLSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskTLS); }
+  bool IsFPMRPresent() const { return m_opt_regsets.AnySet(eRegsetMaskFPMR); }
 
   bool IsSVEReg(unsigned reg) const;
   bool IsSVEZReg(unsigned reg) const;
@@ -139,6 +143,7 @@ class RegisterInfoPOSIX_arm64
   bool IsSMEReg(unsigned reg) const;
   bool IsSMERegZA(unsigned reg) const;
   bool IsSMERegZT(unsigned reg) const;
+  bool IsFPMRReg(unsigned reg) const;
 
   uint32_t GetRegNumSVEZ0() const;
   uint32_t GetRegNumSVEFFR() const;
@@ -150,6 +155,7 @@ class RegisterInfoPOSIX_arm64
   uint32_t GetMTEOffset() const;
   uint32_t GetTLSOffset() const;
   uint32_t GetSMEOffset() const;
+  uint32_t GetFPMROffset() const;
 
 private:
   typedef std::map<uint32_t, std::vector<lldb_private::RegisterInfo>>
@@ -181,6 +187,7 @@ class RegisterInfoPOSIX_arm64
   std::vector<uint32_t> m_mte_regnum_collection;
   std::vector<uint32_t> m_tls_regnum_collection;
   std::vector<uint32_t> m_sme_regnum_collection;
+  std::vector<uint32_t> m_fpmr_regnum_collection;
 };
 
 #endif

diff  --git a/lldb/test/API/linux/aarch64/fpmr/Makefile b/lldb/test/API/linux/aarch64/fpmr/Makefile
new file mode 100644
index 00000000000000..10495940055b63
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/fpmr/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/linux/aarch64/fpmr/TestAArch64LinuxFPMR.py b/lldb/test/API/linux/aarch64/fpmr/TestAArch64LinuxFPMR.py
new file mode 100644
index 00000000000000..5a3b8f501095e9
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/fpmr/TestAArch64LinuxFPMR.py
@@ -0,0 +1,58 @@
+"""
+Test lldb's ability to read and write the AArch64 FPMR register.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class AArch64LinuxFPMR(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessArch("aarch64")
+    @skipUnlessPlatform(["linux"])
+    def test_fpmr_register(self):
+        if not self.isAArch64FPMR():
+            self.skipTest("FPMR must be present.")
+
+        self.build()
+        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
+
+        lldbutil.run_break_set_by_file_and_line(
+            self,
+            "main.c",
+            line_number("main.c", "// Set break point at this line."),
+            num_expected_locations=1,
+        )
+
+        self.runCmd("run", RUN_SUCCEEDED)
+
+        if self.process().GetState() == lldb.eStateExited:
+            self.fail("Test program failed to run.")
+
+        self.expect(
+            "thread list",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stopped", "stop reason = breakpoint"],
+        )
+
+        # This has been set by the program.
+        expected_fpmr = (0b101010 << 32) | 0b101
+        self.expect(
+            "register read --all",
+            substrs=["Floating Point Mode Register", f"fpmr = {expected_fpmr:#018x}"],
+        )
+
+        # Write a value for the program to find. Same fields but with bit values
+        # inverted.
+        new_fpmr = (0b010101 << 32) | 0b010
+        self.runCmd(f"register write fpmr {new_fpmr:#x}")
+
+        # This value should be saved and restored after expressions.
+        self.runCmd("p expr_func()")
+        self.expect("register read fpmr", substrs=[f"fpmr = {new_fpmr:#018x}"])
+
+        # 0 means the program found the new value in the sysreg as expected.
+        self.expect("continue", substrs=["exited with status = 0"])

diff  --git a/lldb/test/API/linux/aarch64/fpmr/main.c b/lldb/test/API/linux/aarch64/fpmr/main.c
new file mode 100644
index 00000000000000..bdb7d8f40b64dd
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/fpmr/main.c
@@ -0,0 +1,41 @@
+#include <asm/hwcap.h>
+#include <stdint.h>
+#include <sys/auxv.h>
+
+#ifndef HWCAP2_FPMR
+#define HWCAP2_FPMR (1UL << 48)
+#endif
+
+uint64_t get_fpmr(void) {
+  uint64_t fpmr = 0;
+  __asm__ volatile("mrs %0, s3_3_c4_c4_2" : "=r"(fpmr));
+  return fpmr;
+}
+
+void set_fpmr(uint64_t value) {
+  __asm__ volatile("msr s3_3_c4_c4_2, %0" ::"r"(value));
+}
+
+// Set F8S1 (bits 0-2) and LSCALE2 (bits 37-32) (to prove we treat fpmr as 64
+// bit).
+const uint64_t original_fpmr = (uint64_t)0b101010 << 32 | (uint64_t)0b101;
+
+void expr_func() { set_fpmr(original_fpmr); }
+
+int main(int argc, char *argv[]) {
+  if (!(getauxval(AT_HWCAP2) & HWCAP2_FPMR))
+    return 1;
+
+  // As FPMR controls a bunch of floating point options that are quite
+  // extensive, we're not going to run any floating point ops here. Instead just
+  // update the value from the debugger and check it from this program, and vice
+  // versa.
+  set_fpmr(original_fpmr);
+
+  // Here the debugger checks it read back the value above, then writes in a new
+  // value. Note that the bits are flipped in the new value.
+  uint64_t new_fpmr = get_fpmr(); // Set break point at this line.
+  uint64_t expected_fpmr = ((uint64_t)0b010101 << 32) | (uint64_t)0b010;
+
+  return new_fpmr == expected_fpmr ? 0 : 1;
+}


        


More information about the lldb-commits mailing list