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

>From ce05713c28e35020c989edb9e41c0b9c6833c309 Mon Sep 17 00:00:00 2001
From: David Spickett <[email protected]>
Date: Tue, 20 Jan 2026 14:55:10 +0000
Subject: [PATCH 1/2] [lldb][Linux] Read memory protection keys for memory
 regions

Memory protection keys (https://docs.kernel.org/core-api/protection-keys.html)
are implemented using two things:
* A key value attached to each page table entry.
* A set of permissions stored somewhere else (in a register on AArch64 and X86),
  which is indexed into by that protection key.

So far I have updated LLDB to show the permissions part on AArch64 Linux
by reading the por register.

Now I am adding the ability to see which key each memory region is using.

The key is parsed from the /proc/.../smaps file, and so will only be present
for live processes, not core files.

This is sent as part of the qMemoryRegionInfo response as a new
"protection-key" key. As far as I know "memory protection keys" are Linux
specific, but I don't know of a good generic name for it, so I've
copied what smaps calls it.

I have updated the "memory region" command to show this key. A lot of the
time this will be the default 0 key. I considered hiding this, but decided
that for this initial support it's better to be explicit and verbose.

(this also prevents a Linux specific detail being added to a top level
command)

I have not yet added a way to see the protection key and effective permissions
in the same place, but I think users likely will want to do things like
that. So I have added 2 new methods to SBMemoryRegionInfo, HasProtectionKey
and GetProtectionKey. Users should not trust the result of GetProtectionKey
unless HasProtectionKey is true.
---
 lldb/docs/resources/lldbgdbremote.md          |   2 +
 lldb/include/lldb/API/SBMemoryRegionInfo.h    |  13 ++
 lldb/include/lldb/Target/MemoryRegionInfo.h   |  14 +-
 lldb/source/API/SBMemoryRegionInfo.cpp        |  12 ++
 lldb/source/Commands/CommandObjectMemory.cpp  |   4 +
 .../Plugins/Process/Utility/LinuxProcMaps.cpp |   4 +
 .../GDBRemoteCommunicationClient.cpp          |   4 +
 .../GDBRemoteCommunicationServerLLGS.cpp      |   3 +
 lldb/source/Target/MemoryRegionInfo.cpp       |  16 +--
 .../permission_overlay/TestAArch64LinuxPOE.py |  54 ++++++++
 .../linux/aarch64/permission_overlay/main.c   |   5 +
 .../Process/Utility/LinuxProcMapsTest.cpp     | 126 +++++++++++++-----
 .../MemoryTagManagerAArch64MTETest.cpp        |   2 +-
 .../GDBRemoteCommunicationClientTest.cpp      |  20 +++
 .../Process/minidump/MinidumpParserTest.cpp   | 114 ++++++++--------
 15 files changed, 292 insertions(+), 101 deletions(-)

diff --git a/lldb/docs/resources/lldbgdbremote.md 
b/lldb/docs/resources/lldbgdbremote.md
index 9aa7ad2259a6a..148b3a03aff86 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -1443,6 +1443,8 @@ tuples to return are:
   listed (`dirty-pages:;`) indicates no dirty pages in
   this memory region.  The *absence* of this key means
   that this stub cannot determine dirty pages.
+* `protection-key:<key>`- where `<key>` is an unsigned integer memory 
protection
+  key.
 
 If the address requested is not in a mapped region (e.g. we've jumped through
 a NULL pointer and are at 0x0) currently lldb expects to get back the size
diff --git a/lldb/include/lldb/API/SBMemoryRegionInfo.h 
b/lldb/include/lldb/API/SBMemoryRegionInfo.h
index dc5aa0858e1e3..9174675d2a11c 100644
--- a/lldb/include/lldb/API/SBMemoryRegionInfo.h
+++ b/lldb/include/lldb/API/SBMemoryRegionInfo.h
@@ -111,6 +111,19 @@ class LLDB_API SBMemoryRegionInfo {
   ///     or 0 if this information is unavailable.
   int GetPageSize();
 
+  /// Returns whether this memory region has a memory protection key.
+  ///
+  /// \return
+  ///     True if the region memory region has a memory protection key.
+  bool HasProtectionKey();
+
+  /// Returns the memory protection key of the memory region.
+  ///
+  /// \return
+  ///     The memory protection key of the region. This value is only valid if
+  ///     HasProtectionKey() is true.
+  uint32_t GetProtectionKey();
+
   bool operator==(const lldb::SBMemoryRegionInfo &rhs) const;
 
   bool operator!=(const lldb::SBMemoryRegionInfo &rhs) const;
diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h 
b/lldb/include/lldb/Target/MemoryRegionInfo.h
index dc37a7dbeda52..1513d93f1ab22 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -29,11 +29,13 @@ class MemoryRegionInfo {
                    OptionalBool execute, OptionalBool shared,
                    OptionalBool mapped, ConstString name, OptionalBool flash,
                    lldb::offset_t blocksize, OptionalBool memory_tagged,
-                   OptionalBool stack_memory, OptionalBool shadow_stack)
+                   OptionalBool stack_memory, OptionalBool shadow_stack,
+                   std::optional<unsigned> protection_key)
       : m_range(range), m_read(read), m_write(write), m_execute(execute),
         m_shared(shared), m_mapped(mapped), m_name(name), m_flash(flash),
         m_blocksize(blocksize), m_memory_tagged(memory_tagged),
-        m_is_stack_memory(stack_memory), m_is_shadow_stack(shadow_stack) {}
+        m_is_stack_memory(stack_memory), m_is_shadow_stack(shadow_stack),
+        m_protection_key(protection_key) {}
 
   RangeType &GetRange() { return m_range; }
 
@@ -57,6 +59,8 @@ class MemoryRegionInfo {
 
   OptionalBool IsShadowStack() const { return m_is_shadow_stack; }
 
+  std::optional<unsigned> GetProtectionKey() const { return m_protection_key; }
+
   void SetReadable(OptionalBool val) { m_read = val; }
 
   void SetWritable(OptionalBool val) { m_write = val; }
@@ -81,6 +85,8 @@ class MemoryRegionInfo {
 
   void SetIsShadowStack(OptionalBool val) { m_is_shadow_stack = val; }
 
+  void SetProtectionKey(std::optional<unsigned> key) { m_protection_key = key; 
}
+
   // Get permissions as a uint32_t that is a mask of one or more bits from the
   // lldb::Permissions
   uint32_t GetLLDBPermissions() const {
@@ -111,7 +117,8 @@ class MemoryRegionInfo {
            m_memory_tagged == rhs.m_memory_tagged &&
            m_pagesize == rhs.m_pagesize &&
            m_is_stack_memory == rhs.m_is_stack_memory &&
-           m_is_shadow_stack == rhs.m_is_shadow_stack;
+           m_is_shadow_stack == rhs.m_is_shadow_stack &&
+           m_protection_key == rhs.m_protection_key;
   }
 
   bool operator!=(const MemoryRegionInfo &rhs) const { return !(*this == rhs); 
}
@@ -154,6 +161,7 @@ class MemoryRegionInfo {
   OptionalBool m_memory_tagged = eDontKnow;
   OptionalBool m_is_stack_memory = eDontKnow;
   OptionalBool m_is_shadow_stack = eDontKnow;
+  std::optional<unsigned> m_protection_key = std::nullopt;
   int m_pagesize = 0;
   std::optional<std::vector<lldb::addr_t>> m_dirty_pages;
 };
diff --git a/lldb/source/API/SBMemoryRegionInfo.cpp 
b/lldb/source/API/SBMemoryRegionInfo.cpp
index cd25be5d52769..99bb6f0de4915 100644
--- a/lldb/source/API/SBMemoryRegionInfo.cpp
+++ b/lldb/source/API/SBMemoryRegionInfo.cpp
@@ -160,6 +160,18 @@ int SBMemoryRegionInfo::GetPageSize() {
   return m_opaque_up->GetPageSize();
 }
 
+bool SBMemoryRegionInfo::HasProtectionKey() {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_up->GetProtectionKey() != std::nullopt;
+}
+
+uint32_t SBMemoryRegionInfo::GetProtectionKey() {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_up->GetProtectionKey().value_or(0);
+}
+
 bool SBMemoryRegionInfo::GetDescription(SBStream &description) {
   LLDB_INSTRUMENT_VA(this, description);
 
diff --git a/lldb/source/Commands/CommandObjectMemory.cpp 
b/lldb/source/Commands/CommandObjectMemory.cpp
index 93b6c1751b121..909761094f94b 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1694,6 +1694,10 @@ class CommandObjectMemoryRegion : public 
CommandObjectParsed {
     MemoryRegionInfo::OptionalBool is_shadow_stack = 
range_info.IsShadowStack();
     if (is_shadow_stack == MemoryRegionInfo::OptionalBool::eYes)
       result.AppendMessage("shadow stack: yes");
+    if (std::optional<unsigned> protection_key =
+            range_info.GetProtectionKey())
+      result.AppendMessageWithFormat("protection key: %" PRIu32 "\n",
+                                     *protection_key);
 
     const std::optional<std::vector<addr_t>> &dirty_page_list =
         range_info.GetDirtyPageList();
diff --git a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp 
b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
index 2ed896327a2f8..eea0bf245877d 100644
--- a/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
+++ b/lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp
@@ -174,6 +174,10 @@ void lldb_private::ParseLinuxSMapRegions(llvm::StringRef 
linux_smap,
               region->SetMemoryTagged(MemoryRegionInfo::eYes);
             else if (flag == "ss")
               region->SetIsShadowStack(MemoryRegionInfo::eYes);
+        } else if (name == "ProtectionKey") {
+          unsigned key = 0;
+          if (!value.ltrim().getAsInteger(10, key))
+            region->SetProtectionKey(key);
         }
       } else {
         // Orphaned settings line
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 738e4013b6154..c639090ebe9af 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -1677,6 +1677,10 @@ Status GDBRemoteCommunicationClient::GetMemoryRegionInfo(
               dirty_page_list.push_back(page);
           }
           region_info.SetDirtyPageList(dirty_page_list);
+        } else if (name == "protection-key") {
+          unsigned protection_key = 0;
+          if (!value.getAsInteger(10, protection_key))
+            region_info.SetProtectionKey(protection_key);
         }
       }
 
diff --git 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 2f62415446b7a..72b6d7adbbf96 100644
--- 
a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ 
b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -2907,6 +2907,9 @@ 
GDBRemoteCommunicationServerLLGS::Handle_qMemoryRegionInfo(
       response.PutStringAsRawHex8(name.GetStringRef());
       response.PutChar(';');
     }
+
+    if (std::optional<unsigned> protection_key = 
region_info.GetProtectionKey())
+      response.Printf("protection-key:%" PRIu32 ";", *protection_key);
   }
 
   return SendPacketNoLock(response.GetString());
diff --git a/lldb/source/Target/MemoryRegionInfo.cpp 
b/lldb/source/Target/MemoryRegionInfo.cpp
index 979e45ad023af..7bdb3dc4f3168 100644
--- a/lldb/source/Target/MemoryRegionInfo.cpp
+++ b/lldb/source/Target/MemoryRegionInfo.cpp
@@ -12,14 +12,14 @@ using namespace lldb_private;
 
 llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS,
                                             const MemoryRegionInfo &Info) {
-  return OS << llvm::formatv("MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, "
-                             "{5}, `{6}`, {7}, {8}, {9}, {10}, {11})",
-                             Info.GetRange().GetRangeBase(),
-                             Info.GetRange().GetRangeEnd(), Info.GetReadable(),
-                             Info.GetWritable(), Info.GetExecutable(),
-                             Info.GetMapped(), Info.GetName(), Info.GetFlash(),
-                             Info.GetBlocksize(), Info.GetMemoryTagged(),
-                             Info.IsStackMemory(), Info.IsShadowStack());
+  return OS << llvm::formatv(
+             "MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, "
+             "{5}, `{6}`, {7}, {8}, {9}, {10}, {11}, {12})",
+             Info.GetRange().GetRangeBase(), Info.GetRange().GetRangeEnd(),
+             Info.GetReadable(), Info.GetWritable(), Info.GetExecutable(),
+             Info.GetMapped(), Info.GetName(), Info.GetFlash(),
+             Info.GetBlocksize(), Info.GetMemoryTagged(), Info.IsStackMemory(),
+             Info.IsShadowStack(), Info.GetProtectionKey());
 }
 
 void llvm::format_provider<MemoryRegionInfo::OptionalBool>::format(
diff --git 
a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py 
b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index 056267a2dc900..3b4bd8e55cf47 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -79,6 +79,50 @@ def test_poe_live(self):
         self.expect("expression expr_function()", substrs=["$0 = 1"])
         self.expect("register read por", substrs=[self.EXPECTED_POR])
 
+        # 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.
+        self.expect(
+            "memory region read_only_page", substrs=["rwx", "protection key: 
6"]
+        )
+        # 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 are also in SBMemoryRegionInfo.
+        process = self.dbg.GetSelectedTarget().GetProcess()
+        info = lldb.SBMemoryRegionInfo()
+
+        frame = (
+            self.dbg.GetSelectedTarget()
+            .GetProcess()
+            .GetSelectedThread()
+            .GetSelectedFrame()
+        )
+
+        err = lldb.SBError()
+        read_only_addr = frame.GetValueForVariablePath(
+            "read_only_page"
+        ).GetValueAsUnsigned(err)
+        self.assertTrue(err.Success())
+        key_zero_addr = frame.GetValueForVariablePath(
+            "key_zero_page"
+        ).GetValueAsUnsigned(err)
+        self.assertTrue(err.Success())
+
+        region_api_info = [
+            # An unmapped region will have no key at all.
+            # The getter returns 0 as a default, but should not be trusted.
+            (0, False, 0),
+            (read_only_addr, True, 6),
+            (key_zero_addr, True, 0),
+        ]
+        for addr, valid, key in region_api_info:
+            err = process.GetMemoryRegionInfo(addr, info)
+            self.assertTrue(err.Success())
+            self.assertEqual(info.HasProtectionKey(), valid)
+            self.assertEqual(info.GetProtectionKey(), key)
+
         # 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")
@@ -127,3 +171,13 @@ def test_poe_core(self):
                 "register read por",
                 substrs=[f"     {self.EXPECTED_POR}\n" + 
self.EXPECTED_POR_FIELDS],
             )
+
+        # Protection keys are listed in /proc/<pid>/smaps, which is not 
included
+        # in core files.
+        self.expect("memory region --all", substrs=["protection key:"], 
matching=False)
+
+        # No region should have a key at all, not even a default.
+        process = self.dbg.GetSelectedTarget().GetProcess()
+        for region in 
self.dbg.GetSelectedTarget().GetProcess().GetMemoryRegions():
+            self.assertEqual(region.HasProtectionKey(), False)
+            self.assertEqual(region.GetProtectionKey(), 0)
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c 
b/lldb/test/API/linux/aarch64/permission_overlay/main.c
index 6f47ba9d774da..ec2c0088b7084 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/main.c
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -81,6 +81,11 @@ int main(void) {
   const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
   const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
 
+  // This page will have the default key 0.
+  char *key_zero_page = mmap(NULL, page_size, prot, flags, -1, 0);
+  if (key_zero_page == MAP_FAILED)
+    exit(2);
+
   // Later we will use this to cause a protection key fault.
   char *read_only_page = NULL;
 
diff --git a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp 
b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
index d94bb4f4db982..f3f40cbc2f19d 100644
--- a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
+++ b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
@@ -93,20 +93,21 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[abc]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             "unexpected /proc/{pid}/maps exec permission char"),
         // Single entry
         std::make_tuple(
             "55a4512f7000-55a451b68000 rw-p 00000000 00:00 0    [heap]",
             MemoryRegionInfos{
-                MemoryRegionInfo(
-                    make_range(0x55a4512f7000, 0x55a451b68000),
-                    MemoryRegionInfo::eYes, MemoryRegionInfo::eYes,
-                    MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
-                    MemoryRegionInfo::eYes, ConstString("[heap]"),
-                    MemoryRegionInfo::eDontKnow, 0, 
MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+                MemoryRegionInfo(make_range(0x55a4512f7000, 0x55a451b68000),
+                                 MemoryRegionInfo::eYes, 
MemoryRegionInfo::eYes,
+                                 MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+                                 MemoryRegionInfo::eYes, ConstString("[heap]"),
+                                 MemoryRegionInfo::eDontKnow, 0,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             ""),
         // Multiple entries
@@ -116,27 +117,30 @@ INSTANTIATE_TEST_SUITE_P(
             "ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 "
             "[vsyscall]",
             MemoryRegionInfos{
-                MemoryRegionInfo(
-                    make_range(0x7fc090021000, 0x7fc094000000),
-                    MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
-                    MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
-                    MemoryRegionInfo::eYes, ConstString(nullptr),
-                    MemoryRegionInfo::eDontKnow, 0, 
MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
-                MemoryRegionInfo(
-                    make_range(0x7fc094000000, 0x7fc094a00000),
-                    MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
-                    MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
-                    MemoryRegionInfo::eYes, ConstString(nullptr),
-                    MemoryRegionInfo::eDontKnow, 0, 
MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+                MemoryRegionInfo(make_range(0x7fc090021000, 0x7fc094000000),
+                                 MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+                                 MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+                                 MemoryRegionInfo::eYes, ConstString(nullptr),
+                                 MemoryRegionInfo::eDontKnow, 0,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow, std::nullopt),
+                MemoryRegionInfo(make_range(0x7fc094000000, 0x7fc094a00000),
+                                 MemoryRegionInfo::eNo, MemoryRegionInfo::eNo,
+                                 MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+                                 MemoryRegionInfo::eYes, ConstString(nullptr),
+                                 MemoryRegionInfo::eDontKnow, 0,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow,
+                                 MemoryRegionInfo::eDontKnow, std::nullopt),
                 MemoryRegionInfo(
                     make_range(0xffffffffff600000, 0xffffffffff601000),
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
                     MemoryRegionInfo::eYes, ConstString("[vsyscall]"),
                     MemoryRegionInfo::eDontKnow, 0, 
MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+                    std::nullopt),
             },
             "")));
 
@@ -163,7 +167,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             "malformed /proc/{pid}/smaps entry, missing dash between address "
             "range"),
@@ -184,7 +188,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             ""),
         // Single shared region parses, has no flags
@@ -197,7 +201,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eYes,
                     ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             ""),
         // Single region with flags, other lines ignored
@@ -213,7 +217,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eNo),
+                    MemoryRegionInfo::eNo, std::nullopt),
             },
             ""),
         // Whitespace ignored
@@ -227,7 +231,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eNo),
+                    MemoryRegionInfo::eNo, std::nullopt),
             },
             ""),
         // VmFlags line means it has flag info, but nothing is set
@@ -241,7 +245,7 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eNo),
+                    MemoryRegionInfo::eNo, std::nullopt),
             },
             ""),
         // Handle some pages not having a flags line
@@ -258,14 +262,14 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[foo]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
                 MemoryRegionInfo(
                     make_range(0x3333, 0x4444), MemoryRegionInfo::eYes,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString("[bar]"), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eYes, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eNo),
+                    MemoryRegionInfo::eNo, std::nullopt),
             },
             ""),
         // Handle no pages having a flags line (older kernels)
@@ -283,14 +287,14 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
                 MemoryRegionInfo(
                     make_range(0x3333, 0x4444), MemoryRegionInfo::eYes,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eDontKnow),
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             ""),
         // We must look for exact flag strings, ignoring substrings of longer
@@ -305,7 +309,61 @@ INSTANTIATE_TEST_SUITE_P(
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
                     ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
                     MemoryRegionInfo::eNo, MemoryRegionInfo::eDontKnow,
-                    MemoryRegionInfo::eNo),
+                    MemoryRegionInfo::eNo, std::nullopt),
+            },
+            ""),
+        // 0 is the default protection key.
+        std::make_tuple(
+            "0-0 rw-p 00000000 00:00 0\n"
+            "ProtectionKey:          0",
+            MemoryRegionInfos{
+                MemoryRegionInfo(
+                    make_range(0, 0), MemoryRegionInfo::eYes,
+                    MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+                    MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+                    ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+                    MemoryRegionInfo::eDontKnow, 0),
+            },
+            ""),
+        std::make_tuple(
+            "0-0 rw-p 00000000 00:00 0\n"
+            "ProtectionKey:          99",
+            MemoryRegionInfos{
+                MemoryRegionInfo(
+                    make_range(0, 0), MemoryRegionInfo::eYes,
+                    MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+                    MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+                    ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+                    MemoryRegionInfo::eDontKnow, 99),
+            },
+            ""),
+        std::make_tuple(
+            "0-0 rw-p 00000000 00:00 0\n"
+            "ProtectionKey:      not_an_integer",
+            MemoryRegionInfos{
+                MemoryRegionInfo(
+                    make_range(0, 0), MemoryRegionInfo::eYes,
+                    MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+                    MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+                    ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
+            },
+            ""),
+        // Should be unsigned.
+        std::make_tuple(
+            "0-0 rw-p 00000000 00:00 0\n"
+            "ProtectionKey:      -24",
+            MemoryRegionInfos{
+                MemoryRegionInfo(
+                    make_range(0, 0), MemoryRegionInfo::eYes,
+                    MemoryRegionInfo::eYes, MemoryRegionInfo::eNo,
+                    MemoryRegionInfo::eNo, MemoryRegionInfo::eYes,
+                    ConstString(nullptr), MemoryRegionInfo::eDontKnow, 0,
+                    MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow,
+                    MemoryRegionInfo::eDontKnow, std::nullopt),
             },
             "")));
 
diff --git a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp 
b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
index 30199bfe5c254..4afae00b8fa5f 100644
--- a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
+++ b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp
@@ -236,7 +236,7 @@ static MemoryRegionInfo MakeRegionInfo(lldb::addr_t base, 
lldb::addr_t size,
       MemoryRegionInfo::eYes, ConstString(), MemoryRegionInfo::eNo, 0,
       /*memory_tagged=*/
       tagged ? MemoryRegionInfo::eYes : MemoryRegionInfo::eNo,
-      MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow);
+      MemoryRegionInfo::eDontKnow, MemoryRegionInfo::eDontKnow, std::nullopt);
 }
 
 TEST(MemoryTagManagerAArch64MTETest, MakeTaggedRange) {
diff --git 
a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp 
b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
index a5156326a1447..8dacbf136e931 100644
--- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
+++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
@@ -401,6 +401,7 @@ TEST_F(GDBRemoteCommunicationClientTest, 
GetMemoryRegionInfo) {
             region_info.IsStackMemory());
   EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow,
             region_info.IsShadowStack());
+  EXPECT_EQ(std::nullopt, region_info.GetProtectionKey());
 
   result = std::async(std::launch::async, [&] {
     return client.GetMemoryRegionInfo(addr, region_info);
@@ -433,6 +434,25 @@ TEST_F(GDBRemoteCommunicationClientTest, 
GetMemoryRegionInfo) {
                "start:a000;size:2000;type:heap;");
   EXPECT_TRUE(result.get().Success());
   EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsStackMemory());
+
+  result = std::async(std::launch::async, [&] {
+    return client.GetMemoryRegionInfo(addr, region_info);
+  });
+
+  HandlePacket(server, "qMemoryRegionInfo:a000",
+               "start:a000;size:2000;protection-key:42;");
+  EXPECT_TRUE(result.get().Success());
+  ASSERT_THAT(region_info.GetProtectionKey(),
+              ::testing::Optional(::testing::Eq(42)));
+
+  result = std::async(std::launch::async, [&] {
+    return client.GetMemoryRegionInfo(addr, region_info);
+  });
+
+  HandlePacket(server, "qMemoryRegionInfo:a000",
+               "start:a000;size:2000;protection-key:not_a_number;");
+  EXPECT_TRUE(result.get().Success());
+  ASSERT_THAT(region_info.GetProtectionKey(), std::nullopt);
 }
 
 TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfoInvalidResponse) {
diff --git a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp 
b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
index 44f653c6fa135..0bc31ec8eeee2 100644
--- a/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
+++ b/lldb/unittests/Process/minidump/MinidumpParserTest.cpp
@@ -382,23 +382,24 @@ TEST_F(MinidumpParserTest, GetMemoryRegionInfo) {
 
   EXPECT_THAT(
       parser->BuildMemoryRegions(),
-      testing::Pair(testing::ElementsAre(
-                        MemoryRegionInfo({0x0, 0x10000}, no, no, no, unknown,
-                                         no, ConstString(), unknown, 0, 
unknown,
-                                         unknown, unknown),
-                        MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown),
-                        MemoryRegionInfo({0x40000, 0x1000}, yes, no, no,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown),
-                        MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown),
-                        MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown)),
-                    true));
+      testing::Pair(
+          testing::ElementsAre(
+              MemoryRegionInfo({0x0, 0x10000}, no, no, no, unknown, no,
+                               ConstString(), unknown, 0, unknown, unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x10000, 0x21000}, yes, yes, no, unknown, yes,
+                               ConstString(), unknown, 0, unknown, unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x40000, 0x1000}, yes, no, no, unknown, yes,
+                               ConstString(), unknown, 0, unknown, unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x7ffe0000, 0x1000}, yes, no, no, unknown, yes,
+                               ConstString(), unknown, 0, unknown, unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x7ffe1000, 0xf000}, no, no, no, unknown, yes,
+                               ConstString(), unknown, 0, unknown, unknown,
+                               unknown, std::nullopt)),
+          true));
 }
 
 TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemoryList) {
@@ -420,14 +421,15 @@ TEST_F(MinidumpParserTest, 
GetMemoryRegionInfoFromMemoryList) {
 
   EXPECT_THAT(
       parser->BuildMemoryRegions(),
-      testing::Pair(testing::ElementsAre(
-                        MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown),
-                        MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown)),
-                    false));
+      testing::Pair(
+          testing::ElementsAre(
+              MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, unknown,
+                               yes, ConstString(), unknown, 0, unknown, 
unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, unknown,
+                               yes, ConstString(), unknown, 0, unknown, 
unknown,
+                               unknown, std::nullopt)),
+          false));
 }
 
 TEST_F(MinidumpParserTest, GetMemoryRegionInfoFromMemory64List) {
@@ -437,14 +439,15 @@ TEST_F(MinidumpParserTest, 
GetMemoryRegionInfoFromMemory64List) {
   // we don't have a MemoryInfoListStream.
   EXPECT_THAT(
       parser->BuildMemoryRegions(),
-      testing::Pair(testing::ElementsAre(
-                        MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown),
-                        MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown,
-                                         unknown, yes, ConstString(), unknown,
-                                         0, unknown, unknown, unknown)),
-                    false));
+      testing::Pair(
+          testing::ElementsAre(
+              MemoryRegionInfo({0x1000, 0x10}, yes, unknown, unknown, unknown,
+                               yes, ConstString(), unknown, 0, unknown, 
unknown,
+                               unknown, std::nullopt),
+              MemoryRegionInfo({0x2000, 0x20}, yes, unknown, unknown, unknown,
+                               yes, ConstString(), unknown, 0, unknown, 
unknown,
+                               unknown, std::nullopt)),
+          false));
 }
 
 TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) {
@@ -468,27 +471,28 @@ TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMaps) {
   ConstString app_process("/system/bin/app_process");
   ConstString linker("/system/bin/linker");
   ConstString liblog("/system/lib/liblog.so");
-  EXPECT_THAT(
-      parser->BuildMemoryRegions(),
-      testing::Pair(
-          testing::ElementsAre(
-              MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, no, yes,
-                               app_process, unknown, 0, unknown, unknown,
-                               unknown),
-              MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, no, yes,
-                               app_process, unknown, 0, unknown, unknown,
-                               unknown),
-              MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, no, yes,
-                               ConstString(), unknown, 0, unknown, unknown,
-                               unknown),
-              MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, no, yes,
-                               ConstString(), unknown, 0, unknown, unknown,
-                               unknown),
-              MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, no, yes,
-                               linker, unknown, 0, unknown, unknown, unknown),
-              MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, no, yes,
-                               liblog, unknown, 0, unknown, unknown, unknown)),
-          true));
+  EXPECT_THAT(parser->BuildMemoryRegions(),
+              testing::Pair(
+                  testing::ElementsAre(
+                      MemoryRegionInfo({0x400d9000, 0x2000}, yes, no, yes, no,
+                                       yes, app_process, unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt),
+                      MemoryRegionInfo({0x400db000, 0x1000}, yes, no, no, no,
+                                       yes, app_process, unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt),
+                      MemoryRegionInfo({0x400dc000, 0x1000}, yes, yes, no, no,
+                                       yes, ConstString(), unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt),
+                      MemoryRegionInfo({0x400ec000, 0x1000}, yes, no, no, no,
+                                       yes, ConstString(), unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt),
+                      MemoryRegionInfo({0x400ee000, 0x1000}, yes, yes, no, no,
+                                       yes, linker, unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt),
+                      MemoryRegionInfo({0x400fc000, 0x1000}, yes, yes, yes, no,
+                                       yes, liblog, unknown, 0, unknown,
+                                       unknown, unknown, std::nullopt)),
+                  true));
 }
 
 TEST_F(MinidumpParserTest, GetMemoryRegionInfoLinuxMapsError) {
@@ -508,7 +512,7 @@ TEST_F(MinidumpParserTest, 
GetMemoryRegionInfoLinuxMapsError) {
               testing::Pair(testing::ElementsAre(MemoryRegionInfo(
                                 {0x400fc000, 0x1000}, yes, yes, yes, no, yes,
                                 ConstString(nullptr), unknown, 0, unknown,
-                                unknown, unknown)),
+                                unknown, unknown, std::nullopt)),
                             true));
 }
 

>From 68521efced5d4138085a3d72bd22cdb5854ebcc8 Mon Sep 17 00:00:00 2001
From: David Spickett <[email protected]>
Date: Thu, 22 Jan 2026 14:53:26 +0000
Subject: [PATCH 2/2] [lldb][Linux] Add overlay and effective permissions to
 "memory region"

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.
---
 lldb/include/lldb/Target/ABI.h                | 25 ++++++++++
 lldb/source/Commands/CommandObjectMemory.cpp  | 23 +++++++++-
 .../Plugins/ABI/AArch64/ABISysV_arm64.cpp     | 46 +++++++++++++++++++
 .../Plugins/ABI/AArch64/ABISysV_arm64.h       |  5 ++
 .../permission_overlay/TestAArch64LinuxPOE.py | 21 +++++++--
 .../linux/aarch64/permission_overlay/main.c   |  7 +--
 6 files changed, 118 insertions(+), 9 deletions(-)

diff --git a/lldb/include/lldb/Target/ABI.h b/lldb/include/lldb/Target/ABI.h
index 1a1f1724222e3..4eb38d33c9eed 100644
--- a/lldb/include/lldb/Target/ABI.h
+++ b/lldb/include/lldb/Target/ABI.h
@@ -152,6 +152,31 @@ 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 permissions being overlayed for a given memory key, and the
+  /// resulting permissions after applying the overlay. Typically the 
protection
+  /// key is used to look up in some architecture specific set of permissions.
+  /// On AArch64, this is the POR register, used by the Permission Overlay
+  /// Extension.
+  ///
+  /// Returns std::nullopt if the current target does not have such an overlay
+  /// system, or if the protection key is not valid.
+  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 909761094f94b..f1e3479fc8f69 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1695,10 +1695,29 @@ class CommandObjectMemoryRegion : public 
CommandObjectParsed {
     if (is_shadow_stack == MemoryRegionInfo::OptionalBool::eYes)
       result.AppendMessage("shadow stack: yes");
     if (std::optional<unsigned> protection_key =
-            range_info.GetProtectionKey())
-      result.AppendMessageWithFormat("protection key: %" PRIu32 "\n",
+            range_info.GetProtectionKey()) {
+      result.AppendMessageWithFormat("protection key: %" PRIu32,
                                      *protection_key);
 
+      if (const lldb::ABISP &abi = target.GetProcessSP()->GetABI()) {
+        uint32_t base_permissions = range_info.GetLLDBPermissions();
+        if (auto permissions =
+                abi->GetMemoryPermissions(*m_exe_ctx.GetRegisterContext(),
+                                          *protection_key, base_permissions)) {
+          result.AppendMessageWithFormatv(
+              " ({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'
+                                                                    : '-');
+        }
+      } else
+        result.AppendMessage("");
+    }
+
     const std::optional<std::vector<addr_t>> &dirty_page_list =
         range_info.GetDirtyPageList();
     if (dirty_page_list) {
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..53c79ee2d6eb1 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
@@ -81,6 +81,11 @@ class ABISysV_arm64 : public ABIAArch64 {
   lldb::addr_t FixCodeAddress(lldb::addr_t pc) override;
   lldb::addr_t FixDataAddress(lldb::addr_t pc) override;
 
+  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 3b4bd8e55cf47..9b20b39095789 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -82,12 +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-)"],
+        )
+
+        # Overlay permissions are on their own line.
+        self.expect(
+            "memory region --all",
+            patterns=["\nprotection key: [0-9]+ \([rwx-]{3}, effective: 
[rwx-]{3}\)\n"],
         )
-        # 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 are also in SBMemoryRegionInfo.
         process = self.dbg.GetSelectedTarget().GetProcess()
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.

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

Reply via email to