Skip to content

[lldb][Linux] Read memory protection keys for memory regions#182246

Merged
DavidSpickett merged 1 commit into
llvm:mainfrom
DavidSpickett:lldb-poe-pr2
Apr 24, 2026
Merged

[lldb][Linux] Read memory protection keys for memory regions#182246
DavidSpickett merged 1 commit into
llvm:mainfrom
DavidSpickett:lldb-poe-pr2

Conversation

@DavidSpickett

@DavidSpickett DavidSpickett commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

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.

(lldb) memory region --all
[0x0000000000000000-0x000aaaae3b140000) ---
[0x000aaaae3b140000-0x000aaaae3b150000) r-x /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out PT_LOAD[0]
protection key: 0
[0x000aaaae3b150000-0x000aaaae3b160000) rw- /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out
protection key: 0

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 the top level memory region command)

I have not added protection keys to the SBAPI because:

The ability to see the permissions the key refers to, and the final permissions after the overlay, will be in a follow up PR.

@llvmbot

llvmbot commented Feb 19, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-lldb

Author: David Spickett (DavidSpickett)

Changes

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 the top level memory region command)

I have not added protection keys to the SBAPI because:

The ability to see the permissions the key refers to, and the final permissions after the overlay, will be in a follow up PR.


Patch is 33.82 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/182246.diff

13 Files Affected:

  • (modified) lldb/docs/resources/lldbgdbremote.md (+2)
  • (modified) lldb/include/lldb/Target/MemoryRegionInfo.h (+11-3)
  • (modified) lldb/source/Commands/CommandObjectMemory.cpp (+3)
  • (modified) lldb/source/Plugins/Process/Utility/LinuxProcMaps.cpp (+4)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp (+4)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp (+3)
  • (modified) lldb/source/Target/MemoryRegionInfo.cpp (+8-8)
  • (modified) lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py (+14)
  • (modified) lldb/test/API/linux/aarch64/permission_overlay/main.c (+5)
  • (modified) lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp (+92-34)
  • (modified) lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp (+1-1)
  • (modified) lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp (+20)
  • (modified) lldb/unittests/Process/minidump/MinidumpParserTest.cpp (+59-55)
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/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/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp
index 93b6c1751b121..a8431c56f6415 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1694,6 +1694,9 @@ 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 11f164c2426ce..c112c7a3e1153 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -1686,6 +1686,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..cbde422f47bd2 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -79,6 +79,16 @@ 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"])
+
         # 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 +137,7 @@ 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)
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::nullo...
[truncated]

@DavidSpickett

DavidSpickett commented Feb 19, 2026

Copy link
Copy Markdown
Contributor Author

This is the next part of POE support. After this I have:

  • Showing keys and permissions in line, as well as the effective permissions after combining overlay and page table permissions.
  • Documentation and release notes.

@github-actions

github-actions Bot commented Feb 19, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 33394 tests passed
  • 514 tests skipped

✅ The build succeeded and all tests passed.

Comment thread lldb/docs/resources/lldbgdbremote.md Outdated
Comment thread lldb/docs/resources/lldbgdbremote.md Outdated
Comment thread lldb/source/Commands/CommandObjectMemory.cpp Outdated
@DavidSpickett

Copy link
Copy Markdown
Contributor Author

Ping @omjavaid

I have the next PRs up as well - #184115, #184119. So you can see the end point, and read the docs to see my intent.

I will be away next week but I'll address any feedback you have when I return.

@omjavaid

Copy link
Copy Markdown
Contributor

Ping @omjavaid

I have the next PRs up as well - #184115, #184119. So you can see the end point, and read the docs to see my intent.

I will be away next week but I'll address any feedback you have when I return.

Apologies for the delay. I will put my review later today

Comment thread lldb/docs/resources/lldbgdbremote.md
Comment thread lldb/include/lldb/Target/MemoryRegionInfo.h Outdated
Comment thread lldb/source/Target/MemoryRegionInfo.cpp
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 the top level
memory region command)

I have not added protection keys to the SBAPI because:
* I don't know of a specific need for them.
* They are very easy to add if someone does want them later
  (just a HasProtectionKey and GetProtectionKey).
* Arm's POE2 (https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/future-architecture-technologies-poe2-and-vmte)
  is coming soon, which makes these permissions more complex.
  There's no need to risk adding something too simple to handle
  those.

The ability to see the permissions the key refers to, and the final
permissions after the overlay, will be in a follow up PR.
@DavidSpickett

Copy link
Copy Markdown
Contributor Author

@omjavaid I've significantly reduced the arguments to MemoryRegionInfo and now this PR is just an extra getter/setter for the protection key.

Please take another look at this and #184115. With the assumption that we do not clash with GDB's implementation, which I will keep track of.

@DavidSpickett

Copy link
Copy Markdown
Contributor Author

ping @omjavaid

@omjavaid omjavaid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Thanks for waiting on the review.

@DavidSpickett
DavidSpickett merged commit 69724c8 into llvm:main Apr 24, 2026
12 checks passed
@DavidSpickett
DavidSpickett deleted the lldb-poe-pr2 branch April 24, 2026 10:19
@llvm-ci

llvm-ci commented Apr 24, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder lldb-x86_64-debian running on lldb-x86_64-debian while building lldb at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/162/builds/46247

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
PASS: lldb-api :: lang/cpp/template-alias/TestTemplateAlias.py (104 of 3440)
PASS: lldb-api :: functionalities/breakpoint/breakpoint_command/TestBreakpointCommand.py (105 of 3440)
PASS: lldb-api :: functionalities/breakpoint/breakpoint_conditions/TestBreakpointConditions.py (106 of 3440)
PASS: lldb-api :: tools/lldb-dap/startDebugging/TestDAP_startDebugging.py (107 of 3440)
PASS: lldb-api :: commands/expression/ir-interpreter/TestIRInterpreter.py (108 of 3440)
PASS: lldb-api :: functionalities/data-formatter/data-formatter-stl/generic/bitset/TestDataFormatterGenericBitset.py (109 of 3440)
PASS: lldb-api :: lang/cpp/template/TestTemplateArgs.py (110 of 3440)
PASS: lldb-api :: functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py (111 of 3440)
PASS: lldb-api :: types/TestShortTypeExpr.py (112 of 3440)
PASS: lldb-api :: functionalities/load_unload/TestLoadUnload.py (113 of 3440)
FAIL: lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py (114 of 3440)
******************** TEST 'lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py' FAILED ********************
Script:
--
/usr/bin/python3 /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./lib --env LLVM_INCLUDE_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/include --env LLVM_TOOLS_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin --triple x86_64-unknown-linux-gnu --build-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex --lldb-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/lldb --compiler /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/clang --dsymutil /home/worker/2.0.1/lldb-x86_64-debian/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./bin --lldb-obj-root /home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb --lldb-libs-dir /home/worker/2.0.1/lldb-x86_64-debian/build/./lib --cmake-build-type Release -t /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4 -p TestGdbRemoteLibrariesSvr4Support.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 23.0.0git (https://github.com/llvm/llvm-project.git revision 69724c88e3e0ed4b23d2fc446c3d472b414146bd)
  clang revision 69724c88e3e0ed4b23d2fc446c3d472b414146bd
  llvm revision 69724c88e3e0ed4b23d2fc446c3d472b414146bd
Skipping the following test categories: libc++, msvcstl, dsym, pdb, gmodules, debugserver, objc

--
Command Output (stderr):
--
Change dir to: /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4
UNSUPPORTED: LLDB (/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang-x86_64) :: test_libraries_svr4_libs_present_debugserver (TestGdbRemoteLibrariesSvr4Support.TestGdbRemoteLibrariesSvr4Support.test_libraries_svr4_libs_present_debugserver) (test case does not fall in any category of interest for this run) 
/usr/bin/gmake VPATH=/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4 -C /home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support -I /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4 -I /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make -f /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4/Makefile all TRIPLE=x86_64-unknown-linux-gnu CC=/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang CC_TYPE=clang CXX=/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang++ LLVM_AR=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-ar AR=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-ar OBJCOPY=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-objcopy STRIP=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-strip ARCHIVER=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-ar DWP=/home/worker/2.0.1/lldb-x86_64-debian/build/./bin/llvm-dwp CLANG_MODULE_CACHE_DIR=/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/module-cache-clang/lldb-api LLDB_OBJ_ROOT=/home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb OS=Linux HOST_OS=Linux

gmake: Entering directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'
"/usr/bin/gmake" -f /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/Makefile.rules \
	DYLIB_NAME=svr4lib_a DYLIB_CXX_SOURCES=svr4lib_a.cpp \
	DYLIB_ONLY=YES
gmake[1]: Entering directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'
gmake[1]: Nothing to be done for 'all'.
gmake[1]: Leaving directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'
"/usr/bin/gmake" -f /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/Makefile.rules \
	DYLIB_NAME=svr4lib_b\\\" DYLIB_CXX_SOURCES=svr4lib_b_quote.cpp \
	DYLIB_ONLY=YES
gmake[1]: Entering directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'
/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang   svr4lib_b_quote.o -g -O0  -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/../../../../..//include -I/home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb/include -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make -include /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/test_common.h -fno-limit-debug-info -target x86_64-unknown-linux-gnu -D_DEFAULT_SOURCE -mllvm -x86-asm-syntax=att      --driver-mode=g++ -shared -o "libsvr4lib_b\".so"
clang: warning: argument unused during compilation: '-mllvm -x86-asm-syntax=att' [-Wunused-command-line-argument]
gmake[1]: Leaving directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'
/home/worker/2.0.1/lldb-x86_64-debian/build/bin/clang main.o -g -O0  -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/../../../../..//include -I/home/worker/2.0.1/lldb-x86_64-debian/build/tools/lldb/include -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/test/API/tools/lldb-server/libraries-svr4 -I/home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make -include /home/worker/2.0.1/lldb-x86_64-debian/llvm-project/lldb/packages/Python/lldbsuite/test/make/test_common.h -fno-limit-debug-info -target x86_64-unknown-linux-gnu -D_DEFAULT_SOURCE -mllvm -x86-asm-syntax=att  -L. -lsvr4lib_a -lsvr4lib_b\"    -ldl --driver-mode=g++ -o "a.out"
clang: warning: argument unused during compilation: '-mllvm -x86-asm-syntax=att' [-Wunused-command-line-argument]
gmake: Leaving directory '/home/worker/2.0.1/lldb-x86_64-debian/build/lldb-test-build.noindex/tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support'

DavidSpickett added a commit that referenced this pull request Apr 24, 2026
…#193934)

Reverts #182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Apr 24, 2026
…ry regions" (#193934)

Reverts llvm/llvm-project#182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Apr 24, 2026
…ry regions" (#193934)

Reverts llvm/llvm-project#182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
cpullvm-upstream-sync Bot pushed a commit to navaneethshan/cpullvm-toolchain-1 that referenced this pull request Apr 24, 2026
…ry regions" (#193934)

Reverts llvm/llvm-project#182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
yingopq pushed a commit to yingopq/llvm-project that referenced this pull request Apr 29, 2026
…2246)

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.

```
(lldb) memory region --all
[0x0000000000000000-0x000aaaae3b140000) ---
[0x000aaaae3b140000-0x000aaaae3b150000) r-x /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out PT_LOAD[0]
protection key: 0
[0x000aaaae3b150000-0x000aaaae3b160000) rw- /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out
protection key: 0
```

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 the top level
memory region command).

I have not added protection keys to the SBAPI because:
* I don't know of a specific need for them.
* They are very easy to add if someone does want them later (just a
HasProtectionKey and GetProtectionKey).
* Arm's POE2
(https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/future-architecture-technologies-poe2-and-vmte)
is coming soon, which makes these permissions more complex. There's no
need to risk adding something too simple to handle those.

The ability to see the permissions the key refers to, and the final
permissions after the overlay, will be in a follow up PR.
yingopq pushed a commit to yingopq/llvm-project that referenced this pull request Apr 29, 2026
…llvm#193934)

Reverts llvm#182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…2246)

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.

```
(lldb) memory region --all
[0x0000000000000000-0x000aaaae3b140000) ---
[0x000aaaae3b140000-0x000aaaae3b150000) r-x /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out PT_LOAD[0]
protection key: 0
[0x000aaaae3b150000-0x000aaaae3b160000) rw- /tmp/test_lldb/linux/aarch64/permission_overlay/2/TestAArch64LinuxPOE/a.out
protection key: 0
```

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 the top level
memory region command).

I have not added protection keys to the SBAPI because:
* I don't know of a specific need for them.
* They are very easy to add if someone does want them later (just a
HasProtectionKey and GetProtectionKey).
* Arm's POE2
(https://developer.arm.com/community/arm-community-blogs/b/architectures-and-processors-blog/posts/future-architecture-technologies-poe2-and-vmte)
is coming soon, which makes these permissions more complex. There's no
need to risk adding something too simple to handle those.

The ability to see the permissions the key refers to, and the final
permissions after the overlay, will be in a follow up PR.
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…llvm#193934)

Reverts llvm#182246

Test failures on x86 bot:
```
Failed Tests (2):
  lldb-api :: tools/lldb-server/TestGdbRemote_qMemoryRegion.py
  lldb-api :: tools/lldb-server/libraries-svr4/TestGdbRemoteLibrariesSvr4Support.py
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants