[Lldb-commits] [lldb] [lldb][AArch64] Fix expression evaluation with Guarded Control Stacks (PR #123918)

David Spickett via lldb-commits lldb-commits at lists.llvm.org
Wed Jan 22 01:59:38 PST 2025


https://github.com/DavidSpickett created https://github.com/llvm/llvm-project/pull/123918

When the Guarded Control Stack (GCS) is enabled, returns cause the 
processor to validate that the address at the location pointed to
by gcspr_el0 matches the one in the link register.

```
ret (lr=A) << pc

| GCS |
+=====+
|  A  |
|  B  | << gcspr_el0

Fault: tried to return to A when you should have returned to B.
```

Therefore when an expression wraper function tries to return to
the expression return address (usually `_start` if there is a libc),
it would fault.

```
ret (lr=_start) << pc

| GCS        |
+============+
| user_func1 |
| user_func2 | << gcspr_el0

Fault: tried to return to _start when you should have return to user_func2.
```

To fix this we must push that return address to the GCS in PrepareTrivialCall.
This value is then consumed by the final return and the expression
completes as expected.

```
ret (lr=_start) << pc

| GCS        |
+============+
| user_func1 |
| user_func2 |
| _start     | << gcspr_el0

No fault, we return to _start as normal.
```

The gcspr_el0 register will be restored after expression evaluation
so that the program can continue correctly.

However, due to restrictions in the Linux GCS ABI, we will not 
restore the enable bit of gcs_features_enabled. Re-enabling GCS
via ptrace is not supported because it requires memory to be
allocated.

We could disable GCS if the expression enabled GCS, however this
would use up that state transition that the program might later
rely on. And generally it is cleaner to ignore the whole bit
rather than one state transition of it.

We will also not restore the GCS entry that was overwritten with
the expression's return address. On the grounds that:
* This entry will never be used by the program. If the program branches,
  the entry will be overwritten. If the program returns, gcspr_el0
  will point to the entry before the expression return address
  and that entry will instead be validated.
* Any expression that calls functions will overwrite even more
  entries, so the user needs to be aware of that anyway if they
  want to preserve the contents of the GCS for inspection.
* An expression could leave the program in a state where
  restoring the value makes the situation worse. Especially
  if we ever support this in bare metal debugging.

I will later document all this on https://lldb.llvm.org/use/aarch64-linux.html
as well.

Tests have been added for:
* A function call that does not interact with GCS.
* A call that does, and disables it (we do not re-enable it).
* A call that does, and enables it (we do not disable it again).

>From 951a38910e49f3e93dc6c9a084e955ab01ad4c49 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at linaro.org>
Date: Fri, 9 Aug 2024 11:56:29 +0100
Subject: [PATCH 1/2] [lldb][AArch64] Add Guarded Control Stack registers

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.
---
 .../NativeRegisterContextLinux_arm64.cpp      |  86 +++++++++++
 .../Linux/NativeRegisterContextLinux_arm64.h  |  16 +++
 .../Utility/RegisterContextPOSIX_arm64.cpp    |   4 +
 .../Utility/RegisterContextPOSIX_arm64.h      |   1 +
 .../Utility/RegisterInfoPOSIX_arm64.cpp       |  39 ++++-
 .../Process/Utility/RegisterInfoPOSIX_arm64.h |   7 +
 .../linux/aarch64/gcs/TestAArch64LinuxGCS.py  | 134 ++++++++++++++++++
 lldb/test/API/linux/aarch64/gcs/main.c        |  23 ++-
 8 files changed, 305 insertions(+), 5 deletions(-)

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..e08d4821bafc4f 100644
--- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
+++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
@@ -83,3 +83,137 @@ 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:"])
+
+        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.
+        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)
+
+        # Modify the 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 but don't pass this
+        # signal to the application.
+        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",
+            ],
+        )
+
+        # Any combination of lock bits could be set. Flip then restore one of them.
+        STACK_PUSH = 2
+        stack_push = bool((locked >> STACK_PUSH) & 1)
+        new_locked = (locked & ~(1 << STACK_PUSH)) | (int(not stack_push) << STACK_PUSH)
+        self.runCmd(f"register write gcs_features_locked 0x{new_locked:x}")
+        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 change
+        # the feature here, but we cannot know if the libc locked it or not.
+        # Given that we know the other registers in the set write correctly, we
+        # can assume this one does.
+
+        self.runCmd(f"register write gcs_features_locked 0x{locked:x}")
+
+        # Now to prove we can write gcs_features_enabled, disable GCS and continue
+        # past the fault.
+        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}"],
+        )
+
+        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;
 }

>From d192ab9d62b18d03b0354006a5611f75af04fc40 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at linaro.org>
Date: Tue, 20 Aug 2024 16:06:34 +0100
Subject: [PATCH 2/2] [lldb][AArch64] Fix expression evaluation with Guarded
 Control Stacks

When the Guarded Control Stack (GCS) is enabled, returns cause the
processor to validate that the address at the location pointed to
by gcspr_el0 matches the one in the link register.

```
ret (lr=A) << pc

| GCS |
+=====+
|  A  |
|  B  | << gcspr_el0

Fault: tried to return to A when you should have returned to B.
```

Therefore when an expression wraper function tries to return to
the expression return address (usually `_start` if there is a libc),
it would fault.

```
ret (lr=_start) << pc

| GCS        |
+============+
| user_func1 |
| user_func2 | << gcspr_el0

Fault: tried to return to _start when you should have return to user_func2.
```

To fix this we must push that return address to the GCS in PrepareTrivialCall.
This value is then consumed by the final return and the expression
completes as expected.

```
ret (lr=_start) << pc

| GCS        |
+============+
| user_func1 |
| user_func2 |
| _start     | << gcspr_el0

No fault, we return to _start as normal.
```

The gcspr_el0 register will be restored after expression evaluation
so that the program can continue correctly.

However, due to restrictions in the Linux GCS ABI, we will not
restore the enable bit of gcs_features_enabled. Re-enabling GCS
via ptrace is not supported because it requires memory to be
allocated.

We could disable GCS if the expression enabled GCS, however this
would use up that state transition that the program might later
rely on. And generally it is cleaner to ignore the whole bit
rather than one state transition of it.

We will also not restore the GCS entry that was overwritten with
the expression's return address. On the grounds that:
* This entry will never be used by the program. If the program branches,
  the entry will be overwritten. If the program returns, gcspr_el0
  will point to the entry before the expression return address
  and that entry will instead be validated.
* Any expression that calls functions will overwrite even more
  entries, so the user needs to be aware of that anyway if they
  want to preserve the contents of the GCS for inspection.
* An expression could leave the program in a state where
  restoring the value makes the situation worse. Especially
  if we ever support this in bare metal debugging.

I will later document all this on https://lldb.llvm.org/use/aarch64-linux.html
as well.

Tests have been added for:
* A function call that does not interact with GCS.
* A call that does, and disables it (we do not re-enable it).
* A call that does, and enables it (we do not disable it again).
---
 .../Plugins/ABI/AArch64/ABISysV_arm64.cpp     |  56 +++++
 .../NativeRegisterContextLinux_arm64.cpp      |  20 +-
 .../linux/aarch64/gcs/TestAArch64LinuxGCS.py  | 192 +++++++++++++++---
 lldb/test/API/linux/aarch64/gcs/main.c        |  47 ++++-
 4 files changed, 276 insertions(+), 39 deletions(-)

diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
index 93b8141e97ef86..d843e718b6875e 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
@@ -60,6 +60,59 @@ ABISysV_arm64::CreateInstance(lldb::ProcessSP process_sp, const ArchSpec &arch)
   return ABISP();
 }
 
+static bool PushToLinuxGuardedControlStack(addr_t return_addr,
+                                           RegisterContext *reg_ctx,
+                                           Thread &thread) {
+  // If the Guarded Control Stack extension is enabled we need to put the return
+  // address onto that stack.
+  const RegisterInfo *gcs_features_enabled_info =
+      reg_ctx->GetRegisterInfoByName("gcs_features_enabled");
+  if (!gcs_features_enabled_info)
+    return false;
+
+  uint64_t gcs_features_enabled = reg_ctx->ReadRegisterAsUnsigned(
+      gcs_features_enabled_info, LLDB_INVALID_ADDRESS);
+  if (gcs_features_enabled == LLDB_INVALID_ADDRESS)
+    return false;
+
+  // Only attempt this if GCS is enabled. If it's not enabled then gcspr_el0
+  // may point to unmapped memory.
+  if ((gcs_features_enabled & 1) == 0)
+    return false;
+
+  const RegisterInfo *gcspr_el0_info =
+      reg_ctx->GetRegisterInfoByName("gcspr_el0");
+  if (!gcspr_el0_info)
+    return false;
+
+  uint64_t gcspr_el0 =
+      reg_ctx->ReadRegisterAsUnsigned(gcspr_el0_info, LLDB_INVALID_ADDRESS);
+  if (gcspr_el0 == LLDB_INVALID_ADDRESS)
+    return false;
+
+  // A link register entry on the GCS is 8 bytes.
+  gcspr_el0 -= 8;
+  if (!reg_ctx->WriteRegisterFromUnsigned(gcspr_el0_info, gcspr_el0))
+    return false;
+
+  Status error;
+  size_t wrote = thread.GetProcess()->WriteMemory(gcspr_el0, &return_addr,
+                                                  sizeof(return_addr), error);
+  if ((wrote != sizeof(return_addr) || error.Fail()))
+    return false;
+
+  Log *log = GetLog(LLDBLog::Expressions);
+  LLDB_LOGF(log,
+            "Pushed return address 0x%" PRIx64 "to Guarded Control Stack. "
+            "gcspr_el0 was 0%" PRIx64 ", is now 0x%" PRIx64 ".",
+            return_addr, gcspr_el0 + 8, gcspr_el0);
+
+  // gcspr_el0 will be restored to the original value by lldb-server after
+  // the call has finished, which serves as the "pop".
+
+  return true;
+}
+
 bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp,
                                        addr_t func_addr, addr_t return_addr,
                                        llvm::ArrayRef<addr_t> args) const {
@@ -103,6 +156,9 @@ bool ABISysV_arm64::PrepareTrivialCall(Thread &thread, addr_t sp,
           return_addr))
     return false;
 
+  if (GetProcessSP()->GetTarget().GetArchitecture().GetTriple().isOSLinux())
+    PushToLinuxGuardedControlStack(return_addr, reg_ctx, thread);
+
   // Set "sp" to the requested value
   if (!reg_ctx->WriteRegisterFromUnsigned(
           reg_ctx->GetRegisterInfo(eRegisterKindGeneric,
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
index efd3385c46e92f..884c7d4b9e3590 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
@@ -1063,9 +1063,27 @@ Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
           std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
       break;
     case RegisterSetType::GCS:
+      // It is not permitted to enable GCS via ptrace. We can disable it, but
+      // to keep things simple we will not revert any change to the
+      // PR_SHADOW_STACK_ENABLE bit. Instead patch in the current enable bit
+      // into the registers we are about to restore.
+      m_gcs_is_valid = false;
+      error = ReadGCS();
+      if (error.Fail())
+        return error;
+
+      uint64_t enable_bit = m_gcs_regs.features_enabled & 1UL;
+      gcs_regs new_gcs_regs = *reinterpret_cast<const gcs_regs *>(src);
+      new_gcs_regs.features_enabled =
+          (new_gcs_regs.features_enabled & ~1UL) | enable_bit;
+
+      const uint8_t *new_gcs_src =
+          reinterpret_cast<const uint8_t *>(&new_gcs_regs);
       error = RestoreRegisters(
-          GetGCSBuffer(), &src, GetGCSBufferSize(), m_gcs_is_valid,
+          GetGCSBuffer(), &new_gcs_src, GetGCSBufferSize(), m_gcs_is_valid,
           std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this));
+      src += GetGCSBufferSize();
+
       break;
     }
 
diff --git a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
index e08d4821bafc4f..6d86b359cf51fe 100644
--- a/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
+++ b/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
@@ -84,6 +84,38 @@ def test_gcs_fault(self):
             ],
         )
 
+    def check_gcs_registers(
+        self,
+        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
+
     @skipUnlessArch("aarch64")
     @skipUnlessPlatform(["linux"])
     def test_gcs_registers(self):
@@ -108,38 +140,7 @@ def test_gcs_registers(self):
 
         self.expect("register read --all", substrs=["Guarded Control Stack Registers:"])
 
-        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()
+        enabled, locked, spr_el0 = self.check_gcs_registers()
 
         # Features enabled should have at least the enable bit set, it could have
         # others depending on what the C library did.
@@ -161,7 +162,7 @@ def check_gcs_registers(
             substrs=["stopped", "stop reason = breakpoint"],
         )
 
-        _, _, spr_el0 = check_gcs_registers(enabled, locked, spr_el0 - 8)
+        _, _, spr_el0 = self.check_gcs_registers(enabled, locked, spr_el0 - 8)
 
         # Modify the control stack pointer to cause a fault.
         spr_el0 += 8
@@ -217,3 +218,128 @@ def check_gcs_registers(
                 "exited with status = 0",
             ],
         )
+
+    @skipUnlessPlatform(["linux"])
+    def test_gcs_expression_simple(self):
+        if not self.isAArch64GCS():
+            self.skipTest("Target must support GCS.")
+
+        self.build()
+        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
+
+        # Break before GCS has been enabled.
+        self.runCmd("b main")
+        # And after it has been enabled.
+        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"],
+        )
+
+        # GCS has not been enabled yet and the ABI plugin should know not to
+        # attempt pushing to the control stack.
+        before = self.check_gcs_registers()
+        expr_cmd = "p get_gcs_status()"
+        self.expect(expr_cmd, substrs=["(unsigned long) 0"])
+        self.check_gcs_registers(*before)
+
+        # Continue to when GCS has been enabled.
+        self.runCmd("continue")
+        self.expect(
+            "thread list",
+            STOPPED_DUE_TO_BREAKPOINT,
+            substrs=["stopped", "stop reason = breakpoint"],
+        )
+
+        # This time we do need to push to the GCS and having done so, we can
+        # return from this expression without causing a fault.
+        before = self.check_gcs_registers()
+        self.expect(expr_cmd, substrs=["(unsigned long) 1"])
+        self.check_gcs_registers(*before)
+
+    @skipUnlessPlatform(["linux"])
+    def test_gcs_expression_disable_gcs(self):
+        if not self.isAArch64GCS():
+            self.skipTest("Target must support GCS.")
+
+        self.build()
+        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
+
+        # Break after GCS is enabled.
+        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"],
+        )
+
+        # Unlock all features so the expression can enable them again.
+        self.runCmd("register write gcs_features_locked 0")
+        # Disable all features, but keep GCS itself enabled.
+        PR_SHADOW_STACK_ENABLE = 1
+        self.runCmd(f"register write gcs_features_enabled 0x{PR_SHADOW_STACK_ENABLE:x}")
+
+        enabled, locked, spr_el0 = self.check_gcs_registers()
+        # We restore everything apart GCS being enabled, as we are not allowed to
+        # go from disabled -> enabled via ptrace.
+        self.expect("p change_gcs_config(false)", substrs=["true"])
+        enabled &= ~1
+        self.check_gcs_registers(enabled, locked, spr_el0)
+
+    @skipUnlessPlatform(["linux"])
+    def test_gcs_expression_enable_gcs(self):
+        if not self.isAArch64GCS():
+            self.skipTest("Target must support GCS.")
+
+        self.build()
+        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
+
+        # Break before GCS is enabled.
+        self.runCmd("b main")
+
+        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"],
+        )
+
+        # Unlock all features so the expression can enable them again.
+        self.runCmd("register write gcs_features_locked 0")
+        # Disable all features. The program needs PR_SHADOW_STACK_PUSH, but it
+        # will enable that itself.
+        self.runCmd(f"register write gcs_features_enabled 0")
+
+        enabled, locked, spr_el0 = self.check_gcs_registers()
+        self.expect("p change_gcs_config(true)", substrs=["true"])
+        # Though we could disable GCS with ptrace, we choose not to to be
+        # consistent with the disabled -> enabled behaviour.
+        enabled |= 1
+        self.check_gcs_registers(enabled, locked, spr_el0)
diff --git a/lldb/test/API/linux/aarch64/gcs/main.c b/lldb/test/API/linux/aarch64/gcs/main.c
index 09354639af376f..396aef7499ca9e 100644
--- a/lldb/test/API/linux/aarch64/gcs/main.c
+++ b/lldb/test/API/linux/aarch64/gcs/main.c
@@ -1,4 +1,5 @@
 #include <asm/hwcap.h>
+#include <stdbool.h>
 #include <sys/auxv.h>
 #include <sys/prctl.h>
 
@@ -8,7 +9,12 @@
 
 #define PR_GET_SHADOW_STACK_STATUS 74
 #define PR_SET_SHADOW_STACK_STATUS 75
-#define PR_SHADOW_STACK_ENABLE (1UL)
+#define PR_LOCK_SHADOW_STACK_STATUS 76
+
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#define PR_SHADOW_STACK_WRITE (1UL << 1)
+#define PR_SHADOW_STACK_PUSH (1UL << 2)
+
 #define PRCTL_SYSCALL_NO 167
 
 // Once we enable GCS, we cannot return from the function that made the syscall
@@ -36,6 +42,36 @@ unsigned long get_gcs_status() {
   return mode;
 }
 
+extern void _start();
+bool change_gcs_config(bool enable) {
+  // The test unlocks and disables all features (excluding the main enable bit)
+  // before calling this expression. Enable them again.
+  unsigned long new_status =
+      enable | PR_SHADOW_STACK_PUSH | PR_SHADOW_STACK_WRITE;
+
+  if (enable) {
+    // We would not be able to return from prctl().
+    my_prctl(PR_SET_SHADOW_STACK_STATUS, new_status, 0, 0, 0);
+
+    // This is a stack, so we must push in reverse order to the pops we want to
+    // have later. So push the return of __lldb_expr (_start), then the return
+    // address of this function (__lldb_expr).
+    __asm__ __volatile__("sys	#3, C7, C7, #0, %0\n"  // gcspushm _start
+                         "sys	#3, C7, C7, #0, x30\n" // gcspushm x30
+                         :
+                         : "r"(_start));
+  } else {
+    if (prctl(PR_SET_SHADOW_STACK_STATUS, new_status, 0, 0, 0) != 0)
+      return false;
+  }
+
+  // Turn back on all locks.
+  if (prctl(PR_LOCK_SHADOW_STACK_STATUS, ~(0UL), 0, 0, 0) != 0)
+    return false;
+
+  return true;
+}
+
 void gcs_signal() {
   // If we enabled GCS manually, then we could just return from main to generate
   // a signal. However, if the C library enabled it, then we'd just exit
@@ -50,10 +86,11 @@ void gcs_signal() {
 }
 
 // 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; }
+// the fault we cause by changing its value. Also used to check expression
+// eval can handle function calls.
+int test_func2() { return 99; }
 
-void test_func() { test_func2(); }
+int test_func() { return test_func2(); }
 
 int main() {
   if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
@@ -71,7 +108,7 @@ int main() {
   // By now we should have one memory region where the GCS is stored.
 
   // For register read/write tests.
-  test_func();
+  volatile int i = 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



More information about the lldb-commits mailing list