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

David Spickett via lldb-commits lldb-commits at lists.llvm.org
Fri Aug 30 02:30:21 PDT 2024


https://github.com/DavidSpickett created https://github.com/llvm/llvm-project/pull/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.

>From 32303b41a52db204cab1c0f8a5ab3496a1244674 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at linaro.org>
Date: Thu, 29 Aug 2024 09:41:39 +0100
Subject: [PATCH] [lldb][AArch64][Linux] Add Floating Point Mode Register

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.
---
 .../Python/lldbsuite/test/lldbtest.py         |  3 +
 .../NativeRegisterContextLinux_arm64.cpp      | 92 ++++++++++++++++++-
 .../Linux/NativeRegisterContextLinux_arm64.h  | 12 +++
 .../Utility/RegisterContextPOSIX_arm64.cpp    |  4 +
 .../Utility/RegisterContextPOSIX_arm64.h      |  1 +
 .../Utility/RegisterInfoPOSIX_arm64.cpp       | 33 +++++++
 .../Process/Utility/RegisterInfoPOSIX_arm64.h |  7 ++
 lldb/test/API/linux/aarch64/fpmr/Makefile     |  3 +
 .../aarch64/fpmr/TestAArch64LinuxFPMR.py      | 58 ++++++++++++
 lldb/test/API/linux/aarch64/fpmr/main.c       | 41 +++++++++
 10 files changed, 252 insertions(+), 2 deletions(-)
 create mode 100644 lldb/test/API/linux/aarch64/fpmr/Makefile
 create mode 100644 lldb/test/API/linux/aarch64/fpmr/TestAArch64LinuxFPMR.py
 create mode 100644 lldb/test/API/linux/aarch64/fpmr/main.c

diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index b57c3bdd87c83c..0d78574da18d79 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1360,6 +1360,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 054b7d9b2ec575..6bd3f029c595f3 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),
@@ -261,6 +268,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();
@@ -407,6 +417,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.
@@ -530,6 +555,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; }
@@ -559,3 +588,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