[Lldb-commits] [lldb] 4c66205 - [lldb][Linux] Add overlay and effective permissions to "memory region" (#184115)

via lldb-commits lldb-commits at lists.llvm.org
Fri Apr 24 05:33:34 PDT 2026


Author: David Spickett
Date: 2026-04-24T12:33:29Z
New Revision: 4c66205b070cdb8d18f9456ebbb5c592b5134487

URL: https://github.com/llvm/llvm-project/commit/4c66205b070cdb8d18f9456ebbb5c592b5134487
DIFF: https://github.com/llvm/llvm-project/commit/4c66205b070cdb8d18f9456ebbb5c592b5134487.diff

LOG: [lldb][Linux] Add overlay and effective permissions to "memory region" (#184115)

In this change I'm extending the "memory region" command to show users
the
overlay permissions that a protection key refers to, and the result of
applying that overlay to the page table permissions.

For example, protection key 0 refers to Perm0 in the por register.
```
(lldb) register read por 
             Perm0 = Read, Write, Execute
```
This is the default key, so many regions use it. 
```
(lldb) memory region --all
<...>
[0x000ffffff7db0000-0x000ffffff7f40000) r-x /usr/lib/aarch64-linux-gnu/libc.so.6 PT_LOAD[0]
protection key: 0 (rwx, effective: r-x)
```
Protection keys can only change what was already enabled in the 
page table. So we start with read and execute. Then a read/write/execute
overlay
is applied. We cannot add write, so the result is read and execute.

Here's an example of its use with a real crash (output edited):
```
(lldb) c
* thread #1, name = 'test.o', stop reason = signal SIGSEGV: failed protection key checks (fault address=0xffffff7d60000)
-> 106    read_only_page[0] = '?';
(lldb) memory region 0xffffff7d60000
[0x000ffffff7d60000-0x000ffffff7d70000) rw- 
protection key: 6 (r--, effective: r--)
(lldb) register read por 
             Perm6 = Read
```
The calculation of permissions is implemented by a new ABI method.
It's in ABI for 2 reasons:
* These overlays are usually in a register (X86 and AArch64 are)
  and that register name is architecture specific.
* The way the overlay values apply may differ between architecture.
  AArch64 treats a set bit as adding a permission, but some may 
  treat it as removing.

Technically this is dependent on operating system and architecture.
However, so are the methods for removing non-address bits, and those
are in ABI too.

To test this I have changed the allocations in the test program
to use read+execute permissions by default. With read+write+execute
I could not observe that the overlay only changes enabled permissions.

Added: 
    

Modified: 
    lldb/include/lldb/Target/ABI.h
    lldb/source/Commands/CommandObjectMemory.cpp
    lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
    lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
    lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
    lldb/test/API/linux/aarch64/permission_overlay/main.c

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/ABI.h b/lldb/include/lldb/Target/ABI.h
index 1a1f1724222e3..3e2e7808c6c22 100644
--- a/lldb/include/lldb/Target/ABI.h
+++ b/lldb/include/lldb/Target/ABI.h
@@ -152,6 +152,33 @@ class ABI : public PluginInterface {
 
   static lldb::ABISP FindPlugin(lldb::ProcessSP process_sp, const ArchSpec &arch);
 
+  struct MemoryPermissions {
+    // Both of these are sets of lldb::Permissions values.
+    // Overlay are the permissions being applied to the original permissions.
+    uint32_t overlay;
+    // Effective is the result of applying the overlay to the original
+    // permissions. Calculating this is done by the plugin because some
+    // permission overlays are done as positive (add permissions) and some as
+    // negative (remove permissions).
+    uint32_t effective;
+  };
+
+  /// Get the effective memory permissions that result when the permissions
+  /// referred to by a protection key are applied to the original permissions.
+  ///
+  /// This is intended for architectures that have some sort of permission
+  /// overlay system. Where the protection key is used to look up a set of
+  /// permissions that modifies the original permissions.
+  ///
+  /// \returns the overlay permissions (that the protection key refers to) and
+  ///   the effective permissions. If the target does not have an overlay
+  ///   system, or it does and the protection key is invalid, returns nullopt.
+  virtual std::optional<MemoryPermissions>
+  GetMemoryPermissions(lldb_private::RegisterContext &reg_ctx,
+                       unsigned protection_key, uint32_t original_permissions) {
+    return std::nullopt;
+  }
+
 protected:
   ABI(lldb::ProcessSP process_sp, std::unique_ptr<llvm::MCRegisterInfo> info_up)
       : m_process_wp(process_sp), m_mc_register_info_up(std::move(info_up)) {

diff  --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 00a254f60ae19..ac91b63e378a8 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1689,8 +1689,28 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
     LazyBool is_shadow_stack = range_info.IsShadowStack();
     if (is_shadow_stack == eLazyBoolYes)
       result.AppendMessage("shadow stack: yes");
-    if (std::optional<unsigned> protection_key = range_info.GetProtectionKey())
-      result.AppendMessageWithFormatv("protection key: {0}", *protection_key);
+    if (std::optional<unsigned> protection_key =
+            range_info.GetProtectionKey()) {
+      Stream &strm = result.GetOutputStream();
+      strm << llvm::formatv("protection key: {0}", *protection_key);
+
+      if (const lldb::ABISP &abi = target.GetProcessSP()->GetABI()) {
+        if (auto permissions = abi->GetMemoryPermissions(
+                *m_exe_ctx.GetRegisterContext(), *protection_key,
+                range_info.GetLLDBPermissions())) {
+          strm << llvm::formatv(
+              " ({0}{1}{2}, effective: {3}{4}{5})",
+              permissions->overlay & lldb::ePermissionsReadable ? 'r' : '-',
+              permissions->overlay & lldb::ePermissionsWritable ? 'w' : '-',
+              permissions->overlay & lldb::ePermissionsExecutable ? 'x' : '-',
+              permissions->effective & lldb::ePermissionsReadable ? 'r' : '-',
+              permissions->effective & lldb::ePermissionsWritable ? 'w' : '-',
+              permissions->effective & lldb::ePermissionsExecutable ? 'x'
+                                                                    : '-');
+        }
+      }
+      strm.PutChar('\n');
+    }
 
     const std::optional<std::vector<addr_t>> &dirty_page_list =
         range_info.GetDirtyPageList();

diff  --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
index aa9c20b6bb2cf..83a777da3237e 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
@@ -884,3 +884,49 @@ void ABISysV_arm64::Initialize() {
 void ABISysV_arm64::Terminate() {
   PluginManager::UnregisterPlugin(CreateInstance);
 }
+
+std::optional<ABISysV_arm64::MemoryPermissions>
+ABISysV_arm64::GetMemoryPermissions(lldb_private::RegisterContext &reg_ctx,
+                                    unsigned protection_key,
+                                    uint32_t original_permissions) {
+  // The presence of the POR register means we have the Permission Overlay
+  // Extension.
+  // See Arm Architecture Reference manual "POR_EL0, Permission Overlay Register
+  // 0 (EL0)".
+  const RegisterInfo *por_info = reg_ctx.GetRegisterInfoByName("por");
+  if (!por_info)
+    return std::nullopt;
+
+  uint64_t por_value =
+      reg_ctx.ReadRegisterAsUnsigned(por_info, LLDB_INVALID_ADDRESS);
+  if (por_value == LLDB_INVALID_ADDRESS)
+    return std::nullopt;
+
+  // POR contains 16, 4-bit permission sets (though Linux limits this to 8
+  // useable sets).
+  if (protection_key >= 16)
+    return std::nullopt;
+
+  // Bit 3 - reserved, bit 2 - write, bit 1 - execute, bit 0 - read.
+  const uint64_t por_permissions = (por_value >> (protection_key * 4)) & 0xf;
+  uint32_t overlay = 0;
+  if (por_permissions & 4)
+    overlay |= lldb::ePermissionsWritable;
+  if (por_permissions & 2)
+    overlay |= lldb::ePermissionsExecutable;
+  if (por_permissions & 1)
+    overlay |= lldb::ePermissionsReadable;
+
+  uint32_t effective = original_permissions;
+
+  // Permission overlays cannot add permissions, they can only keep, or disable,
+  // what was originally set.
+  if (!(overlay & lldb::ePermissionsWritable))
+    effective &= ~lldb::ePermissionsWritable;
+  if (!(overlay & lldb::ePermissionsExecutable))
+    effective &= ~lldb::ePermissionsExecutable;
+  if (!(overlay & lldb::ePermissionsReadable))
+    effective &= ~lldb::ePermissionsReadable;
+
+  return MemoryPermissions{overlay, effective};
+}

diff  --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
index 213fbf7417b2c..ae09e6dc87bd1 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
@@ -81,6 +81,14 @@ class ABISysV_arm64 : public ABIAArch64 {
   lldb::addr_t FixCodeAddress(lldb::addr_t pc) override;
   lldb::addr_t FixDataAddress(lldb::addr_t pc) override;
 
+  // If the Permission Overlay Extension is present, use the protection key
+  // to look up overlay permissions in por_el0 and apply them to the original
+  // permissions.
+  virtual std::optional<MemoryPermissions>
+  GetMemoryPermissions(lldb_private::RegisterContext &reg_ctx,
+                       unsigned protection_key,
+                       uint32_t original_permissions) override;
+
 protected:
   lldb::ValueObjectSP
   GetReturnValueObjectImpl(lldb_private::Thread &thread,

diff  --git a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index f4e67b2f402e0..affd66514b9bf 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -82,15 +82,25 @@ def test_poe_live(self):
         # Unmapped region has no key (not even default).
         self.expect("memory region 0", substrs=["protection key:"], matching=False)
 
-        # The region has base permissions rwx, which is what we see here.
+        # The region has base permissions r-x, and overlay is r--. The result
+        # is that execution is disabled.
         self.expect(
-            "memory region read_only_page", substrs=["rwx", "protection key: 6"]
+            "memory region read_only_page",
+            substrs=["rw-", "protection key: 6 (r--, effective: r--)"],
+        )
+        # A region not assigned to a protection key has the default key 0. This
+        # key is rwx, but overlays cannot add permissions not already in the
+        # page table. So the execute permission is not enabled.
+        self.expect(
+            "memory region key_zero_page",
+            substrs=["rw-", "protection key: 0 (rwx, effective: rw-)"],
         )
-        # A region not assigned to a protection key has the default key 0.
-        self.expect("memory region key_zero_page", substrs=["rwx", "protection key: 0"])
 
-        # Protection keys should be on their own line.
-        self.expect("memory region --all", patterns=["\nprotection key: [0-9]+\n"])
+        # Overlay permissions are on their own line.
+        self.expect(
+            "memory region --all",
+            patterns=["\nprotection key: [0-9]+ \([rwx-]{3}, effective: [rwx-]{3}\)\n"],
+        )
 
         # Not passing this to the application allows us to fix the permissions
         # using lldb, then continue to a normal exit.

diff  --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c b/lldb/test/API/linux/aarch64/permission_overlay/main.c
index ec2c0088b7084..5eb4782b9a6ca 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/main.c
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -76,9 +76,10 @@ int main(void) {
   // 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;
+  // pkeys can only subtract from the set of permissions in the page table.
+  // So we leave out execute here to check later that an overlay does not
+  // enable execution.
+  const int prot = PROT_READ | PROT_WRITE;
   const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 
   // This page will have the default key 0.


        


More information about the lldb-commits mailing list