[Lldb-commits] [lldb] [lldb][Linux] Add overlay and effective permissions to "memory region" (PR #184115)
David Spickett via lldb-commits
lldb-commits at lists.llvm.org
Wed Apr 1 05:17:03 PDT 2026
https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/184115
>From c6155ab3ceef56be5ba3801d8afcea047d507f77 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
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 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.
---
lldb/docs/resources/lldbgdbremote.md | 2 +
lldb/include/lldb/Target/MemoryRegionInfo.h | 11 ++++-
lldb/source/Commands/CommandObjectMemory.cpp | 2 +
.../Plugins/Process/Utility/LinuxProcMaps.cpp | 4 ++
.../GDBRemoteCommunicationClient.cpp | 4 ++
.../GDBRemoteCommunicationServerLLGS.cpp | 3 ++
lldb/source/Target/MemoryRegionInfo.cpp | 16 ++++----
.../permission_overlay/TestAArch64LinuxPOE.py | 17 ++++++++
.../linux/aarch64/permission_overlay/main.c | 5 +++
.../Process/Utility/LinuxProcMapsTest.cpp | 40 +++++++++++++++++++
.../GDBRemoteCommunicationClientTest.cpp | 20 ++++++++++
11 files changed, 115 insertions(+), 9 deletions(-)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 9aa7ad2259a6a..ef28b476bab27 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 d6bbe92e0ab58..16faf6ed9d64b 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -51,6 +51,8 @@ class MemoryRegionInfo {
LazyBool IsShadowStack() const { return m_is_shadow_stack; }
+ std::optional<unsigned> GetProtectionKey() const { return m_protection_key; }
+
void SetReadable(LazyBool val) { m_read = val; }
void SetWritable(LazyBool val) { m_write = val; }
@@ -81,6 +83,11 @@ class MemoryRegionInfo {
return *this;
}
+ MemoryRegionInfo &SetProtectionKey(std::optional<unsigned> key) {
+ m_protection_key = key;
+ return *this;
+ }
+
// Get permissions as a uint32_t that is a mask of one or more bits from the
// lldb::Permissions
uint32_t GetLLDBPermissions() const {
@@ -114,7 +121,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); }
@@ -157,6 +165,7 @@ class MemoryRegionInfo {
LazyBool m_memory_tagged = eLazyBoolDontKnow;
LazyBool m_is_stack_memory = eLazyBoolDontKnow;
LazyBool m_is_shadow_stack = eLazyBoolDontKnow;
+ 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 f8d9d027bdff0..24806c1af4229 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1694,6 +1694,8 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
LazyBool is_shadow_stack = range_info.IsShadowStack();
if (is_shadow_stack == eLazyBoolYes)
result.AppendMessage("shadow stack: yes");
+ if (std::optional<unsigned> protection_key = range_info.GetProtectionKey())
+ result.AppendMessageWithFormatv("protection key: {0}", *protection_key);
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 74142ce0f00be..d952b7dbdb816 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(eLazyBoolYes);
else if (flag == "ss")
region->SetIsShadowStack(eLazyBoolYes);
+ } 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 9ec0b07b592f7..43c6490d5d889 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 48f6648292c2c..5126016fcd0a8 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -2895,6 +2895,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 683ea20e7596e..eb0b860f8a737 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<LazyBool>::format(const LazyBool &B, raw_ostream &OS,
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index 056267a2dc900..f4e67b2f402e0 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -79,6 +79,19 @@ 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 should be on their own line.
+ self.expect("memory region --all", patterns=["\nprotection key: [0-9]+\n"])
+
# 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 +140,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 a6663cbd1b04b..fcfd76cc67960 100644
--- a/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
+++ b/lldb/unittests/Process/Utility/LinuxProcMapsTest.cpp
@@ -269,6 +269,46 @@ INSTANTIATE_TEST_SUITE_P(
.SetIsShadowStack(eLazyBoolYes)
.SetMemoryTagged(eLazyBoolNo),
},
+ ""),
+ // 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), eLazyBoolYes,
+ eLazyBoolYes, eLazyBoolNo,
+ eLazyBoolNo, eLazyBoolYes,
+ ConstString(nullptr))
+ .SetProtectionKey(0),
+ },
+ ""),
+ std::make_tuple("0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: 99",
+ MemoryRegionInfos{
+ MemoryRegionInfo(make_range(0, 0), eLazyBoolYes,
+ eLazyBoolYes, eLazyBoolNo,
+ eLazyBoolNo, eLazyBoolYes,
+ ConstString(nullptr))
+ .SetProtectionKey(99),
+ },
+ ""),
+ std::make_tuple("0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: not_an_integer",
+ MemoryRegionInfos{
+ MemoryRegionInfo(make_range(0, 0), eLazyBoolYes,
+ eLazyBoolYes, eLazyBoolNo,
+ eLazyBoolNo, eLazyBoolYes,
+ ConstString(nullptr)),
+ },
+ ""),
+ // Should be unsigned.
+ std::make_tuple("0-0 rw-p 00000000 00:00 0\n"
+ "ProtectionKey: -24",
+ MemoryRegionInfos{
+ MemoryRegionInfo(make_range(0, 0), eLazyBoolYes,
+ eLazyBoolYes, eLazyBoolNo,
+ eLazyBoolNo, eLazyBoolYes,
+ ConstString(nullptr)),
+ },
"")));
TEST_P(LinuxProcSMapsTestFixture, ParseSMapRegions) {
diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
index b6082b6acbd7c..d8cc3ff4f7d19 100644
--- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
+++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
@@ -398,6 +398,7 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) {
EXPECT_EQ(lldb_private::eLazyBoolDontKnow, region_info.GetMemoryTagged());
EXPECT_EQ(lldb_private::eLazyBoolDontKnow, region_info.IsStackMemory());
EXPECT_EQ(lldb_private::eLazyBoolDontKnow, region_info.IsShadowStack());
+ EXPECT_EQ(std::nullopt, region_info.GetProtectionKey());
result = std::async(std::launch::async, [&] {
return client.GetMemoryRegionInfo(addr, region_info);
@@ -429,6 +430,25 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) {
"start:a000;size:2000;type:heap;");
EXPECT_TRUE(result.get().Success());
EXPECT_EQ(lldb_private::eLazyBoolNo, 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) {
>From ac6461545dd2664e07adf0052e3f83c94b42c952 Mon Sep 17 00:00:00 2001
From: David Spickett <david.spickett at arm.com>
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 | 24 +++++++++-
.../Plugins/ABI/AArch64/ABISysV_arm64.cpp | 46 +++++++++++++++++++
.../Plugins/ABI/AArch64/ABISysV_arm64.h | 5 ++
.../permission_overlay/TestAArch64LinuxPOE.py | 22 ++++++---
.../linux/aarch64/permission_overlay/main.c | 7 +--
6 files changed, 118 insertions(+), 11 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 ®_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 24806c1af4229..d9a876f711162 100644
--- a/lldb/source/Commands/CommandObjectMemory.cpp
+++ b/lldb/source/Commands/CommandObjectMemory.cpp
@@ -1694,8 +1694,28 @@ class CommandObjectMemoryRegion : public CommandObjectParsed {
LazyBool is_shadow_stack = range_info.IsShadowStack();
if (is_shadow_stack == eLazyBoolYes)
result.AppendMessage("shadow stack: yes");
- if (std::optional<unsigned> protection_key = range_info.GetProtectionKey())
- result.AppendMessageWithFormatv("protection key: {0}", *protection_key);
+ if (std::optional<unsigned> protection_key =
+ range_info.GetProtectionKey()) {
+ Stream &strm = result.GetOutputStream();
+ strm << llvm::formatv("protection key: {0}", *protection_key);
+
+ if (const lldb::ABISP &abi = target.GetProcessSP()->GetABI()) {
+ if (auto permissions = abi->GetMemoryPermissions(
+ *m_exe_ctx.GetRegisterContext(), *protection_key,
+ range_info.GetLLDBPermissions())) {
+ strm << llvm::formatv(
+ " ({0}{1}{2}, effective: {3}{4}{5})",
+ permissions->overlay & lldb::ePermissionsReadable ? 'r' : '-',
+ permissions->overlay & lldb::ePermissionsWritable ? 'w' : '-',
+ permissions->overlay & lldb::ePermissionsExecutable ? 'x' : '-',
+ permissions->effective & lldb::ePermissionsReadable ? 'r' : '-',
+ permissions->effective & lldb::ePermissionsWritable ? 'w' : '-',
+ permissions->effective & lldb::ePermissionsExecutable ? 'x'
+ : '-');
+ }
+ }
+ strm.PutChar('\n');
+ }
const std::optional<std::vector<addr_t>> &dirty_page_list =
range_info.GetDirtyPageList();
diff --git a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
index aa9c20b6bb2cf..83a777da3237e 100644
--- a/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
+++ b/lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
@@ -884,3 +884,49 @@ void ABISysV_arm64::Initialize() {
void ABISysV_arm64::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}
+
+std::optional<ABISysV_arm64::MemoryPermissions>
+ABISysV_arm64::GetMemoryPermissions(lldb_private::RegisterContext ®_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 ®_ctx,
+ unsigned protection_key,
+ uint32_t original_permissions) override;
+
protected:
lldb::ValueObjectSP
GetReturnValueObjectImpl(lldb_private::Thread &thread,
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
index f4e67b2f402e0..affd66514b9bf 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
+++ b/lldb/test/API/linux/aarch64/permission_overlay/TestAArch64LinuxPOE.py
@@ -82,15 +82,25 @@ def test_poe_live(self):
# Unmapped region has no key (not even default).
self.expect("memory region 0", substrs=["protection key:"], matching=False)
- # The region has base permissions rwx, which is what we see here.
+ # The region has base permissions r-x, and overlay is r--. The result
+ # is that execution is disabled.
self.expect(
- "memory region read_only_page", substrs=["rwx", "protection key: 6"]
+ "memory region read_only_page",
+ substrs=["rw-", "protection key: 6 (r--, effective: r--)"],
+ )
+ # A region not assigned to a protection key has the default key 0. This
+ # key is rwx, but overlays cannot add permissions not already in the
+ # page table. So the execute permission is not enabled.
+ self.expect(
+ "memory region key_zero_page",
+ substrs=["rw-", "protection key: 0 (rwx, effective: rw-)"],
)
- # A region not assigned to a protection key has the default key 0.
- self.expect("memory region key_zero_page", substrs=["rwx", "protection key: 0"])
- # Protection keys should be on their own line.
- self.expect("memory region --all", patterns=["\nprotection key: [0-9]+\n"])
+ # Overlay permissions are on their own line.
+ self.expect(
+ "memory region --all",
+ patterns=["\nprotection key: [0-9]+ \([rwx-]{3}, effective: [rwx-]{3}\)\n"],
+ )
# Not passing this to the application allows us to fix the permissions
# using lldb, then continue to a normal exit.
diff --git a/lldb/test/API/linux/aarch64/permission_overlay/main.c b/lldb/test/API/linux/aarch64/permission_overlay/main.c
index ec2c0088b7084..5eb4782b9a6ca 100644
--- a/lldb/test/API/linux/aarch64/permission_overlay/main.c
+++ b/lldb/test/API/linux/aarch64/permission_overlay/main.c
@@ -76,9 +76,10 @@ int main(void) {
// Which leaves 7 keys available for programs to allocate.
const size_t page_size = (size_t)sysconf(_SC_PAGESIZE);
- // pkeys can only subtract from the set of permissions in the page table,
- // so we set the page table to allow everything.
- const int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
+ // pkeys can only subtract from the set of permissions in the page table.
+ // So we leave out execute here to check later that an overlay does not
+ // enable execution.
+ const int prot = PROT_READ | PROT_WRITE;
const int flags = MAP_PRIVATE | MAP_ANONYMOUS;
// This page will have the default key 0.
More information about the lldb-commits
mailing list