[Lldb-commits] [lldb] 02c6002 - [lldb][AArch64] Add Guarded Control Stack registers (#123720)

via lldb-commits lldb-commits at lists.llvm.org
Fri Jan 24 05:42:10 PST 2025


Author: David Spickett
Date: 2025-01-24T13:42:06Z
New Revision: 02c6002d1cd2dabe4b98368f91e7b4395e5ab11d

URL: https://github.com/llvm/llvm-project/commit/02c6002d1cd2dabe4b98368f91e7b4395e5ab11d
DIFF: https://github.com/llvm/llvm-project/commit/02c6002d1cd2dabe4b98368f91e7b4395e5ab11d.diff

LOG: [lldb][AArch64] Add Guarded Control Stack registers (#123720)

The Guarded Control Stack extension implements a shadow stack and the
Linux kernel provides access to 3 registers for it via ptrace.

struct user_gcs {
	__u64 features_enabled;
	__u64 features_locked;
	__u64 gcspr_el0;
};

This commit adds support for reading those from a live process.

The first 2 are pseudo registers based on the real control register and
the 3rd is a real register. This is the stack pointer for the guarded
stack.

I have added a "gcs_" prefix to the "features" registers so that they
have a clear name when shown individually. Also this means they will tab
complete from "gcs", and be next to gcspr_el0 in any sorted lists of
registers.

Guarded Control Stack Registers:
  gcs_features_enabled = 0x0000000000000000
  gcs_features_locked = 0x0000000000000000
  gcspr_el0 = 0x0000000000000000

Testing is more of the usual, where possible I'm writing a register then
doing something in the program to confirm the value was actually sent to
ptrace.

Added: 
    

Modified: 
    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
    lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
    lldb/test/API/linux/aarch64/gcs/main.c

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
index 6056f3001fed6e..efd3385c46e92f 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
@@ -64,8 +64,14 @@
 #define NT_ARM_FPMR 0x40e /* Floating point mode register */
 #endif
 
+#ifndef NT_ARM_GCS
+#define NT_ARM_GCS 0x410 /* Guarded Control Stack control registers */
+#endif
+
 #define HWCAP_PACA (1 << 30)
 
+#define HWCAP_GCS (1UL << 32)
+
 #define HWCAP2_MTE (1 << 18)
 
 #define HWCAP2_FPMR (1UL << 48)
@@ -150,6 +156,8 @@ NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
         opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE);
       if (*auxv_at_hwcap2 & HWCAP2_FPMR)
         opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR);
+      if (*auxv_at_hwcap & HWCAP_GCS)
+        opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS);
     }
 
     opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
@@ -193,6 +201,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   ::memset(&m_pac_mask, 0, sizeof(m_pac_mask));
   ::memset(&m_tls_regs, 0, sizeof(m_tls_regs));
   ::memset(&m_sme_pseudo_regs, 0, sizeof(m_sme_pseudo_regs));
+  ::memset(&m_gcs_regs, 0, sizeof(m_gcs_regs));
   std::fill(m_zt_reg.begin(), m_zt_reg.end(), 0);
 
   m_mte_ctrl_reg = 0;
@@ -213,6 +222,7 @@ NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   m_tls_is_valid = false;
   m_zt_buffer_is_valid = false;
   m_fpmr_is_valid = false;
+  m_gcs_is_valid = false;
 
   // SME adds the tpidr2 register
   m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs)
@@ -433,6 +443,14 @@ NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info,
     offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset();
     assert(offset < GetFPMRBufferSize());
     src = (uint8_t *)GetFPMRBuffer() + offset;
+  } else if (IsGCS(reg)) {
+    error = ReadGCS();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset();
+    assert(offset < GetGCSBufferSize());
+    src = (uint8_t *)GetGCSBuffer() + offset;
   } else
     return Status::FromErrorString(
         "failed - register wasn't recognized to be a GPR or an FPR, "
@@ -657,6 +675,17 @@ Status NativeRegisterContextLinux_arm64::WriteRegister(
     ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
 
     return WriteFPMR();
+  } else if (IsGCS(reg)) {
+    error = ReadGCS();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset();
+    assert(offset < GetGCSBufferSize());
+    dst = (uint8_t *)GetGCSBuffer() + offset;
+    ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
+
+    return WriteGCS();
   }
 
   return Status::FromErrorString("Failed to write register value");
@@ -672,6 +701,7 @@ enum RegisterSetType : uint32_t {
   SME,  // ZA only, because SVCR and SVG are pseudo registers.
   SME2, // ZT only.
   FPMR,
+  GCS, // Guarded Control Stack registers.
 };
 
 static uint8_t *AddRegisterSetType(uint8_t *dst,
@@ -759,6 +789,13 @@ NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) {
       return error;
   }
 
+  if (GetRegisterInfo().IsGCSPresent()) {
+    cached_size += sizeof(RegisterSetType) + GetGCSBufferSize();
+    error = ReadGCS();
+    if (error.Fail())
+      return error;
+  }
+
   // tpidr is always present but tpidr2 depends on SME.
   cached_size += sizeof(RegisterSetType) + GetTLSBufferSize();
   error = ReadTLS();
@@ -867,6 +904,11 @@ Status NativeRegisterContextLinux_arm64::ReadAllRegisterValues(
                             GetFPMRBufferSize());
   }
 
+  if (GetRegisterInfo().IsGCSPresent()) {
+    dst = AddSavedRegisters(dst, RegisterSetType::GCS, GetGCSBuffer(),
+                            GetGCSBufferSize());
+  }
+
   dst = AddSavedRegisters(dst, RegisterSetType::TLS, GetTLSBuffer(),
                           GetTLSBufferSize());
 
@@ -1020,6 +1062,11 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
           GetFPMRBuffer(), &src, GetFPMRBufferSize(), m_fpmr_is_valid,
           std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
       break;
+    case RegisterSetType::GCS:
+      error = RestoreRegisters(
+          GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid,
+          std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this));
+      break;
     }
 
     if (error.Fail())
@@ -1067,6 +1114,10 @@ bool NativeRegisterContextLinux_arm64::IsFPMR(unsigned reg) const {
   return GetRegisterInfo().IsFPMRReg(reg);
 }
 
+bool NativeRegisterContextLinux_arm64::IsGCS(unsigned reg) const {
+  return GetRegisterInfo().IsGCSReg(reg);
+}
+
 llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() {
   if (!m_refresh_hwdebug_info) {
     return llvm::Error::success();
@@ -1215,6 +1266,7 @@ void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() {
   m_tls_is_valid = false;
   m_zt_buffer_is_valid = false;
   m_fpmr_is_valid = false;
+  m_gcs_is_valid = false;
 
   // Update SVE and ZA registers in case there is change in configuration.
   ConfigureRegisterContext();
@@ -1400,6 +1452,40 @@ Status NativeRegisterContextLinux_arm64::WriteTLS() {
   return WriteRegisterSet(&ioVec, GetTLSBufferSize(), NT_ARM_TLS);
 }
 
+Status NativeRegisterContextLinux_arm64::ReadGCS() {
+  Status error;
+
+  if (m_gcs_is_valid)
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetGCSBuffer();
+  ioVec.iov_len = GetGCSBufferSize();
+
+  error = ReadRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS);
+
+  if (error.Success())
+    m_gcs_is_valid = true;
+
+  return error;
+}
+
+Status NativeRegisterContextLinux_arm64::WriteGCS() {
+  Status error;
+
+  error = ReadGCS();
+  if (error.Fail())
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetGCSBuffer();
+  ioVec.iov_len = GetGCSBufferSize();
+
+  m_gcs_is_valid = false;
+
+  return WriteRegisterSet(&ioVec, GetGCSBufferSize(), NT_ARM_GCS);
+}
+
 Status NativeRegisterContextLinux_arm64::ReadZAHeader() {
   Status error;
 

diff  --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
index 16190b5492582b..7ed0da85034969 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
@@ -92,6 +92,7 @@ class NativeRegisterContextLinux_arm64
   bool m_pac_mask_is_valid;
   bool m_tls_is_valid;
   size_t m_tls_size;
+  bool m_gcs_is_valid;
 
   struct user_pt_regs m_gpr_arm64; // 64-bit general purpose registers.
 
@@ -136,6 +137,12 @@ class NativeRegisterContextLinux_arm64
 
   uint64_t m_fpmr_reg;
 
+  struct gcs_regs {
+    uint64_t features_enabled;
+    uint64_t features_locked;
+    uint64_t gcspr_e0;
+  } m_gcs_regs;
+
   bool IsGPR(unsigned reg) const;
 
   bool IsFPR(unsigned reg) const;
@@ -166,6 +173,10 @@ class NativeRegisterContextLinux_arm64
 
   Status WriteZA();
 
+  Status ReadGCS();
+
+  Status WriteGCS();
+
   // No WriteZAHeader because writing only the header will disable ZA.
   // Instead use WriteZA and ensure you have the correct ZA buffer size set
   // beforehand if you wish to disable it.
@@ -187,6 +198,7 @@ class NativeRegisterContextLinux_arm64
   bool IsMTE(unsigned reg) const;
   bool IsTLS(unsigned reg) const;
   bool IsFPMR(unsigned reg) const;
+  bool IsGCS(unsigned reg) const;
 
   uint64_t GetSVERegVG() { return m_sve_header.vl / 8; }
 
@@ -212,6 +224,8 @@ class NativeRegisterContextLinux_arm64
 
   void *GetFPMRBuffer() { return &m_fpmr_reg; }
 
+  void *GetGCSBuffer() { return &m_gcs_regs; }
+
   size_t GetSVEHeaderSize() { return sizeof(m_sve_header); }
 
   size_t GetPACMaskSize() { return sizeof(m_pac_mask); }
@@ -234,6 +248,8 @@ class NativeRegisterContextLinux_arm64
 
   size_t GetFPMRBufferSize() { return sizeof(m_fpmr_reg); }
 
+  size_t GetGCSBufferSize() { return sizeof(m_gcs_regs); }
+
   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 575e9c8c81cbf5..0233837f99d097 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
@@ -63,6 +63,10 @@ bool RegisterContextPOSIX_arm64::IsFPMR(unsigned reg) const {
   return m_register_info_up->IsFPMRReg(reg);
 }
 
+bool RegisterContextPOSIX_arm64::IsGCS(unsigned reg) const {
+  return m_register_info_up->IsGCSReg(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 35ad56c98a7aed..de46c628d836d8 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
@@ -59,6 +59,7 @@ class RegisterContextPOSIX_arm64 : public lldb_private::RegisterContext {
   bool IsSME(unsigned reg) const;
   bool IsMTE(unsigned reg) const;
   bool IsFPMR(unsigned reg) const;
+  bool IsGCS(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 f51a93e1b2dcbd..c004c0f3c3cf52 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
@@ -97,6 +97,10 @@ static lldb_private::RegisterInfo g_register_infos_sme2[] = {
 static lldb_private::RegisterInfo g_register_infos_fpmr[] = {
     DEFINE_EXTENSION_REG(fpmr)};
 
+static lldb_private::RegisterInfo g_register_infos_gcs[] = {
+    DEFINE_EXTENSION_REG(gcs_features_enabled),
+    DEFINE_EXTENSION_REG(gcs_features_locked), DEFINE_EXTENSION_REG(gcspr_el0)};
+
 // Number of register sets provided by this context.
 enum {
   k_num_gpr_registers = gpr_w28 - gpr_x0 + 1,
@@ -109,6 +113,7 @@ enum {
   // only for SME1 registers.
   k_num_sme_register = 3,
   k_num_fpmr_register = 1,
+  k_num_gcs_register = 3,
   k_num_register_sets_default = 2,
   k_num_register_sets = 3
 };
@@ -221,6 +226,9 @@ static const lldb_private::RegisterSet g_reg_set_sme_arm64 = {
 static const lldb_private::RegisterSet g_reg_set_fpmr_arm64 = {
     "Floating Point Mode Register", "fpmr", k_num_fpmr_register, nullptr};
 
+static const lldb_private::RegisterSet g_reg_set_gcs_arm64 = {
+    "Guarded Control Stack Registers", "gcs", k_num_gcs_register, nullptr};
+
 RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
     const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets)
     : lldb_private::RegisterInfoAndSetInterface(target_arch),
@@ -273,6 +281,9 @@ RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
       if (m_opt_regsets.AllSet(eRegsetMaskFPMR))
         AddRegSetFPMR();
 
+      if (m_opt_regsets.AllSet(eRegsetMaskGCS))
+        AddRegSetGCS();
+
       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();
@@ -434,6 +445,24 @@ void RegisterInfoPOSIX_arm64::AddRegSetFPMR() {
   m_dynamic_reg_sets.back().registers = m_fpmr_regnum_collection.data();
 }
 
+void RegisterInfoPOSIX_arm64::AddRegSetGCS() {
+  uint32_t gcs_regnum = m_dynamic_reg_infos.size();
+  for (uint32_t i = 0; i < k_num_gcs_register; i++) {
+    m_gcs_regnum_collection.push_back(gcs_regnum + i);
+    m_dynamic_reg_infos.push_back(g_register_infos_gcs[i]);
+    m_dynamic_reg_infos[gcs_regnum + i].byte_offset =
+        m_dynamic_reg_infos[gcs_regnum + i - 1].byte_offset +
+        m_dynamic_reg_infos[gcs_regnum + i - 1].byte_size;
+    m_dynamic_reg_infos[gcs_regnum + i].kinds[lldb::eRegisterKindLLDB] =
+        gcs_regnum + i;
+  }
+
+  m_per_regset_regnum_range[m_register_set_count] =
+      std::make_pair(gcs_regnum, m_dynamic_reg_infos.size());
+  m_dynamic_reg_sets.push_back(g_reg_set_gcs_arm64);
+  m_dynamic_reg_sets.back().registers = m_gcs_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.
@@ -561,6 +590,10 @@ bool RegisterInfoPOSIX_arm64::IsFPMRReg(unsigned reg) const {
   return llvm::is_contained(m_fpmr_regnum_collection, reg);
 }
 
+bool RegisterInfoPOSIX_arm64::IsGCSReg(unsigned reg) const {
+  return llvm::is_contained(m_gcs_regnum_collection, reg);
+}
+
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; }
 
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; }
@@ -593,4 +626,8 @@ uint32_t RegisterInfoPOSIX_arm64::GetSMEOffset() const {
 
 uint32_t RegisterInfoPOSIX_arm64::GetFPMROffset() const {
   return m_register_info_p[m_fpmr_regnum_collection[0]].byte_offset;
-}
\ No newline at end of file
+}
+
+uint32_t RegisterInfoPOSIX_arm64::GetGCSOffset() const {
+  return m_register_info_p[m_gcs_regnum_collection[0]].byte_offset;
+}

diff  --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
index 16a951ef0935f0..d2ddf7d86d8c39 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
@@ -33,6 +33,7 @@ class RegisterInfoPOSIX_arm64
     eRegsetMaskZA = 32,
     eRegsetMaskZT = 64,
     eRegsetMaskFPMR = 128,
+    eRegsetMaskGCS = 256,
     eRegsetMaskDynamic = ~1,
   };
 
@@ -113,6 +114,8 @@ class RegisterInfoPOSIX_arm64
 
   void AddRegSetFPMR();
 
+  void AddRegSetGCS();
+
   uint32_t ConfigureVectorLengthSVE(uint32_t sve_vq);
 
   void ConfigureVectorLengthZA(uint32_t za_vq);
@@ -132,6 +135,7 @@ class RegisterInfoPOSIX_arm64
   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 IsGCSPresent() const { return m_opt_regsets.AnySet(eRegsetMaskGCS); }
 
   bool IsSVEReg(unsigned reg) const;
   bool IsSVEZReg(unsigned reg) const;
@@ -144,6 +148,7 @@ class RegisterInfoPOSIX_arm64
   bool IsSMERegZA(unsigned reg) const;
   bool IsSMERegZT(unsigned reg) const;
   bool IsFPMRReg(unsigned reg) const;
+  bool IsGCSReg(unsigned reg) const;
 
   uint32_t GetRegNumSVEZ0() const;
   uint32_t GetRegNumSVEFFR() const;
@@ -156,6 +161,7 @@ class RegisterInfoPOSIX_arm64
   uint32_t GetTLSOffset() const;
   uint32_t GetSMEOffset() const;
   uint32_t GetFPMROffset() const;
+  uint32_t GetGCSOffset() const;
 
 private:
   typedef std::map<uint32_t, std::vector<lldb_private::RegisterInfo>>
@@ -188,6 +194,7 @@ class RegisterInfoPOSIX_arm64
   std::vector<uint32_t> m_tls_regnum_collection;
   std::vector<uint32_t> m_sme_regnum_collection;
   std::vector<uint32_t> m_fpmr_regnum_collection;
+  std::vector<uint32_t> m_gcs_regnum_collection;
 };
 
 #endif

diff  --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
index 0928ff8e14e000..d3d4dbecf4a2ac 100644
--- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
+++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
@@ -83,3 +83,155 @@ def test_gcs_fault(self):
                 "stop reason = signal SIGSEGV: control protection fault",
             ],
         )
+
+    @skipUnlessArch("aarch64")
+    @skipUnlessPlatform(["linux"])
+    def test_gcs_registers(self):
+        if not self.isAArch64GCS():
+            self.skipTest("Target must support GCS.")
+
+        self.build()
+        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
+
+        self.runCmd("b test_func")
+        self.runCmd("b test_func2")
+        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"],
+        )
+
+        self.expect("register read --all", substrs=["Guarded Control Stack Registers:"])
+
+        # This helper reads all the GCS registers and optionally compares them
+        # against a previous state, then returns the current register values.
+        def check_gcs_registers(
+            expected_gcs_features_enabled=None,
+            expected_gcs_features_locked=None,
+            expected_gcspr_el0=None,
+        ):
+            thread = self.dbg.GetSelectedTarget().process.GetThreadAtIndex(0)
+            registerSets = thread.GetFrameAtIndex(0).GetRegisters()
+            gcs_registers = registerSets.GetFirstValueByName(
+                r"Guarded Control Stack Registers"
+            )
+
+            gcs_features_enabled = gcs_registers.GetChildMemberWithName(
+                "gcs_features_enabled"
+            ).GetValueAsUnsigned()
+            if expected_gcs_features_enabled is not None:
+                self.assertEqual(expected_gcs_features_enabled, gcs_features_enabled)
+
+            gcs_features_locked = gcs_registers.GetChildMemberWithName(
+                "gcs_features_locked"
+            ).GetValueAsUnsigned()
+            if expected_gcs_features_locked is not None:
+                self.assertEqual(expected_gcs_features_locked, gcs_features_locked)
+
+            gcspr_el0 = gcs_registers.GetChildMemberWithName(
+                "gcspr_el0"
+            ).GetValueAsUnsigned()
+            if expected_gcspr_el0 is not None:
+                self.assertEqual(expected_gcspr_el0, gcspr_el0)
+
+            return gcs_features_enabled, gcs_features_locked, gcspr_el0
+
+        enabled, locked, spr_el0 = check_gcs_registers()
+
+        # Features enabled should have at least the enable bit set, it could have
+        # others depending on what the C library did, but we can't rely on always
+        # having them.
+        self.assertTrue(enabled & 1, "Expected GCS enable bit to be set.")
+
+        # Features locked we cannot predict, we will just assert that it remains
+        # the same as we continue.
+
+        # spr_el0 will point to some memory region that is a shadow stack region.
+        self.expect(f"memory region {spr_el0}", substrs=["shadow stack: yes"])
+
+        # Continue into test_func2, where the GCS pointer should have been
+        # decremented, and the other registers remain the same.
+        self.runCmd("continue")
+
+        self.expect(
+            "thread list",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stopped", "stop reason = breakpoint"],
+        )
+
+        _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8)
+
+        # Any combination of GCS feature lock bits might have been set by the C
+        # library, and could be set to 0 or 1. To check that we can modify them,
+        # invert one of those bits then write it back to the lock register.
+        # The stack pushing feature is bit 2 of that register.
+        STACK_PUSH = 2
+        # Get the original value of the stack push lock bit.
+        stack_push = bool((locked >> STACK_PUSH) & 1)
+        # Invert the value and put it back into the set of lock bits.
+        new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)
+        # Write the new lock bits, which are the same as before, only with stack
+        # push locked (if it was previously unlocked), or unlocked (if it was
+        # previously locked).
+        self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")
+        # We should be able to read back this new set of lock bits.
+        self.expect(
+            f"register read gcs_features_locked",
+            substrs=[f"gcs_features_locked = 0x{new_locked:016x}"],
+        )
+
+        # We could prove the write made it to hardware by trying to prctl() to
+        # enable or disable the stack push feature here, but because the libc
+        # may or may not have locked it, it's tricky to coordinate this. Given
+        # that we know the other registers can be written and their values are
+        # seen by the process, we can assume this is too.
+
+        # Restore the original lock bits, as the libc may rely on being able
+        # to use certain features during program execution.
+        self.runCmd(f"register write gcs_features_locked 0x{locked:x}")
+
+        # Modify the guarded control stack pointer to cause a fault.
+        spr_el0 += 8
+        self.runCmd(f"register write gcspr_el0 {spr_el0}")
+        self.expect(
+            "register read gcspr_el0", substrs=[f"gcspr_el0 = 0x{spr_el0:016x}"]
+        )
+
+        # If we wrote it back correctly, we will now fault. Don't pass this signal
+        # to the application, as we will continue past it later.
+        self.runCmd("process handle SIGSEGV --pass false")
+        self.runCmd("continue")
+
+        self.expect(
+            "thread list",
+            "Expected stopped by SIGSEGV.",
+            substrs=[
+                "stopped",
+                "stop reason = signal SIGSEGV: control protection fault",
+            ],
+        )
+
+        # Now to prove we can write gcs_features_enabled, disable GCS and continue
+        # past the fault we caused. Note that although the libc likely locked the
+        # ability to disable GCS, ptrace bypasses the lock bits.
+        enabled &= ~1
+        self.runCmd(f"register write gcs_features_enabled {enabled}")
+        self.expect(
+            "register read gcs_features_enabled",
+            substrs=[f"gcs_features_enabled = 0x{enabled:016x}"],
+        )
+
+        # With GCS disabled, the invalid guarded control stack pointer is not
+        # checked, so the program can finish normally.
+        self.runCmd("continue")
+        self.expect(
+            "process status",
+            substrs=[
+                "exited with status = 0",
+            ],
+        )

diff  --git a/lldb/test/API/linux/aarch64/gcs/main.c b/lldb/test/API/linux/aarch64/gcs/main.c
index 32a9b07c207436..09354639af376f 100644
--- a/lldb/test/API/linux/aarch64/gcs/main.c
+++ b/lldb/test/API/linux/aarch64/gcs/main.c
@@ -2,8 +2,8 @@
 #include <sys/auxv.h>
 #include <sys/prctl.h>
 
-#ifndef HWCAP2_GCS
-#define HWCAP2_GCS (1UL << 63)
+#ifndef HWCAP_GCS
+#define HWCAP_GCS (1UL << 32)
 #endif
 
 #define PR_GET_SHADOW_STACK_STATUS 74
@@ -49,8 +49,14 @@ void gcs_signal() {
       "ret\n");
 }
 
+// These functions are used to observe gcspr_el0 changing as we enter them, and
+// the fault we cause by changing its value.
+void test_func2() { volatile int i = 99; }
+
+void test_func() { test_func2(); }
+
 int main() {
-  if (!(getauxval(AT_HWCAP2) & HWCAP2_GCS))
+  if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
     return 1;
 
   unsigned long mode = get_gcs_status();
@@ -63,7 +69,16 @@ int main() {
   }
 
   // By now we should have one memory region where the GCS is stored.
-  gcs_signal(); // Set break point at this line.
+
+  // For register read/write tests.
+  test_func();
+
+  // If this was a register test, we would have disabled GCS during the
+  // test_func call. We cannot re-enable it from ptrace so skip this part in
+  // this case.
+  mode = get_gcs_status();
+  if ((mode & 1) == 1)
+    gcs_signal(); // Set break point at this line.
 
   return 0;
 }


        


More information about the lldb-commits mailing list