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

This change adds initial support for managing the Permission Overlay Extension 
(POE). This extension allows userspace programs to change memory permissions 
without making a sycall.

This is used to implement Linux's memory protection keys 
(https://docs.kernel.org/core-api/protection-keys.html) on AArch64.

Overview of POE:
* Page table entries have a set of permissions. To change these, a program 
would have to use a syscall which adds overhead.
* 3 bits of the page table entry are used for a protection key 0-7.
* POE adds a new register "por" which stores 4-bit sets of permissions.
* The protection key is an index into this por register.
* Permissions in POR are applied on top of the page table permissions, but may 
only remove permissions. For example, if you overlay read/execute over 
read/write, the result is read. Since execute was not in the page table 
permissions.
* This register can be modified without leaving userspace, making permission 
changes faster.

To help debug this, I have made the following changes to LLDB:
* Ability to read and write the por register.
* Save and restore of por when running expressions.
* Register field definitions to print the por permissions in a human readable 
form.
* Recognition of memory protection key faults as a distinct type of SIGSEGV 
(this will apply to Linux on any architecture).

There are a few more features to add around memory region information, that 
will be in follow up changes.

>From a33f9f4767e805d39a5c33e808a6a2425838d8bd Mon Sep 17 00:00:00 2001
From: David Spickett <[email protected]>
Date: Tue, 20 Jan 2026 14:37:40 +0000
Subject: [PATCH] [lldb][AArch64][Linux] Add support for the Permission Overlay
 Extension (POE)

This change adds initial support for managing the Permission
Overlay Extension (POE). This extension allows userpace programs
to change memory permissions without making a sycall.

This is used to implement Linux's memory protection keys
(https://docs.kernel.org/core-api/protection-keys.html) on AArch64.

Overview of POE:
* Page table entries have a set of permissions. To change these,
  a program would have to use a syscall which adds overhead.
* 3 bits of the page table entry are used for a protection key 0-7.
* POE adds a new register "por" which stores 4-bit sets of permissions.
* The protection key is an index into this por register.
* Permissions in POR are applied on top of the page table
  permissions, but may only remove permissions. For example,
  if you overlay read/execute over read/write, the result
  is read. Since execute was not in the page table permissions.
* This register can be modified without leaving userspace,
  making permission changes faster.

To help debug this, I have made the following changes to LLDB:
* Ability to read and write the por register.
* Save and restore of por when running expressions.
* Register field definitions to print the por permissions in a
  human readable form.
* Recognition of memory protection key faults as a distinct type
  of SIGSEGV (this will apply to Linux on any architecture).

There are a few more features to add, that will be in follow up
changes.
---
 .../Python/lldbsuite/test/cpu_feature.py      |   1 +
 .../Python/lldbsuite/test/lldbtest.py         |   3 +
 .../NativeRegisterContextLinux_arm64.cpp      |  89 +++++++++++-
 .../Linux/NativeRegisterContextLinux_arm64.h  |  16 +++
 .../Plugins/Process/Utility/LinuxSignals.cpp  |   1 +
 .../Utility/RegisterContextPOSIX_arm64.cpp    |   4 +
 .../Utility/RegisterContextPOSIX_arm64.h      |   1 +
 .../Utility/RegisterFlagsDetector_arm64.cpp   |  34 +++++
 .../Utility/RegisterFlagsDetector_arm64.h     |   5 +-
 .../Utility/RegisterInfoPOSIX_arm64.cpp       |  33 +++++
 .../Process/Utility/RegisterInfoPOSIX_arm64.h |   7 +
 .../RegisterContextPOSIXCore_arm64.cpp        |  15 ++
 .../elf-core/RegisterContextPOSIXCore_arm64.h |   1 +
 .../Process/elf-core/RegisterUtilities.h      |   4 +
 .../linux/aarch64/permission_overlay/Makefile |   3 +
 .../permission_overlay/TestAArch64LinuxPOE.py | 129 ++++++++++++++++++
 .../linux/aarch64/permission_overlay/corefile | Bin 0 -> 393216 bytes
 .../linux/aarch64/permission_overlay/main.c   | 128 +++++++++++++++++
 18 files changed, 472 insertions(+), 2 deletions(-)
 create mode 100644 lldb/test/API/linux/aarch64/permission_overlay/Makefile
 create mode 100644 
lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
 create mode 100644 lldb/test/API/linux/aarch64/permission_overlay/corefile
 create mode 100644 lldb/test/API/linux/aarch64/permission_overlay/main.c

diff --git a/lldb/packages/Python/lldbsuite/test/cpu_feature.py 
b/lldb/packages/Python/lldbsuite/test/cpu_feature.py
index d7668c1884e40..736d5e86399ae 100644
--- a/lldb/packages/Python/lldbsuite/test/cpu_feature.py
+++ b/lldb/packages/Python/lldbsuite/test/cpu_feature.py
@@ -61,6 +61,7 @@ def _is_supported_darwin(self, cmd_runner):
 
 class AArch64:
     FPMR = CPUFeature("fpmr")
+    POE = CPUFeature("poe")
     GCS = CPUFeature("gcs")
     MTE = CPUFeature("mte", "hw.optional.arm.FEAT_MTE4")
     MTE_STORE_ONLY = CPUFeature("mtestoreonly")
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py 
b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 55b527e3a9ce6..616d7cea6b4e3 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -1405,6 +1405,9 @@ def isAArch64PAuth(self):
     def isAArch64FPMR(self):
         return self.isAArch64() and self.isSupported(cpu_feature.AArch64.FPMR)
 
+    def isAArch64POE(self):
+        return self.isAArch64() and self.isSupported(cpu_feature.AArch64.POE)
+
     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 294a446686f22..41b094f1a383a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
@@ -65,6 +65,10 @@
 #define NT_ARM_FPMR 0x40e /* Floating point mode register */
 #endif
 
+#ifndef NT_ARM_POE
+#define NT_ARM_POE 0x40f /* Permission Overlay registers */
+#endif
+
 #ifndef NT_ARM_GCS
 #define NT_ARM_GCS 0x410 /* Guarded Control Stack control registers */
 #endif
@@ -77,6 +81,8 @@
 
 #define HWCAP2_FPMR (1UL << 48)
 
+#define HWCAP2_POE (1ULL << 63)
+
 using namespace lldb;
 using namespace lldb_private;
 using namespace lldb_private::process_linux;
@@ -159,6 +165,8 @@ 
NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux(
         opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR);
       if (*auxv_at_hwcap & HWCAP_GCS)
         opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS);
+      if (*auxv_at_hwcap2 & HWCAP2_POE)
+        opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskPOE);
     }
 
     opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS);
@@ -206,6 +214,7 @@ 
NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   ::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));
+  ::memset(&m_poe_regs, 0, sizeof(m_poe_regs));
   std::fill(m_zt_reg.begin(), m_zt_reg.end(), 0);
 
   m_mte_ctrl_reg = 0;
@@ -227,6 +236,7 @@ 
NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64(
   m_zt_buffer_is_valid = false;
   m_fpmr_is_valid = false;
   m_gcs_is_valid = false;
+  m_poe_is_valid = false;
 
   // SME adds the tpidr2 register
   m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs)
@@ -455,6 +465,14 @@ NativeRegisterContextLinux_arm64::ReadRegister(const 
RegisterInfo *reg_info,
     offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset();
     assert(offset < GetGCSBufferSize());
     src = (uint8_t *)GetGCSBuffer() + offset;
+  } else if (IsPOE(reg)) {
+    error = ReadPOE();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetPOEOffset();
+    assert(offset < GetPOEBufferSize());
+    src = (uint8_t *)GetPOEBuffer() + offset;
   } else
     return Status::FromErrorString(
         "failed - register wasn't recognized to be a GPR or an FPR, "
@@ -690,6 +708,17 @@ Status NativeRegisterContextLinux_arm64::WriteRegister(
     ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
 
     return WriteGCS();
+  } else if (IsPOE(reg)) {
+    error = ReadPOE();
+    if (error.Fail())
+      return error;
+
+    offset = reg_info->byte_offset - GetRegisterInfo().GetPOEOffset();
+    assert(offset < GetPOEBufferSize());
+    dst = (uint8_t *)GetPOEBuffer() + offset;
+    ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size);
+
+    return WritePOE();
   }
 
   return Status::FromErrorString("Failed to write register value");
@@ -706,6 +735,7 @@ enum RegisterSetType : uint32_t {
   SME2, // ZT only.
   FPMR,
   GCS, // Guarded Control Stack registers.
+  POE, // Permission Overlay registers.
 };
 
 static uint8_t *AddRegisterSetType(uint8_t *dst,
@@ -800,6 +830,13 @@ 
NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) {
       return error;
   }
 
+  if (GetRegisterInfo().IsPOEPresent()) {
+    cached_size += sizeof(RegisterSetType) + GetPOEBufferSize();
+    error = ReadPOE();
+    if (error.Fail())
+      return error;
+  }
+
   // tpidr is always present but tpidr2 depends on SME.
   cached_size += sizeof(RegisterSetType) + GetTLSBufferSize();
   error = ReadTLS();
@@ -913,6 +950,11 @@ Status 
NativeRegisterContextLinux_arm64::ReadAllRegisterValues(
                             GetGCSBufferSize());
   }
 
+  if (GetRegisterInfo().IsPOEPresent()) {
+    dst = AddSavedRegisters(dst, RegisterSetType::POE, GetPOEBuffer(),
+                            GetPOEBufferSize());
+  }
+
   dst = AddSavedRegisters(dst, RegisterSetType::TLS, GetTLSBuffer(),
                           GetTLSBufferSize());
 
@@ -1066,7 +1108,7 @@ Status 
NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
           GetFPMRBuffer(), &src, GetFPMRBufferSize(), m_fpmr_is_valid,
           std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this));
       break;
-    case RegisterSetType::GCS:
+    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
@@ -1090,6 +1132,12 @@ Status 
NativeRegisterContextLinux_arm64::WriteAllRegisterValues(
 
       break;
     }
+    case RegisterSetType::POE:
+      error = RestoreRegisters(
+          GetPOEBuffer(), &src, GetPOEBufferSize(), m_poe_is_valid,
+          std::bind(&NativeRegisterContextLinux_arm64::WritePOE, this));
+      break;
+    }
 
     if (error.Fail())
       return error;
@@ -1140,6 +1188,10 @@ bool NativeRegisterContextLinux_arm64::IsGCS(unsigned 
reg) const {
   return GetRegisterInfo().IsGCSReg(reg);
 }
 
+bool NativeRegisterContextLinux_arm64::IsPOE(unsigned reg) const {
+  return GetRegisterInfo().IsPOEReg(reg);
+}
+
 llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() {
   if (!m_refresh_hwdebug_info) {
     return llvm::Error::success();
@@ -1244,6 +1296,7 @@ void 
NativeRegisterContextLinux_arm64::InvalidateAllRegisters() {
   m_zt_buffer_is_valid = false;
   m_fpmr_is_valid = false;
   m_gcs_is_valid = false;
+  m_poe_is_valid = false;
 
   // Update SVE and ZA registers in case there is change in configuration.
   ConfigureRegisterContext();
@@ -1591,6 +1644,40 @@ Status NativeRegisterContextLinux_arm64::WriteFPMR() {
   return WriteRegisterSet(&ioVec, GetFPMRBufferSize(), NT_ARM_FPMR);
 }
 
+Status NativeRegisterContextLinux_arm64::ReadPOE() {
+  Status error;
+
+  if (m_poe_is_valid)
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetPOEBuffer();
+  ioVec.iov_len = GetPOEBufferSize();
+
+  error = ReadRegisterSet(&ioVec, GetPOEBufferSize(), NT_ARM_POE);
+
+  if (error.Success())
+    m_poe_is_valid = true;
+
+  return error;
+}
+
+Status NativeRegisterContextLinux_arm64::WritePOE() {
+  Status error;
+
+  error = ReadPOE();
+  if (error.Fail())
+    return error;
+
+  struct iovec ioVec;
+  ioVec.iov_base = GetPOEBuffer();
+  ioVec.iov_len = GetPOEBufferSize();
+
+  m_poe_is_valid = false;
+
+  return WriteRegisterSet(&ioVec, GetPOEBufferSize(), NT_ARM_POE);
+}
+
 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 7ed0da8503496..cc34ef4c8f246 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
@@ -93,6 +93,7 @@ class NativeRegisterContextLinux_arm64
   bool m_tls_is_valid;
   size_t m_tls_size;
   bool m_gcs_is_valid;
+  bool m_poe_is_valid;
 
   struct user_pt_regs m_gpr_arm64; // 64-bit general purpose registers.
 
@@ -137,6 +138,12 @@ class NativeRegisterContextLinux_arm64
 
   uint64_t m_fpmr_reg;
 
+  struct poe_regs {
+    uint64_t por_reg;
+  };
+
+  struct poe_regs m_poe_regs;
+
   struct gcs_regs {
     uint64_t features_enabled;
     uint64_t features_locked;
@@ -192,6 +199,10 @@ class NativeRegisterContextLinux_arm64
 
   Status WriteFPMR();
 
+  Status ReadPOE();
+
+  Status WritePOE();
+
   bool IsSVE(unsigned reg) const;
   bool IsSME(unsigned reg) const;
   bool IsPAuth(unsigned reg) const;
@@ -199,6 +210,7 @@ class NativeRegisterContextLinux_arm64
   bool IsTLS(unsigned reg) const;
   bool IsFPMR(unsigned reg) const;
   bool IsGCS(unsigned reg) const;
+  bool IsPOE(unsigned reg) const;
 
   uint64_t GetSVERegVG() { return m_sve_header.vl / 8; }
 
@@ -226,6 +238,8 @@ class NativeRegisterContextLinux_arm64
 
   void *GetGCSBuffer() { return &m_gcs_regs; }
 
+  void *GetPOEBuffer() { return &m_poe_regs; }
+
   size_t GetSVEHeaderSize() { return sizeof(m_sve_header); }
 
   size_t GetPACMaskSize() { return sizeof(m_pac_mask); }
@@ -250,6 +264,8 @@ class NativeRegisterContextLinux_arm64
 
   size_t GetGCSBufferSize() { return sizeof(m_gcs_regs); }
 
+  size_t GetPOEBufferSize() { return sizeof(m_poe_regs); }
+
   llvm::Error ReadHardwareDebugInfo() override;
 
   llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override;
diff --git a/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp 
b/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
index dbbfc6a352e02..3cda0c80259de 100644
--- a/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
+++ b/lldb/source/Plugins/Process/Utility/LinuxSignals.cpp
@@ -137,6 +137,7 @@ void LinuxSignals::Reset() {
   ADD_SIGCODE(SIGSEGV, 11, SEGV_MAPERR,  1, "address not mapped to object", 
SignalCodePrintOption::Address);
   ADD_SIGCODE(SIGSEGV, 11, SEGV_ACCERR,  2, "invalid permissions for mapped 
object", SignalCodePrintOption::Address);
   ADD_SIGCODE(SIGSEGV, 11, SEGV_BNDERR,  3, "failed address bounds checks", 
SignalCodePrintOption::Bounds);
+  ADD_SIGCODE(SIGSEGV, 11, SEGV_PKUERR,  4, "failed protection key checks", 
SignalCodePrintOption::Address);
   ADD_SIGCODE(SIGSEGV, 11, SEGV_MTEAERR, 8, "async tag check fault");
   ADD_SIGCODE(SIGSEGV, 11, SEGV_MTESERR, 9, "sync tag check fault", 
SignalCodePrintOption::Address);
   ADD_SIGCODE(SIGSEGV, 11, SEGV_CPERR,  10, "control protection fault");
diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp 
b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
index 0233837f99d09..ecff3bfd6c898 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.cpp
@@ -67,6 +67,10 @@ bool RegisterContextPOSIX_arm64::IsGCS(unsigned reg) const {
   return m_register_info_up->IsGCSReg(reg);
 }
 
+bool RegisterContextPOSIX_arm64::IsPOE(unsigned reg) const {
+  return m_register_info_up->IsPOEReg(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 de46c628d836d..eba02cdd13699 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterContextPOSIX_arm64.h
@@ -60,6 +60,7 @@ class RegisterContextPOSIX_arm64 : public 
lldb_private::RegisterContext {
   bool IsMTE(unsigned reg) const;
   bool IsFPMR(unsigned reg) const;
   bool IsGCS(unsigned reg) const;
+  bool IsPOE(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/RegisterFlagsDetector_arm64.cpp 
b/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.cpp
index 330a24af67c4e..038bc946d1441 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.cpp
@@ -25,11 +25,45 @@
 #define HWCAP2_SME (1ULL << 23)
 #define HWCAP2_EBF16 (1ULL << 32)
 #define HWCAP2_FPMR (1ULL << 48)
+#define HWCAP2_POE (1ULL << 63)
 
 #define HWCAP3_MTE_STORE_ONLY (1ULL << 1)
 
 using namespace lldb_private;
 
+Arm64RegisterFlagsDetector::Fields
+Arm64RegisterFlagsDetector::DetectPORFields(uint64_t hwcap, uint64_t hwcap2,
+                                            uint64_t hwcap3) {
+  (void)hwcap;
+  (void)hwcap3;
+
+  if (!(hwcap2 & HWCAP2_POE))
+    return {};
+
+  static const FieldEnum por_perm_enum("por_perm_enum",
+                                       {
+                                           {0b0000, "No Access"},
+                                           {0b0001, "Read"},
+                                           {0b0010, "Execute"},
+                                           {0b0011, "Read, Execute"},
+                                           {0b0100, "Write"},
+                                           {0b0101, "Write, Read"},
+                                           {0b0110, "Write, Execute"},
+                                           {0b0111, "Read, Write, Execute"},
+                                       });
+
+  return {
+      {"Perm15", 60, 63, &por_perm_enum}, {"Perm14", 56, 59, &por_perm_enum},
+      {"Perm13", 52, 55, &por_perm_enum}, {"Perm12", 48, 51, &por_perm_enum},
+      {"Perm11", 44, 47, &por_perm_enum}, {"Perm10", 40, 43, &por_perm_enum},
+      {"Perm9", 36, 39, &por_perm_enum},  {"Perm8", 32, 35, &por_perm_enum},
+      {"Perm7", 28, 31, &por_perm_enum},  {"Perm6", 24, 27, &por_perm_enum},
+      {"Perm5", 20, 23, &por_perm_enum},  {"Perm4", 16, 19, &por_perm_enum},
+      {"Perm3", 12, 15, &por_perm_enum},  {"Perm2", 8, 11, &por_perm_enum},
+      {"Perm1", 4, 7, &por_perm_enum},    {"Perm0", 0, 3, &por_perm_enum},
+  };
+}
+
 Arm64RegisterFlagsDetector::Fields
 Arm64RegisterFlagsDetector::DetectFPMRFields(uint64_t hwcap, uint64_t hwcap2,
                                              uint64_t hwcap3) {
diff --git a/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.h 
b/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.h
index aec2bf9f4886f..3bd3a44f1c30d 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterFlagsDetector_arm64.h
@@ -69,6 +69,8 @@ class Arm64RegisterFlagsDetector {
                                  uint64_t hwcap3);
   static Fields DetectGCSFeatureFields(uint64_t hwcap, uint64_t hwcap2,
                                        uint64_t hwcap3);
+  static Fields DetectPORFields(uint64_t hwcap, uint64_t hwcap2,
+                                uint64_t hwcap3);
 
   struct RegisterEntry {
     RegisterEntry(llvm::StringRef name, unsigned size, DetectorFn detector)
@@ -78,7 +80,7 @@ class Arm64RegisterFlagsDetector {
     llvm::StringRef m_name;
     RegisterFlags m_flags;
     DetectorFn m_detector;
-  } m_registers[8] = {
+  } m_registers[9] = {
       RegisterEntry("cpsr", 4, DetectCPSRFields),
       RegisterEntry("fpsr", 4, DetectFPSRFields),
       RegisterEntry("fpcr", 4, DetectFPCRFields),
@@ -87,6 +89,7 @@ class Arm64RegisterFlagsDetector {
       RegisterEntry("fpmr", 8, DetectFPMRFields),
       RegisterEntry("gcs_features_enabled", 8, DetectGCSFeatureFields),
       RegisterEntry("gcs_features_locked", 8, DetectGCSFeatureFields),
+      RegisterEntry("por", 8, DetectPORFields),
   };
 
   // Becomes true once field detection has been run for all registers.
diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp 
b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
index 3b8d6a84c964c..df1940a15ec6e 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
@@ -101,6 +101,9 @@ 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)};
 
+static lldb_private::RegisterInfo g_register_infos_poe[] = {
+    DEFINE_EXTENSION_REG(por)};
+
 // Number of register sets provided by this context.
 enum {
   k_num_gpr_registers = gpr_w28 - gpr_x0 + 1,
@@ -114,6 +117,7 @@ enum {
   k_num_sme_register = 3,
   k_num_fpmr_register = 1,
   k_num_gcs_register = 3,
+  k_num_poe_register = 1,
   k_num_register_sets_default = 2,
   k_num_register_sets = 3
 };
@@ -229,6 +233,9 @@ static const lldb_private::RegisterSet g_reg_set_fpmr_arm64 
= {
 static const lldb_private::RegisterSet g_reg_set_gcs_arm64 = {
     "Guarded Control Stack Registers", "gcs", k_num_gcs_register, nullptr};
 
+static const lldb_private::RegisterSet g_reg_set_poe_arm64 = {
+    "Permission Overlay Registers", "poe", k_num_poe_register, nullptr};
+
 RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
     const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets)
     : lldb_private::RegisterInfoAndSetInterface(target_arch),
@@ -284,6 +291,9 @@ RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64(
       if (m_opt_regsets.AllSet(eRegsetMaskGCS))
         AddRegSetGCS();
 
+      if (m_opt_regsets.AllSet(eRegsetMaskPOE))
+        AddRegSetPOE();
+
       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();
@@ -462,6 +472,21 @@ void RegisterInfoPOSIX_arm64::AddRegSetGCS() {
   m_dynamic_reg_sets.back().registers = m_gcs_regnum_collection.data();
 }
 
+void RegisterInfoPOSIX_arm64::AddRegSetPOE() {
+  uint32_t poe_regnum = m_dynamic_reg_infos.size();
+  m_poe_regnum_collection.push_back(poe_regnum);
+  m_dynamic_reg_infos.push_back(g_register_infos_poe[0]);
+  m_dynamic_reg_infos[poe_regnum].byte_offset =
+      m_dynamic_reg_infos[poe_regnum - 1].byte_offset +
+      m_dynamic_reg_infos[poe_regnum - 1].byte_size;
+  m_dynamic_reg_infos[poe_regnum].kinds[lldb::eRegisterKindLLDB] = poe_regnum;
+
+  m_per_regset_regnum_range[m_register_set_count] =
+      std::make_pair(poe_regnum, poe_regnum + 1);
+  m_dynamic_reg_sets.push_back(g_reg_set_poe_arm64);
+  m_dynamic_reg_sets.back().registers = m_poe_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.
@@ -593,6 +618,10 @@ bool RegisterInfoPOSIX_arm64::IsGCSReg(unsigned reg) const 
{
   return llvm::is_contained(m_gcs_regnum_collection, reg);
 }
 
+bool RegisterInfoPOSIX_arm64::IsPOEReg(unsigned reg) const {
+  return llvm::is_contained(m_poe_regnum_collection, reg);
+}
+
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; }
 
 uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; }
@@ -630,3 +659,7 @@ uint32_t RegisterInfoPOSIX_arm64::GetFPMROffset() const {
 uint32_t RegisterInfoPOSIX_arm64::GetGCSOffset() const {
   return m_register_info_p[m_gcs_regnum_collection[0]].byte_offset;
 }
+
+uint32_t RegisterInfoPOSIX_arm64::GetPOEOffset() const {
+  return m_register_info_p[m_poe_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 bca5bff24bff0..b40235a3655b8 100644
--- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
+++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
@@ -34,6 +34,7 @@ class RegisterInfoPOSIX_arm64
     eRegsetMaskZT = 64,
     eRegsetMaskFPMR = 128,
     eRegsetMaskGCS = 256,
+    eRegsetMaskPOE = 512,
     eRegsetMaskDynamic = ~1,
   };
 
@@ -118,6 +119,8 @@ class RegisterInfoPOSIX_arm64
 
   void AddRegSetGCS();
 
+  void AddRegSetPOE();
+
   uint32_t ConfigureVectorLengthSVE(uint32_t sve_vq);
 
   void ConfigureVectorLengthZA(uint32_t za_vq);
@@ -138,6 +141,7 @@ class RegisterInfoPOSIX_arm64
   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 IsPOEPresent() const { return m_opt_regsets.AnySet(eRegsetMaskPOE); }
 
   bool IsSVEReg(unsigned reg) const;
   bool IsSVEZReg(unsigned reg) const;
@@ -151,6 +155,7 @@ class RegisterInfoPOSIX_arm64
   bool IsSMERegZT(unsigned reg) const;
   bool IsFPMRReg(unsigned reg) const;
   bool IsGCSReg(unsigned reg) const;
+  bool IsPOEReg(unsigned reg) const;
 
   uint32_t GetRegNumSVEZ0() const;
   uint32_t GetRegNumSVEFFR() const;
@@ -164,6 +169,7 @@ class RegisterInfoPOSIX_arm64
   uint32_t GetSMEOffset() const;
   uint32_t GetFPMROffset() const;
   uint32_t GetGCSOffset() const;
+  uint32_t GetPOEOffset() const;
 
 private:
   typedef std::map<uint32_t, std::vector<lldb_private::RegisterInfo>>
@@ -197,6 +203,7 @@ class RegisterInfoPOSIX_arm64
   std::vector<uint32_t> m_sme_regnum_collection;
   std::vector<uint32_t> m_fpmr_regnum_collection;
   std::vector<uint32_t> m_gcs_regnum_collection;
+  std::vector<uint32_t> m_poe_regnum_collection;
 };
 
 #endif
diff --git 
a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp 
b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp
index d5046d369ab2f..5684d87c622ac 100644
--- a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp
+++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.cpp
@@ -78,6 +78,13 @@ RegisterContextCorePOSIX_arm64::Create(Thread &thread, const 
ArchSpec &arch,
   if (gcs_data.GetByteSize() >= sizeof(gcs_regs))
     opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS);
 
+  DataExtractor poe_data = getRegset(notes, arch.GetTriple(), 
AARCH64_POE_Desc);
+  struct poe_regs {
+    uint64_t por_reg;
+  };
+  if (poe_data.GetByteSize() >= sizeof(poe_regs))
+    opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskPOE);
+
   auto register_info_up =
       std::make_unique<RegisterInfoPOSIX_arm64>(arch, opt_regsets);
   return std::unique_ptr<RegisterContextCorePOSIX_arm64>(
@@ -153,6 +160,9 @@ 
RegisterContextCorePOSIX_arm64::RegisterContextCorePOSIX_arm64(
   if (m_register_info_up->IsGCSPresent())
     m_gcs_data = getRegset(notes, target_triple, AARCH64_GCS_Desc);
 
+  if (m_register_info_up->IsPOEPresent())
+    m_poe_data = getRegset(notes, target_triple, AARCH64_POE_Desc);
+
   ConfigureRegisterContext();
 }
 
@@ -405,6 +415,11 @@ bool RegisterContextCorePOSIX_arm64::ReadRegister(const 
RegisterInfo *reg_info,
     assert(offset < m_fpmr_data.GetByteSize());
     value.SetFromMemoryData(*reg_info, m_fpmr_data.GetDataStart() + offset,
                             reg_info->byte_size, lldb::eByteOrderLittle, 
error);
+  } else if (IsPOE(reg)) {
+    offset = reg_info->byte_offset - m_register_info_up->GetPOEOffset();
+    assert(offset < m_poe_data.GetByteSize());
+    value.SetFromMemoryData(*reg_info, m_poe_data.GetDataStart() + offset,
+                            reg_info->byte_size, lldb::eByteOrderLittle, 
error);
   } else
     return false;
 
diff --git 
a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h 
b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h
index 6140f805ffc78..f6d6c522d836a 100644
--- a/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h
+++ b/lldb/source/Plugins/Process/elf-core/RegisterContextPOSIXCore_arm64.h
@@ -64,6 +64,7 @@ class RegisterContextCorePOSIX_arm64 : public 
RegisterContextPOSIX_arm64 {
   lldb_private::DataExtractor m_zt_data;
   lldb_private::DataExtractor m_fpmr_data;
   lldb_private::DataExtractor m_gcs_data;
+  lldb_private::DataExtractor m_poe_data;
 
   SVEState m_sve_state = SVEState::Unknown;
   uint16_t m_sve_vector_length = 0;
diff --git a/lldb/source/Plugins/Process/elf-core/RegisterUtilities.h 
b/lldb/source/Plugins/Process/elf-core/RegisterUtilities.h
index a5b0d788a1905..e8d4f8ddd7686 100644
--- a/lldb/source/Plugins/Process/elf-core/RegisterUtilities.h
+++ b/lldb/source/Plugins/Process/elf-core/RegisterUtilities.h
@@ -152,6 +152,10 @@ constexpr RegsetDesc AARCH64_GCS_Desc[] = {
     {llvm::Triple::Linux, llvm::Triple::aarch64, llvm::ELF::NT_ARM_GCS},
 };
 
+constexpr RegsetDesc AARCH64_POE_Desc[] = {
+    {llvm::Triple::Linux, llvm::Triple::aarch64, llvm::ELF::NT_ARM_POE},
+};
+
 constexpr RegsetDesc ARM_VFP_Desc[] = {
     {llvm::Triple::FreeBSD, llvm::Triple::arm, llvm::ELF::NT_ARM_VFP},
     {llvm::Triple::Linux, llvm::Triple::arm, llvm::ELF::NT_ARM_VFP},
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/Makefile 
b/lldb/test/API/linux/aarch64/permission_overlay/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/permission_overlay/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git 
a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py 
b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
new file mode 100644
index 0000000000000..f8fbc99ebc920
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -0,0 +1,129 @@
+"""
+Test lldb's support for the AArch64 Permission Overlay extension (POE), which
+is used to implement Linux's memory protection keys feature.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class AArch64LinuxPOE(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    EXPECTED_POR = "por = 0x0000000001234567"
+    EXPECTED_POR_FIELDS = (
+        "         = {\n"
+        "             Perm15 = No Access\n"
+        "             Perm14 = No Access\n"
+        "             Perm13 = No Access\n"
+        "             Perm12 = No Access\n"
+        "             Perm11 = No Access\n"
+        "             Perm10 = No Access\n"
+        "             Perm9 = No Access\n"
+        "             Perm8 = No Access\n"
+        "             Perm7 = No Access\n"
+        "             Perm6 = Read\n"
+        "             Perm5 = Execute\n"
+        "             Perm4 = Read, Execute\n"
+        "             Perm3 = Write\n"
+        "             Perm2 = Write, Read\n"
+        "             Perm1 = Write, Execute\n"
+        "             Perm0 = Read, Write, Execute\n"
+        "           }"
+    )
+
+    @skipUnlessArch("aarch64")
+    @skipUnlessPlatform(["linux"])
+    def test_poe_live(self):
+        if not self.isAArch64POE():
+            self.skipTest("POE 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"],
+        )
+
+        self.expect(
+            "register read --all",
+            substrs=[
+                "Permission Overlay Registers",
+                f"{self.EXPECTED_POR}",
+            ],
+        )
+
+        if self.hasXMLSupport():
+            self.expect(
+                "register read por",
+                substrs=[f"     {self.EXPECTED_POR}\n" + 
self.EXPECTED_POR_FIELDS],
+            )
+
+        # POR should be restored after expression evaluation.
+        self.expect("expression expr_function()", substrs=["$0 = 1"])
+        self.expect("register read por", substrs=[self.EXPECTED_POR])
+
+        # Not passing this to the application allows us to fix the permissions
+        # using lldb, then continue to a normal exit.
+        self.runCmd("process handle SIGSEGV --pass false")
+
+        self.expect(
+            "continue",
+            substrs=[
+                "stop reason = signal SIGSEGV: failed protection key checks 
(fault address="
+            ],
+        )
+
+        # This fault should have happened due to the write, not the read.
+        self.assertEqual(
+            self.dbg.GetSelectedTarget()
+            .GetProcess()
+            .GetSelectedThread()
+            .GetSelectedFrame()
+            .GetFunctionName(),
+            "cause_write_fault",
+        )
+
+        # Allow writes so we can continue. This value has permission 6 changed
+        # from read only (1) to read+write (4).
+        self.runCmd("register write por 0x4234567")
+
+        self.expect("continue", substrs=["exited with status = 0"])
+
+    @skipIfLLVMTargetMissing("AArch64")
+    def test_poe_core(self):
+        # Core was generated by running the test program on a system with POE
+        # and coredump_filter set to 0.
+        self.runCmd("target create --core corefile")
+
+        self.expect("process status", substrs=["SIGSEGV: failed protection key 
checks"])
+
+        self.expect(
+            "register read --all",
+            substrs=[
+                "Permission Overlay Registers",
+                f"{self.EXPECTED_POR}",
+            ],
+        )
+
+        if self.hasXMLSupport():
+            self.expect(
+                "register read por",
+                substrs=[f"     {self.EXPECTED_POR}\n" + 
self.EXPECTED_POR_FIELDS],
+            )
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/corefile 
b/lldb/test/API/linux/aarch64/permission_overlay/corefile
new file mode 100644
index 
0000000000000000000000000000000000000000..d7483aa6f3c216631b7b260ecec5ecf055c3d2ed
GIT binary patch
literal 393216
zcmeI$eQ*?Ip1|?nOiz+Qf(B$y#8??fJR*w&$m)_?x6(`uFITyE7vmPSYh4m1kz_+=
zGzk{I6(hz4jMf_V>Z*jT5|Fn^dAnW}p0anhAgjoUf82)i%H8|JAg+4F?J0P!MAW(8
zGu=-z6Uf48ZCzd6x1s0h=jGSW)1T+*?wPEwty>xl1jKG3c|pc<BCf5-NnKei*ZBF6
zpH>#;G{`wTTbz^8L)+f;QIM^&z3u+ivxArAH_of~+L=$UD;u}xOUC`E@xuK%{usvk
zE6N+k#^%p5SNLsm6FZ)X;x>KBxXoWO?y)Z!*Z(Es9>3(c{P}xgg1F#$^N_z@emFtg
z)#t_K_qT6?xbx>PKQ4XAarxsMoFMM}>&x%&*$Lv#A7_4le=|Ycq>H%x-2`zL>+gjL
z;%1%KpLg%u^SwDioUQj#^O^7Rt4of{zi)qi$#MDP{I_v&_6cUwv2l9y;C+g`H7+jy
zy2O9Vno=*qyGoJaadAGQoc~<%B5d5r3F7kmHM#t_ogZW__&m$+&x~=C2AlYr+Zt-k
zs3w~af6G0#Wxv)X-l)4xZTZ5eJ@V4XxO{$XZq9p#uKfE8U#l&*Z;|n7_2{X*^E5A?
z=ovEFFnci7&)(g!)iaLdzjIbj+Lbl5(%Y}~>fJ-$IPaX~zuTV6fA+Ndakjnf^`DEp
z^X<CKi%a?W-pQly$Y#^l_LrR-&6^psb0ZC=g>HpzHhEtw&2HzyrY+Ir+TLsn?;|=t
zTN24B`bvjpY$@GWEL(BA9h+S%_72jOmA7{7ivCI+|DNFNm=w?SW5+aWw7qxVF7oqt
zPxB5+{QSnuzz4>CY>eJ4*frAXx3hPTE8d*hYhCQun=zQpK9#*^l_~M=|FAjC*=)O1
zk#|08Xy(*x_G^~SPw|Sgb8ZeVAD6$%&)dYlHQ%4?t-YCk#lt7ANrh%e>RLZ<ucOS*
zf0*&w+iyf?{doKRu+(p7(}<rRpBRs2dM&keE0)?hvDYxR((U@szyCyRe($*Ng7*H-
zzwbnB-g~Fd7#)m#SC4=9+PwE}^=8}z*>~;ucc;yJ@5ZsYwC~FC?>?LN-ffZ5@z{5r
z_wE{7qxKy!{yAv#?_}>M`we4$d$Ox-erwCUJ$H93Sm@p0a^b?YJ>B!ybgY`+($XDo
zzxf+Ctm#OsU3bIk#9C7tkM<;^H_QC5-e`3^-qd5xw(ufnyX1ylmom0XYSh!)8vpv&
zv!{zLh%Q=ikrDnOo45M@{%l+v?1BN=?-wPy7|eRG_m+K5*i^)S^2_bZYwPO1ozk|h
z?xUM)kGv;9$s8|lpLaI3=QHnKTb6C<-!!(~p6!1vwKs7jZv?K*nd;o#mX9C5J&mpZ
zznYr&nR&+rbb9Mn)ZejEkDE*Kf18;$VZGpBPMZ?@BGS75&zrSzusLrT7#n4gP>xMT
zH@oHvbIQHoyZv+Ao&BC=-k+D{w6WhcvgTShDW}d49F4mor*3s^dG>ZSaps+0l2d2n
zND)8)0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL
zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~
z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**
z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0
z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{
z1Q0;r(goJn)-4SN0;2(rydYya5!bKTbopo#En6&(eqJt9vTX~-s?Iw)G{q~jX~b_4
z)HI~~_F}JKd^$P^e=uP`&cvAgNppe=q>Wy&*wnN&o1=S??q;m!l!dzIZsv5RjoECB
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

literal 0
HcmV?d00001

diff --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c 
b/lldb/test/API/linux/aarch64/permission_overlay/main.c
new file mode 100644
index 0000000000000..51027d05f3079
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -0,0 +1,128 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#ifndef SYS_pkey_alloc
+#include <linux/unistd.h>
+#endif
+
+// Normally this functionality is provided by the libc as the pkey_* functions.
+// However, POE and therefore protection keys are new to AArch64 so we need to
+// be able to build with a libc without support for it.
+
+static uint64_t por_read(void) {
+  uint64_t por;
+  __asm__ volatile("mrs %0, S3_3_C10_C2_4" /*POR_EL0*/ : "=r"(por));
+  return por;
+}
+
+static void por_write(uint64_t por) {
+  __asm__ volatile("msr S3_3_C10_C2_4, %0\n" /*POR_EL0*/
+                   "isb" ::"r"(por)
+                   : "memory");
+}
+
+// Syscall functions have _syscall suffix in case the C library does
+// define them.
+
+static int pkey_alloc_syscall(unsigned flags, unsigned access_rights) {
+  long res = syscall(SYS_pkey_alloc, flags, access_rights);
+  if (res < 0)
+    exit(2);
+
+  return (int)res;
+}
+
+static int pkey_free_syscall(int pkey) {
+  long res = syscall(SYS_pkey_free, pkey);
+  if (res < 0)
+    exit(2);
+
+  return (int)res;
+}
+
+static int pkey_mprotect_syscall(void *addr, size_t len, int prot, int pkey) {
+  long res = syscall(SYS_pkey_mprotect, addr, len, prot, pkey);
+  if (res < 0)
+    exit(2);
+
+  return (int)res;
+}
+
+static inline uint64_t set_perm(uint64_t por, int pkey, uint8_t perm) {
+  // Each permissions key is 4 bits.
+  const unsigned shift = (unsigned)pkey * 4u;
+  const uint64_t mask = 0xFULL << shift;
+  return (por & ~mask) | ((uint64_t)(perm & 0xF) << shift);
+}
+
+static void cause_write_fault(char *buffer) { buffer[0] = '?'; }
+
+int expr_function() {
+  por_write(set_perm(por_read(), 1, 0));
+  return 1;
+}
+
+int main(void) {
+  // pkeys have 2 parts. First bits in the page table tagging each page
+  // with the key that page uses. Second the register holding the permissions
+  // for that pkey.
+  // On AArch64 we have 3 page table bits and space for 16 sets of permissions
+  // in POR. Page table space is the limiting factor, so we can have a maximum
+  // of 8 pkeys. To provide a default pkey, the kernel uses key 0.
+  // Which leaves 7 keys available for programs to allocate.
+
+  const size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
+  // pkeys can only subtract from the set of permissions in the page table,
+  // so we set the page table to allow everything.
+  const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
+  const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
+
+  // Later we will use this to cause a protection key fault.
+  char *read_only_page = NULL;
+
+  // Allocate all possible pkeys. They can in theory be in a random order, so
+  // we allocate them all up front instead of setting permissions as we go.
+#define NUM_KEYS 7
+  for (unsigned i = 0; i < NUM_KEYS; ++i) {
+    int pkey = pkey_alloc_syscall(/*flags=*/0, /*access_rights=*/0);
+    // Allocate a page to attach to that pkey.
+    char *page = mmap(NULL, page_size, prot, flags, -1, 0);
+    if (page == MAP_FAILED)
+      exit(2);
+    // Attach the pkey to the page.
+    pkey_mprotect_syscall(page, page_size, prot, pkey);
+
+    if (pkey == 6)
+      read_only_page = page;
+  }
+
+  // Set permissions to result in a por value of 0x...01234567. The
+  // final 7 is permission set 0 already set up by the kernel.
+  // Allocated keys start at 1.
+  for (unsigned i = 1; i < (NUM_KEYS + 1); ++i) {
+    // pkey 0 is already set to read+write+execute, we will set all other
+    // valid encodings. 0 is no access and 7 is read+write_execute.
+    uint8_t perm = NUM_KEYS - i;
+    por_write(set_perm(por_read(), i, perm));
+  }
+
+  // This page should allow reads.
+  volatile char c = read_only_page[0]; // Set break point at this line.
+  (void)c;
+
+  // This is a workaround for a kernel bug where if you have not entered the
+  // kernel since updating por, when you crash, the core file will have the
+  // last saved por value rather than the latest one.
+  sleep(1);
+
+  // Will segfault if you try to write to it. This is done via a function so
+  // that we can find the functions name in the backtrace later and make sure
+  // it was not the read above that caused the fault.
+  cause_write_fault(read_only_page);
+
+  return 0;
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to